FDFullScreenPopGesture源碼解析

FDFullScreenPopGesture僅300行不到的代碼就完美實現(xiàn)了絲滑的全屏滑動匿沛,如果是我們自己實現(xiàn)一個全屏滑動而且還能保證UINavigationBar良好的切換效果你會怎么做呢?

1.最簡單的則是在返回的VC中寫下如下代碼:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self.navigationController setNavigationBarHidden:YES animated:animated];
}

當然,如果場景不多還是可取,需要將其隱藏之后,在合適的時機又顯示出來夷家,場景一多可能就容易出亂子了

2.直接隱藏系統(tǒng)的UINavigationBar,自己去實現(xiàn)一個View放在頂部敏释,這種就是完全可自定義库快,不過花費代價也是稍大一些

3.使用截圖將前一個界面的視圖保存起來,自定義手勢钥顽,在滑動時判斷并顯示缺谴,這個需要處理的邏輯和情況也相對復雜,因為push和pop操作需要可以pop到上一級或者指定或者根視圖控制器等

4.實現(xiàn)UIViewControllerAnimatedTransitioning協(xié)議耳鸯,自定義轉場動畫湿蛔,在熟悉的情況下是可行的

5.今天的主角,F(xiàn)DFullScreenPopGesture县爬,完全解耦合阳啥,只需要拖入工程中就能實現(xiàn)該效果。那么這么好的東西他是如何實現(xiàn)的呢财喳?下面我們來看一下??

工程結構:

  • UINavigationController (FDFullscreenPopGesture)pushViewController:animated的hook

  • UIViewController (FDFullscreenPopGesture):主要進行viewWillAppear的hook

  • _FDFullscreenPopGestureRecognizerDelegate:負責管理UIGestureRecognizerDelegate的代理

  • UIViewController (FDFullscreenPopGesture):通過runtime添加幾個設置屬性

    ?

代碼解析:

_FDFullscreenPopGestureRecognizerDelegate

//實現(xiàn)了UIGestureRecognizerDelegate的gestureRecognizerShouldBegin代理方法察迟,對手勢的操作進行管理
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer
{
    //當navigationController中只有一個ViewController時返回NO,該手勢不生效
    if (self.navigationController.viewControllers.count <= 1) {
        return NO;
    }
    
    //當前的 ViewController 禁用了 fd_interactivePopDisabled耳高,fd_interactivePopDisabled是使用objc_getAssociatedObject在類別中添加的一個屬性扎瓶,用戶可設置是否禁用
  UIViewController *topViewController = self.navigationController.viewControllers.lastObject;
    if (topViewController.fd_interactivePopDisabled) {
        return NO;
    }
    //同理設置了一個fd_interactivePopMaxAllowedInitialDistanceToLeftEdge屬性用于設置最大左邊距,當滑動的x坐標大于他時也是無效的
    CGPoint beginningLocation = [gestureRecognizer locationInView:gestureRecognizer.view];
    CGFloat maxAllowedInitialDistance = topViewController.fd_interactivePopMaxAllowedInitialDistanceToLeftEdge;
    if (maxAllowedInitialDistance > 0 && beginningLocation.x > maxAllowedInitialDistance) {
        return NO;
    }

    //當前是否在轉場過程中泌枪。這里通過 KVC 拿到了 NavigationController 中的私有 _isTransitioning 屬性
    if ([[self.navigationController valueForKey:@"_isTransitioning"] boolValue]) {
        return NO;
    }
    
    // 從右往左滑動也是無效的
    CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view];
    if (translation.x <= 0) {
        return NO;
    }
    
    return YES;
}

UIViewController (FDFullscreenPopGesturePrivate)

typedef void (^_FDViewControllerWillAppearInjectBlock)(UIViewController *viewController, BOOL animated);//定義了一個block用于在Swizzling的fd_viewWillAppear方法中回調

