App開發流程之右滑返回手勢功能,app手勢

來源:互聯網
上載者:User

App開發流程之右滑返回手勢功能,app手勢

iOS7以後,導航控制器,內建了從螢幕左邊緣右滑返回的手勢功能。

但是,如果自訂了導覽列返回按鈕,這項功能就失效了,需要自行實現。又如果需要修改手勢觸發範圍,還是需要自行實現。

廣泛應用的一種實現方案是,採用私人變數和Api,完成手勢互動和返回功能,自訂手勢觸發條件和額外功能。

另一種實現方案是,採用UINavigationController的代理方法實現互動和動畫:

- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController

                          interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController NS_AVAILABLE_IOS(7_0);

 

- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController

                                   animationControllerForOperation:(UINavigationControllerOperation)operation

                                                fromViewController:(UIViewController *)fromVC

                                                  toViewController:(UIViewController *)toVC  NS_AVAILABLE_IOS(7_0);

前者,特點是便捷,但是只能使用系統定義的互動和動畫;後者,特點是高度自訂,但是需要額外實現互動協議和動畫協議。

 

採用私人變數和Api,實現右滑返回手勢功能。

先看最核心的邏輯:

- (void)base_pushViewController:(UIViewController *)viewController animated:(BOOL)animated{    if (![self.interactivePopGestureRecognizer.view.gestureRecognizers containsObject:self.base_panGestureRecognizer]) {        [self.interactivePopGestureRecognizer.view addGestureRecognizer:self.base_panGestureRecognizer];                //使用KVC擷取私人變數和Api,實現系統原生的pop手勢效果        NSArray *internalTargets = [self.interactivePopGestureRecognizer valueForKey:@"targets"];        id internalTarget = [internalTargets.firstObject valueForKey:@"target"];        SEL internalAction = NSSelectorFromString(@"handleNavigationTransition:");        self.base_panGestureRecognizer.delegate = [self base_panGestureRecognizerDelegateObject];        [self.base_panGestureRecognizer addTarget:internalTarget action:internalAction];                self.interactivePopGestureRecognizer.enabled = NO;    }        [self base_setupViewControllerBasedNavigationBarAppearanceIfNeeded:viewController];        if (![self.viewControllers containsObject:viewController]) {        [self base_pushViewController:viewController animated:animated];    }}

 

UINavigationController的interactivePopGestureRecognizer屬性,是系統專用於將viewController彈出導航棧的手勢識別對象,有一個私人變數名為“targets”,類似為NSArray;該數組第一個元素對象,有一個私人變數名為“target”,即為實現預期互動的對象;該對象有一個私人方法名為“handleNavigationTransition:”,即為目標方法。

在返回手勢互動的UIView(self.interactivePopGestureRecognizer.view)上添加一個自訂的UIPanGestureRecognizer,利用其delegate對象實現的代理方法gestureRecognizerShouldBegin來控制手勢生效條件。最後禁用系統的返回手勢識別對象,就可以用自訂實現的pan手勢來調用系統的pop互動和動畫。

判斷pan手勢是否能觸發返回操作的代碼如下:

-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{    //正在轉場    if ([[self.navigationController valueForKey:@"_isTransitioning"] boolValue]) {        return NO;    }        //在導航控制器的根控制器介面    if (self.navigationController.viewControllers.count <= 1) {        return NO;    }        UIViewController *popedController = [self.navigationController.viewControllers lastObject];        if (popedController.base_popGestureDisabled) {        return NO;    }        //滿足有效手勢範圍    CGPoint beginningLocation = [gestureRecognizer locationInView:gestureRecognizer.view];    CGFloat popGestureEffectiveDistanceFromLeftEdge = popedController.base_popGestureEffectiveDistanceFromLeftEdge;        if (popGestureEffectiveDistanceFromLeftEdge > 0        && beginningLocation.x > popGestureEffectiveDistanceFromLeftEdge) {        return NO;    }        //右滑手勢    UIPanGestureRecognizer *panGesture = (UIPanGestureRecognizer *)gestureRecognizer;    CGPoint transition = [panGesture translationInView:panGesture.view];        if (transition.x <= 0) {        return NO;    }        return YES;}

 

其中navigationController還使用了私人變數“_isTransitioning”,用於判斷互動是否進行中中。

 

為了使過場動畫過程中,導覽列的互動動畫自然,需要在UIViewController的viewWillAppear方法中,通過swizzle方法調用導覽列顯示或隱藏的動畫方法,所以需要增加一個順延強制的代碼塊:

- (void)base_setupViewControllerBasedNavigationBarAppearanceIfNeeded:(UIViewController *)appearingViewController{    //如果navigationController不顯示導覽列,直接return    if (self.navigationBarHidden) {        return;    }        __weak typeof(self) weakSelf = self;    ViewControllerViewWillAppearDelayBlock block = ^(UIViewController *viewController, BOOL animated) {        __strong typeof(weakSelf) strongSelf = weakSelf;        if (strongSelf) {            [strongSelf setNavigationBarHidden:viewController.base_currentNavigationBarHidden animated:animated];        }    };        appearingViewController.viewWillAppearDelayBlock = block;    UIViewController *disappearingViewController = self.viewControllers.lastObject;    if (disappearingViewController && !disappearingViewController.viewWillAppearDelayBlock) {        disappearingViewController.viewWillAppearDelayBlock = block;    }}

 

實現完整的分類的過程中,使用了一些運行時類型和方法。

1.引用標頭檔#import <objc/runtime.h>

2.為分類增加屬性,涉及到了如下方法:

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

id objc_getAssociatedObject(id object, const void *key)

例如UIViewController (PopGesture)中增加的屬性base_currentNavigationBarHidden的get/set方法:

- (BOOL)base_currentNavigationBarHidden{    NSNumber *number = objc_getAssociatedObject(self, _cmd);    if (number) {        return number.boolValue;    }        self.base_currentNavigationBarHidden = NO;    return NO;}- (void)setBase_currentNavigationBarHidden:(BOOL)hidden{    self.canUseViewWillAppearDelayBlock = YES;        objc_setAssociatedObject(self, @selector(base_currentNavigationBarHidden), @(hidden), OBJC_ASSOCIATION_RETAIN_NONATOMIC);}

 

第一個參數一般為self。

第二個參數const void *key,要求傳入一個地址。

可以聲明一個static char *key;或者static NSString *key;,賦值與否並不重要,因為需要的只是地址,參數為&key。

而上述代碼中使用了_cmd和@selector,作用是一樣的。_cmd返回的是當前方法的SEL,@selector也是返回目標方法的SEL,即是函數地址。

第三個參數即是關聯的值。

第四個參數policy,為枚舉類型,基本對應屬性引用相關的關鍵字:

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.                                             *   The association is not made atomically. */    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied.                                             *   The association is not made atomically. */    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.                                            *   The association is made atomically. */    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.                                            *   The association is made atomically. */};

 

3.理解其他的一些運行時類型或方法

typedef struct objc_method *Method;//An opaque type that represents a method in a class definition.

Method class_getInstanceMethod(Class cls, SEL name) //返回執行個體方法

Method class_getClassMethod(Class cls, SEL name) //返回類方法

IMP method_getImplementation(Method m) //Returns the implementation of a method.

const char *method_getTypeEncoding(Method m) //Returns a string describing a method's parameter and return types.

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) //Adds a new method to a class with a given name and implementation.

IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) //Replaces the implementation of a method for a given class.

void method_exchangeImplementations(Method m1, Method m2) //Exchanges the implementations of two methods.

 

以上方法,可以達到swizzle方法的目的,將分類中新增方法與已有舊的方法交換函數地址,可以作為完全替換(因為運行時,執行的方法名稱仍然為viewWillAppear:,但是指向新增方法地址),也可以在新增方法代碼中調用當前的方法名稱(交換後,當前的方法名稱指向舊方法地址)。例如UIViewController中的下列代碼:

+(void)load{    __weak typeof(self) weakSelf = self;        static dispatch_once_t once;    dispatch_once(&once, ^{        [weakSelf swizzleOriginalSelector:@selector(viewWillAppear:) withNewSelector:@selector(base_viewWillAppear:)];                [weakSelf swizzleOriginalSelector:@selector(viewDidDisappear:) withNewSelector:@selector(base_viewDidDisappear:)];    });}+(void)swizzleOriginalSelector:(SEL)originalSelector withNewSelector:(SEL)newSelector{    Class selfClass = [self class];        Method originalMethod = class_getInstanceMethod(selfClass, originalSelector);    Method newMethod = class_getInstanceMethod(selfClass, newSelector);        IMP originalIMP = method_getImplementation(originalMethod);    IMP newIMP = method_getImplementation(newMethod);        //先用新的IMP加到原始SEL中    BOOL addSuccess = class_addMethod(selfClass, originalSelector, newIMP, method_getTypeEncoding(newMethod));    if (addSuccess) {        class_replaceMethod(selfClass, newSelector, originalIMP, method_getTypeEncoding(originalMethod));    }else{        method_exchangeImplementations(originalMethod, newMethod);    }}-(void)base_viewWillAppear:(BOOL)animated{    [self base_viewWillAppear:animated];        if (self.canUseViewWillAppearDelayBlock        && self.viewWillAppearDelayBlock) {        self.viewWillAppearDelayBlock(self, animated);    }        if (self.transitionCoordinator) {        [self.transitionCoordinator notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {            if ([context isCancelled]) {                self.base_isBeingPoped = NO;            }        }];    }}

 

特別說明:

A.+load靜態方法將在此分類加入運行時調用(Invoked whenever a class or category is added to the Objective-C runtime),執行順序在該類自己的+load方法之後。

 

B.如果在使用中未明確設定base_currentNavigationBarHidden,canUseViewWillAppearDelayBlock則為NO,因為我封裝的父類中提供了類似功能,所以不需要開啟分類中同樣的功能。該功能目的是提供給直接整合分類的朋友。

C.UIViewController的transitionCoordinator屬性,在當前介面有過場互動時候,該屬性有值。並且在互動結束時候,可以回調一個block,以告知過場互動的狀態和相關屬性。這裡聲明了一個屬性base_isBeingPoped,用於標記當前視圖控制器是否正在被pop出導航棧,如果互動取消了,置為NO,最終可以在viewDidDisappear:方法中判斷並執行一些操作。

 

使用UINavigationController的代理方法來實現高度自訂的方案,下次再更新記錄。

 

base項目已更新:git@github.com:ALongWay/base.git

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.