UINavigationBar手勢側滑、隱藏bar钟沛、UIScrollView側滑返回研究二

使用過程中有卡頓和測試失效的問題,還需要繼續(xù)研究優(yōu)化下
上一篇相關文章:iOS側滑pop返回的第三方整理研究

知識點:

Runtime+分類+property現實屬性

前言

當在實際開發(fā)中遇到使用系統navigationBar隱藏或顯示展示某些頁面局扶,總共有以下4種可能:
顯示導航欄頁面A->顯示導航欄頁面B
顯示導航欄頁面A->隱藏導航欄頁面B
隱藏導航欄頁面A->顯示導航欄頁面B
隱藏導航欄頁面A->隱藏導航欄頁面B
在實際開發(fā)中恨统,經常很難同時處理好這幾種可能,經常會出現導航欄突然閃一下或是進入頁面后才隱藏導航欄三妈,有些在側滑時會導航欄位置是空的或是黑的畜埋,顯得特別怪異,但FDFullscreenPopGesture卻很好的處理了這個難題畴蒲,現在研究下這個庫的實現

用法

在使用FDFullscreenPopGesture這個庫時悠鞍,在需要隱藏系統導航欄的頁面的viewDidLoad方法里設置下fd_prefersNavigationBarHidden屬性,需要顯示導航欄的頁面什么都不處理模燥,使用起來非常簡單咖祭,如下

// 引入處理側滑pop返回及處理有無navbar的庫
#import "UINavigationController+FDFullscreenPopGesture.h"
@interface HomeController ()
@end

@implementation HomeController
#pragma mark - life cycle

- (void)viewDidLoad {
    [super viewDidLoad];
    self.fd_prefersNavigationBarHidden = YES;
}
@end

原理研究

1、在UINavigationController+FDFullscreenPopGesture文件里寫了一個UIViewController的分類UINavigationController+FDFullscreenPopGesture,并利用property和Runtime的方式給UIViewController添加fd_prefersNavigationBarHidden屬性

@interface UIViewController (FDFullscreenPopGesture)
@property (nonatomic, assign) BOOL fd_prefersNavigationBarHidden;
@end

@implementation UIViewController (FDFullscreenPopGesture)

- (BOOL)fd_prefersNavigationBarHidden
{
    return [objc_getAssociatedObject(self, _cmd) boolValue];
}

- (void)setFd_prefersNavigationBarHidden:(BOOL)hidden
{
    objc_setAssociatedObject(self, @selector(fd_prefersNavigationBarHidden), @(hidden), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

2蔫骂、對UINavigationController添加一個分類UINavigationController (FDFullscreenPopGesture)么翰,使用Runtime的swizzle黑魔法將pushViewController:animated:的實現替換,增加上額外的處理fd_pushViewController:animated:
,在這個增加額外的方法里的主要功能是

  • 2.1辽旋、給UINavigationController的interactivePopGestureRecognizer.view添加一個新的手勢浩嫌,這個添加的手勢代理是寫的另一個類,同時讓系統默認的處理側滑pop返回的手勢注冊者失效补胚,目的是讓重寫了navigationItem的backItem后也能響應側滑返回
if (![self.interactivePopGestureRecognizer.view.gestureRecognizers containsObject:self.fd_fullscreenPopGestureRecognizer]) {
        
        // Add our own gesture recognizer to where the onboard screen edge pan gesture recognizer is attached to.
        [self.interactivePopGestureRecognizer.view addGestureRecognizer:self.fd_fullscreenPopGestureRecognizer];
        
        // Forward the gesture events to the private handler of the onboard gesture recognizer.
        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];
        
        // Disable the onboard gesture recognizer.
        self.interactivePopGestureRecognizer.enabled = NO;
    }
  • 2.2码耐、設置當前即將要push的ViewController的當要處理隱藏導航欄時的block,這個方法的邏輯是在push時給設置一個block,如下
__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];
        }
    };
    appearingViewController.fd_willAppearInjectBlock = block;

這個block會在viewWillAppear:animated:這個hook的方法里回調,而這個block的邏輯是根據fd_prefersNavigationBarHidden來動態(tài)隱藏或顯示UINavigationBar溶其,同時節(jié)將被隱藏的UIViewController如果沒有設置這個block骚腥,也會將同樣的邏輯設置給這個Controller,保證在UINavigationController的棧里管理的所有UIViewController都有這個block瓶逃,全部代碼如下:

- (void)fd_setupViewControllerBasedNavigationBarAppearanceIfNeeded:(UIViewController *)appearingViewController
{
    if (!self.fd_viewControllerBasedNavigationBarAppearanceEnabled) {
        return;
    }
    
    __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];
        }
    };
    
    // Setup will appear inject block to appearing view controller.
    // Setup disappearing view controller as well, because not every view controller is added into
    // stack by pushing, maybe by "-setViewControllers:".
    appearingViewController.fd_willAppearInjectBlock = block;
    UIViewController *disappearingViewController = self.viewControllers.lastObject;
    if (disappearingViewController && !disappearingViewController.fd_willAppearInjectBlock) {
        disappearingViewController.fd_willAppearInjectBlock = block;
    }
}
  • 2.3桦沉、在UIViewController即將push出新的Controller每瞒,當前Controller解決不可見時也會執(zhí)行一段代碼,代碼邏輯為如果解決要push出來的代碼如果不隱藏導航欄,則設置[self.navigationController setNavigationBarHidden:NO animated:NO]
    全部代碼如下:
- (void)fd_viewWillDisappear:(BOOL)animated
{
    // Forward to primary implementation.
    [self fd_viewWillDisappear:animated];
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        UIViewController *viewController = self.navigationController.viewControllers.lastObject;
        if (viewController && !viewController.fd_prefersNavigationBarHidden) {
            [self.navigationController setNavigationBarHidden:NO animated:NO];
        }
    });
}

總結

對代碼進行了深入的初步研究后發(fā)現纯露,原理是讓每個Controller的viewWillAppear:animated:方法里都執(zhí)行了一遍是否隱藏導航欄的代碼邏輯剿骨,比如我在BaseViewController里定義了一個lh_hideNavBar熟悉,只要這樣調用就會OK,只是FDFullscreenPopGesture使用了分類的方式埠褪,另外也添加了更多判斷邏輯的代碼浓利,我的代碼如下

GitHub:TestPopGestureSolution7


吸收了同事的寫法性宏、TZScrollViewPopGesture珍逸、FDFullscreenPopGesture后寫了一個比較簡單的封裝整理,全部代碼如下(總共112行孽糖,包含側滑渴语、隱藏navbar苹威、UIScrollView側滑):

UIViewController+LHNavigationGesture.h

#import <UIKit/UIKit.h>
@interface UIViewController (LHNavigationGesture) <UIGestureRecognizerDelegate>
/// 是否隱藏導航欄
@property (nonatomic,assign) BOOL lh_hideNavBar;
/// 給view添加側滑返回效果
- (void)lh_addPopGestureToView:(UIView *)view;
@end

UIViewController+LHNavigationGesture.m

#import "UIViewController+LHNavigationGesture.h"
#import <objc/runtime.h>

@implementation UIViewController (LHNavigationGesture)
+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        [self swizzleBarHidden];
        [self swizzlePopGesture];
    });
}
#pragma mark - ******** 支持手勢pop側滑
+ (void)swizzlePopGesture
{
    Method viewDidLoad_originalMethod = class_getInstanceMethod(self, @selector(viewDidLoad));
    Method viewDidLoad_swizzledMethod = class_getInstanceMethod(self, @selector(lh_viewDidLoad));
    method_exchangeImplementations(viewDidLoad_originalMethod, viewDidLoad_swizzledMethod);
}
- (void)lh_viewDidLoad
{
    [self lh_viewDidLoad];
    self.navigationController.interactivePopGestureRecognizer.delegate = (id)self;
}
#pragma mark - ******** 支持navigationBar的隱藏現實不突兀
+ (void)swizzleBarHidden
{
    Method viewWillAppear_originalMethod = class_getInstanceMethod(self, @selector(viewWillAppear:));
    Method viewWillAppear_swizzledMethod = class_getInstanceMethod(self, @selector(lh_viewWillAppear:));
    method_exchangeImplementations(viewWillAppear_originalMethod, viewWillAppear_swizzledMethod);
}
- (void)lh_viewWillAppear:(BOOL)animated
{
    [self lh_viewWillAppear:animated];
    
    [self.navigationController setNavigationBarHidden:self.lh_hideNavBar animated:animated];
}

