首先貼上sunnyxx大神的博客 一個絲滑的全屏滑動返回手勢
FDFullscreenPopGesture是通過替換系統(tǒng)原生的右滑返回手勢來實現(xiàn)全屏滑動返回的效果
手勢返回
為了實現(xiàn)對原有項目0侵入性,那么就需要hook原有方法來實現(xiàn)
+ (void)load
{
// Inject "-pushViewController:animated:"
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(pushViewController:animated:);
SEL swizzledSelector = @selector(fd_pushViewController:animated:);
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) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
hook這種也不需要詳解了,谷歌一下也有很多解釋了,最主要的還是要看被hook的fd_pushViewController:animated:方法
- (void)fd_pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
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;
}
// Handle perferred navigation bar appearance.
[self fd_setupViewControllerBasedNavigationBarAppearanceIfNeeded:viewController];
// Forward to primary implementation.
if (![self.viewControllers containsObject:viewController]) {
[self fd_pushViewController:viewController animated:animated];
}
}
第一步的做法是用自己創(chuàng)建的手勢來替換掉系統(tǒng)原生的返回手勢以實現(xiàn)全屏返回手勢
為什么自己創(chuàng)建的fd_fullscreenPopGestureRecognizer的target&action要設(shè)為系統(tǒng)默認的target&action呢?因為UINavigationController導航控制器的pop&push的層級關(guān)系是由系統(tǒng)管理的(參考[J_雨])。所以只需要決定fd_fullscreenPopGestureRecognizer是否能響應(yīng)就行了
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer
{
// Ignore when no view controller is pushed into the navigation stack.
if (self.navigationController.viewControllers.count <= 1) {
return NO;
}
// Ignore when the active view controller doesn't allow interactive pop.
UIViewController *topViewController = self.navigationController.viewControllers.lastObject;
if (topViewController.fd_interactivePopDisabled) {
return NO;
}
// Ignore when the beginning location is beyond max allowed initial distance to left edge.
CGPoint beginningLocation = [gestureRecognizer locationInView:gestureRecognizer.view];
CGFloat maxAllowedInitialDistance = topViewController.fd_interactivePopMaxAllowedInitialDistanceToLeftEdge;
if (maxAllowedInitialDistance > 0 && beginningLocation.x > maxAllowedInitialDistance) {
return NO;
}
// Ignore pan gesture when the navigation controller is currently in transition.
if ([[self.navigationController valueForKey:@"_isTransitioning"] boolValue]) {
return NO;
}
// Prevent calling the handler when the gesture begins in an opposite direction.
CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view];
BOOL isLeftToRight = [UIApplication sharedApplication].userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionLeftToRight;
CGFloat multiplier = isLeftToRight ? 1 : - 1;
if ((translation.x * multiplier) <= 0) {
return NO;
}
return YES;
}
當為以下情況的時候,全屏返回手勢不生效:
- 導航棧沒有push視圖控制器,也就是說導航棧只有根視圖或則沒有視圖的時候不生效
- 當前導航棧的棧頂視圖控制器不允許全屏返回
- 手勢開始的點小于設(shè)置的最大允許距離
- 當前正在進行過渡態(tài)動畫
- 當前手勢不是從左往后滑
導航欄的顯示隱藏
- (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;
}
}
每次push一個新的視圖控制器的時候,都會設(shè)置一個_FDViewControllerWillAppearInjectBlock,用于在視圖即將顯示的設(shè)置導航欄的顯示與隱藏,因此我們只需要簡單的設(shè)置fd_prefersNavigationBarHidden屬性,試圖控制器每次在即將顯示viewWillAppear:的時候設(shè)置導航欄是否隱藏就行了。
疑問
- (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];
}
});
}
對于這里,我不是很明白為什么在試圖控制器即將消失的時候,為什么還要設(shè)置導航欄隱藏呢?如果是pop的話,viewController為nil,但如果是push的話,不是已經(jīng)在viewWillAppear:的時候設(shè)置了導航了嗎?
個人感覺是最后避免想顯示導航欄卻沒顯示的不就措施吧?
希望有懂的大神能告訴我一下,私聊我也行,??評論區(qū)講解一下也想,當然有不同的想法的也可以在??評論區(qū)發(fā)表意見