使用過程中有卡頓和測試失效的問題,還需要繼續(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