天天看點

iOS 實作全屏右滑傳回功能解析!

由于大屏手機的廣泛運用,單手操作變得越加困難,尤其是對于手小的朋友,讓我如何單手玩手。而蘋果手機不像安卓手機能夠有傳回的實體鍵,對于app要全屏右滑或保持原生邊緣觸發,各有說辭,這裡不讨論其好壞.

下面先看一下實作效果.

iOS 實作全屏右滑傳回功能解析!

全屏pop

效果還不錯吧.當然了,這裡的所有效果都是系統實作的.或許你不信,一起看看實作吧.

#import "GLNavigationController.h"

@interface GLNavigationController () <UIGestureRecognizerDelegate>

@end

@implementation GLNavigationController 

- (void)viewDidLoad {
    [super viewDidLoad];
    //  這句很核心 稍後講解
    id target = self.interactivePopGestureRecognizer.delegate;
    //  這句很核心 稍後講解
    SEL handler = NSSelectorFromString(@"handleNavigationTransition:");
    //  擷取添加系統邊緣觸發手勢的View
    UIView *targetView = self.interactivePopGestureRecognizer.view;

    //  建立pan手勢 作用範圍是全屏
    UIPanGestureRecognizer * fullScreenGes = [[UIPanGestureRecognizer alloc]initWithTarget:target action:handler];
    fullScreenGes.delegate = self;
    [targetView addGestureRecognizer:fullScreenGes];

    // 關閉邊緣觸發手勢 防止和原有邊緣手勢沖突
    [self.interactivePopGestureRecognizer setEnabled:NO];
}

//  防止導航控制器隻有一個rootViewcontroller時觸發手勢
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
//解決與左滑手勢沖突  
  CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view];
    if (translation.x <= ) {
        return NO;
    }
    return self.childViewControllers.count ==  ? NO : YES;
}

@end
           

在實作之前,先推測一下蘋果實作pop的大概思路.首先,需要在一個合适的view上添加邊緣手勢,其次,針對這個手勢必然要實作一個方法響應該事件.當然,根據蘋果一貫代碼風格,處理該事件很可能交給另一個專門的類去處理.

假如以上推測成立,隻要獲得那個專門處理事件的類和方法,實作全屏pop效果就很簡單了.

下面是筆者在分析蘋果實作pop的部分資訊.看到這,是否若有所悟?

(lldb) pclass [self interactivePopGestureRecognizer]
// 資訊->1
UIScreenEdgePanGestureRecognizer
   | UIPanGestureRecognizer
   |    | UIGestureRecognizer
   |    |    | NSObject
(lldb) pclass [self interactivePopGestureRecognizer].delegate
// 資訊->2
_UINavigationInteractiveTransition
   | _UINavigationInteractiveTransitionBase
   |    | UIPercentDrivenInteractiveTransition
   |    |    | NSObject
(lldb) po [self interactivePopGestureRecognizer]
// 資訊->3
<UIScreenEdgePanGestureRecognizer: ; state = Possible; enabled = NO; delaysTouchesBegan = YES; view = <UILayoutContainerView >; target= <(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition >)>>

(lldb) po [self interactivePopGestureRecognizer].delegate
// 資訊->4
<_UINavigationInteractiveTransition: >

(lldb)
           

從資訊1中,可以知道interactivePopGestureRecognizer屬性并不是UIGestureRecognizer類型的對象,而是其子類UIPanGestureRecognizer的子類UIScreenEdgePanGestureRecognizer類型的對象.

UIScreenEdgePanGestureRecognizer是邊緣觸發手勢,在系統中公有API,裡面隻有一個edges屬性,用來設定具體邊緣有效,如左邊緣.具體可以參考官方API.

在資訊3中,可以看到

target= (action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition x7fab1243b850>)
           

這樣一條資訊,裡面包含了target和action.看到這是不是很興奮?iOS開發者再也屬性不過的目标-動作模式了.

到這裡,已經可以确定蘋果的實作方式是通過邊緣觸發手勢處理pop的.這裡target是私有的,如何獲得呢?于是,網上很多人開始使用runtime來獲得一些私有的方法.筆者一般不願在正式上線的項目中使用runtime獲得私有API,雖然不一定會被蘋果拒接,但是會有一定風險,畢竟筆者最近人品還沒爆發.

有沒不用運作時的好方法?

先别着急,繼續看資訊2和4, interactivePopGestureRecognizer的代理是_UINavigationInteractiveTransition,看類名可以想到該類和互動轉場相關.分析到這裡,基本上可以推測出蘋果是通過代理将事件處理委托給了_UINavigationInteractiveTransition對象.

在資訊3中,可以看到

target=<_UINavigationInteractiveTransition 0x7fab1243b850>

的位址是0x7fab1243b850,資訊4中

<_UINavigationInteractiveTransition: 0x7fab1243b850>

的位址也是0x7fab1243b850.

由以上分析,可以确定蘋果的實作方式是将處理邊緣觸發的事件的任務委托給了_UINavigationInteractiveTransition,在_UINavigationInteractiveTransition中有處理該事件的方法handleNavigationTransition:.

代碼分析

id target = self.interactivePopGestureRecognizer.delegate;
           

這句代碼目的是擷取事件處理對象.以便自己添加的手勢可以把事件處理委托給它.

SEL handler = NSSelectorFromString(@"handleNavigationTransition:");
           

這句就是擷取委托對象裡的處理方法.

UIPanGestureRecognizer * fullScreenGes = [[UIPanGestureRecognizer alloc]initWithTarget:target action:handler];
    fullScreenGes.delegate = self;
    [targetView addGestureRecognizer:fullScreenGes];
           

這幾句就是添加自己的全屏手勢,通過目标-動作模式把任務交給了系統委托對象處理.

建議

如果需要自定制導航時,實作是寫在UINavigationController子類中,比較友善.如果不需要,可以單獨寫一個分類.這裡寫在GLNavigationController中,其中GLNavigationController.h繼承自UINavigationController.

其他提示

  • 如果你的導航在不同控制器間有隐藏狀态欄的話,隐藏方法需要使用帶有animated:參數的方法setNavigationBarHidden: animated:,否則過渡會出問題.
  • 不用擔心稽核問題,是可以通過的.
  • 在此感謝前輩:Mrshang110 所做的貢獻.
  • 傳送門在此:點選下載下傳源碼