//在整個文件被加載到運行時概荷,在 main 函數(shù)調用之前被 ObjC 運行時調用的鉤子方法
+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        
        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(fd_viewWillAppear:);
        
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        
        BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        if (success) {
          //主類本身沒有實現(xiàn)需要替換的方法,而是繼承了父類的實現(xiàn)碌燕,即 class_addMethod 方法返回 YES 误证。這時使用 class_getInstanceMethod 函數(shù)獲取到的 originalSelector 指向的就是父類的方法,我們再通過執(zhí)行 class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); 將父類的實現(xiàn)替換到我們自定義的 mrc_viewWillAppear 方法中修壕。這樣就達到了在 mrc_viewWillAppear 方法的實現(xiàn)中調用父類實現(xiàn)的目的愈捅。
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
          //主類本身有實現(xiàn)需要替換的方法,也就是 class_addMethod 方法返回 NO 慈鸠。這種情況的處理比較簡單蓝谨,直接交換兩個方法的實現(xiàn)就可以了
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)fd_viewWillAppear:(BOOL)animated
{
    // Method Swizzling之后, 調用fd_viewWillAppear:實際執(zhí)行的代碼已經(jīng)是原來viewWillAppear中的代碼了
    [self fd_viewWillAppear:animated];
    
    if (self.fd_willAppearInjectBlock) {
        self.fd_willAppearInjectBlock(self, animated);
    }
}

