使用過程中有卡頓和測(cè)試失效的問題,還需要繼續(xù)研究?jī)?yōu)化下
上一篇相關(guān)文章:iOS側(cè)滑pop返回的第三方整理研究
知識(shí)點(diǎn):
Runtime+分類+property現(xiàn)實(shí)屬性
前言
當(dāng)在實(shí)際開發(fā)中遇到使用系統(tǒng)navigationBar隱藏或顯示展示某些頁(yè)面,總共有以下4種可能:
顯示導(dǎo)航欄頁(yè)面A->顯示導(dǎo)航欄頁(yè)面B
顯示導(dǎo)航欄頁(yè)面A->隱藏導(dǎo)航欄頁(yè)面B
隱藏導(dǎo)航欄頁(yè)面A->顯示導(dǎo)航欄頁(yè)面B
隱藏導(dǎo)航欄頁(yè)面A->隱藏導(dǎo)航欄頁(yè)面B
在實(shí)際開發(fā)中,經(jīng)常很難同時(shí)處理好這幾種可能,經(jīng)常會(huì)出現(xiàn)導(dǎo)航欄突然閃一下或是進(jìn)入頁(yè)面后才隱藏導(dǎo)航欄,有些在側(cè)滑時(shí)會(huì)導(dǎo)航欄位置是空的或是黑的,顯得特別怪異,但FDFullscreenPopGesture卻很好的處理了這個(gè)難題,現(xiàn)在研究下這個(gè)庫(kù)的實(shí)現(xiàn)
用法
在使用FDFullscreenPopGesture這個(gè)庫(kù)時(shí),在需要隱藏系統(tǒng)導(dǎo)航欄的頁(yè)面的viewDidLoad方法里設(shè)置下fd_prefersNavigationBarHidden屬性,需要顯示導(dǎo)航欄的頁(yè)面什么都不處理,使用起來非常簡(jiǎn)單,如下
// 引入處理側(cè)滑pop返回及處理有無(wú)navbar的庫(kù)
#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文件里寫了一個(gè)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、對(duì)UINavigationController添加一個(gè)分類UINavigationController (FDFullscreenPopGesture),使用Runtime的swizzle黑魔法將pushViewController:animated:的實(shí)現(xiàn)替換,增加上額外的處理fd_pushViewController:animated:
,在這個(gè)增加額外的方法里的主要功能是
- 2.1、給UINavigationController的
interactivePopGestureRecognizer.view添加一個(gè)新的手勢(shì),這個(gè)添加的手勢(shì)代理是寫的另一個(gè)類,同時(shí)讓系統(tǒng)默認(rèn)的處理側(cè)滑pop返回的手勢(shì)注冊(cè)者失效,目的是讓重寫了navigationItem的backItem后也能響應(yīng)側(cè)滑返回
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、設(shè)置當(dāng)前即將要push的ViewController的當(dāng)要處理隱藏導(dǎo)航欄時(shí)的block,這個(gè)方法的邏輯是在push時(shí)給設(shè)置一個(gè)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;
這個(gè)block會(huì)在viewWillAppear:animated:這個(gè)hook的方法里回調(diào),而這個(gè)block的邏輯是根據(jù)fd_prefersNavigationBarHidden來動(dòng)態(tài)隱藏或顯示UINavigationBar,同時(shí)節(jié)將被隱藏的UIViewController如果沒有設(shè)置這個(gè)block,也會(huì)將同樣的邏輯設(shè)置給這個(gè)Controller,保證在UINavigationController的棧里管理的所有UIViewController都有這個(gè)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,當(dāng)前Controller解決不可見時(shí)也會(huì)執(zhí)行一段代碼,代碼邏輯為如果解決要push出來的代碼如果不隱藏導(dǎo)航欄,則設(shè)置
[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];
}
});
}
總結(jié)
對(duì)代碼進(jìn)行了深入的初步研究后發(fā)現(xiàn),原理是讓每個(gè)Controller的
viewWillAppear:animated:方法里都執(zhí)行了一遍是否隱藏導(dǎo)航欄的代碼邏輯,比如我在BaseViewController里定義了一個(gè)lh_hideNavBar熟悉,只要這樣調(diào)用就會(huì)OK,只是FDFullscreenPopGesture使用了分類的方式,另外也添加了更多判斷邏輯的代碼,我的代碼如下
GitHub:TestPopGestureSolution7
吸收了同事的寫法、TZScrollViewPopGesture、FDFullscreenPopGesture后寫了一個(gè)比較簡(jiǎn)單的封裝整理,全部代碼如下(總共112行,包含側(cè)滑、隱藏navbar、UIScrollView側(cè)滑):
UIViewController+LHNavigationGesture.h
#import <UIKit/UIKit.h>
@interface UIViewController (LHNavigationGesture) <UIGestureRecognizerDelegate>
/// 是否隱藏導(dǎo)航欄
@property (nonatomic,assign) BOOL lh_hideNavBar;
/// 給view添加側(cè)滑返回效果
- (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 - ******** 支持手勢(shì)pop側(cè)滑
+ (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的隱藏現(xiàn)實(shí)不突兀
+ (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側(cè)滑滾動(dòng)
- (void)lh_addPopGestureToView:(UIView *)view {
if (!view) return;
if (!self.navigationController) {
// 在控制器轉(zhuǎn)場(chǎng)的時(shí)候,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類型側(cè)滑滾動(dòng)
@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;
}
// 側(cè)滑手勢(shì)觸發(fā)位置
CGPoint location = [gestureRecognizer locationInView:self.view];
CGPoint offSet = [gestureRecognizer translationInView:gestureRecognizer.view];
BOOL ret = (0 < offSet.x && location.x <= 40);
return ret;
}
/// 只有當(dāng)系統(tǒng)側(cè)滑手勢(shì)失敗了,才去觸發(fā)ScrollView的滑動(dòng)
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
@end