- (void)setLh_hideNavBar:(BOOL)lh_hideNavBar
{
    objc_setAssociatedObject(self, @selector(lh_hideNavBar), @(lh_hideNavBar), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)lh_hideNavBar
{
    return [objc_getAssociatedObject(self, _cmd) boolValue];
}
#pragma mark - ******** 支持UIScrollView側滑滾動
- (void)lh_addPopGestureToView:(UIView *)view {
    if (!view) return;
    if (!self.navigationController) {
        // 在控制器轉場的時候,self.navigationController可能是nil,這里用GCD和遞歸來處理這種情況
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self lh_addPopGestureToView:view];
        });
    } else {
        UIPanGestureRecognizer *pan = self.lh_popGestureRecognizer;
        if (![view.gestureRecognizers containsObject:pan]) {
            [view addGestureRecognizer:pan];
        }
    }
}

- (UIPanGestureRecognizer *)lh_popGestureRecognizer {
    UIPanGestureRecognizer *pan = objc_getAssociatedObject(self, _cmd);
    if (!pan) {
        
        NSArray *internalTargets = [self.navigationController.interactivePopGestureRecognizer valueForKey:@"targets"];
        id target = [internalTargets.firstObject valueForKey:@"target"];
        SEL action = NSSelectorFromString(@"handleNavigationTransition:");
        pan = [[UIPanGestureRecognizer alloc] initWithTarget:target action:action];
        pan.maximumNumberOfTouches = 1;
        pan.delegate = self.navigationController;
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
        objc_setAssociatedObject(self, _cmd, pan, OBJC_ASSOCIATION_ASSIGN);
    }
    return pan;
}
@end

#pragma mark  ******** 支持UIScrollView類型側滑滾動
@interface UINavigationController (LHPopGesturePrivate)
@end

@implementation UINavigationController (LHPopGesture)

- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer {
    if ([[self valueForKey:@"_isTransitioning"] boolValue]) {
        return NO;
    }
    if ([self.navigationController.transitionCoordinator isAnimated]) {
        return NO;
    }
    if (self.childViewControllers.count <= 1) {
        return NO;
    }
    
    // 側滑手勢觸發(fā)位置
    CGPoint location = [gestureRecognizer locationInView:self.view];
    CGPoint offSet = [gestureRecognizer translationInView:gestureRecognizer.view];
    BOOL ret = (0 < offSet.x && location.x <= 40);
    return ret;
}

/// 只有當系統側滑手勢失敗了驾凶,才去觸發(fā)ScrollView的滑動
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    return YES;
}

@end
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末牙甫,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子调违,更是在濱河造成了極大的恐慌窟哺,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件技肩,死亡現場離奇詭異且轨,居然都是意外死亡,警方通過查閱死者的電腦和手機虚婿,發(fā)現死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門旋奢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人然痊,你說我怎么就攤上這事黄绩。” “怎么了玷过?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長筑煮。 經常有香客問我辛蚊,道長,這世上最難降的妖魔是什么真仲? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任袋马,我火速辦了婚禮,結果婚禮上秸应,老公的妹妹穿的比我還像新娘虑凛。我一直安慰自己碑宴,他們只是感情好,可當我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布桑谍。 她就那樣靜靜地躺著延柠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锣披。 梳的紋絲不亂的頭發(fā)上贞间,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天,我揣著相機與錄音雹仿,去河邊找鬼增热。 笑死,一個胖子當著我的面吹牛胧辽,可吹牛的內容都是我干的峻仇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼邑商,長吁一口氣:“原來是場噩夢啊……” “哼摄咆!你這毒婦竟也來了?” 一聲冷哼從身側響起奠骄,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤豆同,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后含鳞,有當地人在樹林里發(fā)現了一具尸體影锈,經...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年蝉绷,在試婚紗的時候發(fā)現自己被綠了鸭廷。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡熔吗,死狀恐怖辆床,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情桅狠,我是刑警寧澤讼载,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站中跌,受9級特大地震影響咨堤,放射性物質發(fā)生泄漏。R本人自食惡果不足惜漩符,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一一喘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嗜暴,春花似錦凸克、人聲如沸议蟆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咐容。三九已至,卻和暖如春疟丙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鸟雏。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工享郊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人孝鹊。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓炊琉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親又活。 傳聞我的和親對象是個殘疾皇子苔咪,可洞房花燭夜當晚...
    茶點故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內容