- (_FDViewControllerWillAppearInjectBlock)fd_willAppearInjectBlock
{
  //_cmd在Objective-C的方法中表示當前方法的selector,正如同self表示當前方法調用的對象實例
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setFd_willAppearInjectBlock:(_FDViewControllerWillAppearInjectBlock)block
{
    objc_setAssociatedObject(self, @selector(fd_willAppearInjectBlock), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

UINavigationController (FDFullscreenPopGesture)

//將此方法替換了pushViewController:animated:
- (void)fd_pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
  
    if (![self.interactivePopGestureRecognizer.view.gestureRecognizers containsObject:self.fd_fullscreenPopGestureRecognizer]) {
        
        // 將自定義的UIPanGestureRecognizer添加到本來interactivePopGestureRecognizer所在的view上
        [self.interactivePopGestureRecognizer.view addGestureRecognizer:self.fd_fullscreenPopGestureRecognizer];

        // 使用kvc拿到內部的targets數(shù)組,并找到他的target和action SEL譬巫,將fd_fullscreenPopGestureRecognizer的target設置為internalTarget咖楣,action設置為handleNavigationTransition.實際上就是將系統(tǒng)的手勢事件轉發(fā)為自定義的手勢,觸發(fā)的事件不變缕题,厲害吧截歉,能找到這些屬性也是牛逼炸了
        NSArray *internalTargets = [self.interactivePopGestureRecognizer valueForKey:@"targets"];
        id internalTarget = [internalTargets.firstObject valueForKey:@"target"];
        SEL internalAction = NSSelectorFromString(@"handleNavigationTransition:");
        self.fd_fullscreenPopGestureRecognizer.delegate = self.fd_popGestureRecognizerDelegate;
        [self.fd_fullscreenPopGestureRecognizer addTarget:internalTarget action:internalAction];

        // 將原來的手勢禁用
        self.interactivePopGestureRecognizer.enabled = NO;
    }
    
    // 通過 fd_prefersNavigationBarHidden 來顯示和隱藏 NavigationBar
    [self fd_setupViewControllerBasedNavigationBarAppearanceIfNeeded:viewController];
    
    //調用父類pushViewController:animated
    if (![self.viewControllers containsObject:viewController]) {
        [self fd_pushViewController:viewController animated:animated];
    }
}

- (void)fd_setupViewControllerBasedNavigationBarAppearanceIfNeeded:(UIViewController *)appearingViewController
{
    //如果設置屬性為NO,即為隱藏
    if (!self.fd_viewControllerBasedNavigationBarAppearanceEnabled) {
        return;
    }
    
    //viewWillAppear的時候將會調用該方法烟零,實際上內部也是通過調用setNavigationBarHidden:animated:來設置NavigationBar的顯示
    __weak typeof(self) weakSelf = self;
    _FDViewControllerWillAppearInjectBlock block = ^(UIViewController *viewController, BOOL animated) {
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (strongSelf) {
            [strongSelf setNavigationBarHidden:viewController.fd_prefersNavigationBarHidden animated:animated];
        }
    };
    
    // 將fd_willAppearInjectBlock注入到新的的view controller中
    // 將棧頂?shù)膙iewController拿出來并且判斷是否已經(jīng)注入了fd_willAppearInjectBlock瘪松,沒有則添加,因為并不一定每個vc都是通過push加入到棧的锨阿,也有可能通過"-setViewControllers:"
    appearingViewController.fd_willAppearInjectBlock = block;
    UIViewController *disappearingViewController = self.viewControllers.lastObject;
    if (disappearingViewController && !disappearingViewController.fd_willAppearInjectBlock) {
        disappearingViewController.fd_willAppearInjectBlock = block;
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末宵睦,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子墅诡,更是在濱河造成了極大的恐慌壳嚎,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件末早,死亡現(xiàn)場離奇詭異烟馅,居然都是意外死亡,警方通過查閱死者的電腦和手機然磷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門郑趁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人姿搜,你說我怎么就攤上這事寡润。” “怎么了舅柜?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵梭纹,是天一觀的道長。 經(jīng)常有香客問我致份,道長变抽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任知举,我火速辦了婚禮瞬沦,結果婚禮上,老公的妹妹穿的比我還像新娘雇锡。我一直安慰自己,他們只是感情好僚焦,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布锰提。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪立肘。 梳的紋絲不亂的頭發(fā)上边坤,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機與錄音谅年,去河邊找鬼茧痒。 笑死,一個胖子當著我的面吹牛融蹂,可吹牛的內容都是我干的旺订。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼超燃,長吁一口氣:“原來是場噩夢啊……” “哼区拳!你這毒婦竟也來了?” 一聲冷哼從身側響起意乓,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤樱调,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后届良,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體笆凌,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年士葫,在試婚紗的時候發(fā)現(xiàn)自己被綠了乞而。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡为障,死狀恐怖晦闰,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情鳍怨,我是刑警寧澤呻右,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站鞋喇,受9級特大地震影響声滥,放射性物質發(fā)生泄漏。R本人自食惡果不足惜侦香,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一落塑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧罐韩,春花似錦憾赁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蟆肆。三九已至,卻和暖如春晦款,著一層夾襖步出監(jiān)牢的瞬間炎功,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工缓溅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蛇损,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓坛怪,卻偏偏與公主長得像淤齐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子酝陈,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內容

  • 發(fā)現(xiàn) 關注 消息 iOS 第三方庫床玻、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,120評論 4 61
  • // 嘿沉帮,剛剛看知乎周刊教女孩子買鞋的屝馑溃刊,突然又想起之前對于家的想象 首先穆壕,我不確定我以后會給自己怎么樣的一個生...
    tree閱讀 160評論 0 1
  • 一個人在外的風評是非常重要的待牵,但風評不是自己給自己的,而是別人給你的喇勋。往往都是當事人不在場的時候缨该,口耳相傳所建立的...
    小皮同學閱讀 2,399評論 2 50
  • 作品目錄 上一話:平行世界的故事(三) 【七】圍剿(下) 京郊會所,二樓桑拿房川背。 “余佬啊贰拿,你說,這鐵公雞都能拔毛...
    沃若的簡書閱讀 422評論 0 9
  • mysql是apt-get安裝的1.給賬戶連接的權限 2.在mysqld.cnf找到bind-address = ...
    Mr_Laoyu閱讀 262評論 0 0