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;
}
}