KMNavigationBarTransition框架解析

本文解析了KMNavigationBarTransition框架,该框架由美团开发,用于定制UINavigationBar样式,实现不同控制器间的独特导航栏效果。内容包括框架结构、push和pop操作的原理,以及源码关键部分的解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

KMNavigationBarTransition是美团开发的专为定制UINavigationBar的样式,在不同的控制器中实现UINavigationBar不同地样式,使每个控制器实现我们想要的效果。

框架结构图:

框架原理:

pushController时:

disappearingViewController即将消失时,若存在动画,则将系统的Bar隐藏,重新生成一个新的Bar;

appearingViewController即将出现时,隐藏系统的Bar,生成新的Bar添加到当前控制器上,等到ViewDidAppear时将新加的Bar删掉,显示系统的Bar

popController时:

disappearingViewController即将消失时,若存在动画,则将系统的Bar隐藏,重新生成一个新的Bar;

appearingViewController即将出现时,隐藏系统的Bar,若上一步生成的Bar还在控制器上的话,等到ViewDidAppear时将生成的Bar删除,显示系统Bar

源码解析:

KMSwizzle.m文件转换函数实现功能

// KMSwizzle.h
#import <objc/runtime.h>

void KMSwizzleMethod(Class originalCls, SEL originalSelector, Class swizzledCls, SEL swizzledSelector) {
    Method originalMethod = class_getInstanceMethod(originalCls, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(swizzledCls, swizzledSelector);
    
    // 若originalCls当前类即子类没有实现时,则调用父类看是否有实现
    // 1.父类有实现时,class_addMethod返回YES
    // 2.父类无实现时,class_addMethod也返回YES,将swizzledMethod实现添加给originalSelector;若子类无originalSelector,则从父类查找。
    BOOL didAddMethod =
    class_addMethod(originalCls,
                    originalSelector,
                    method_getImplementation(swizzledMethod),
                    method_getTypeEncoding(swizzledMethod));
    
    if (didAddMethod) {
        //将originalCls的swizzledSelector用originalMethod替换
        class_replaceMethod(originalCls,
                            swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        //originalMethod有实现,直接转换
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

 KMWeakObjectContainer.m文件保存Vc控制器对象

// KMWeakObjectContainer
#import <objc/runtime.h>

@interface KMWeakObjectContainer : NSObject
@property (nonatomic, weak) id object; //用弱引用存放控制器对象,防止对象不被释放
@end

@implementation KMWeakObjectContainer

void km_objc_setAssociatedWeakObject(id container, void *key, id value)
{
    KMWeakObjectContainer *wrapper = [[KMWeakObjectContainer alloc] init];
    wrapper.object = value;
    // 通过runtime设置对象value
    objc_setAssociatedObject(container, key, wrapper, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

id km_objc_getAssociatedWeakObject(id container, void *key)
{
    // 通过runtime获取对象
    return [(KMWeakObjectContainer *)objc_getAssociatedObject(container, key) object];
}

 NSObject+KMNavigationBarTransition控制Bar的显隐藏

// NSObject+KMNavigationBarTransition
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        // 通过Swizzle将UIBackgroundView的setHidden转换为km_setHidden
        KMSwizzleMethod(objc_getClass("_UIBarBackground"),
                        @selector(setHidden:),
                        [self class],
                        @selector(km_setHidden:));
    });
}

- (void)km_setHidden:(BOOL)hidden {
    UIResponder *responder = (UIResponder *)self;
    while (responder) {
        if ([responder isKindOfClass:[UINavigationBar class]] && ((UINavigationBar *)responder).km_isFakeBar) {
            return;
        }
        if ([responder isKindOfClass:[UINavigationController class]]) {
            [self km_setHidden:((UINavigationController *)responder).km_backgroundViewHidden];
            return;
        }
        responder = responder.nextResponder;
    }
    [self km_setHidden:hidden];
}

 UINavigationBar+KMNavigationBarTransition文件设置Bar的frame

// UINavigationBar+KMNavigationBarTransition
- (void)km_layoutSubviews { // 设置backgroundView的frame
    [self km_layoutSubviews];
    UIView *backgroundView = [self valueForKey:@"_backgroundView"];
    CGRect frame = backgroundView.frame;
    frame.size.height = self.frame.size.height + fabs(frame.origin.y);
    backgroundView.frame = frame;
}

// 设置及获取NaviBar的透明样式
- (BOOL)km_isFakeBar {
    return [objc_getAssociatedObject(self, _cmd) boolValue];
}

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

 UINavigationController+KMNavigationBarTransition.m整个push/pop过程实现原理

// UINavigationController+KMNavigationBarTransition.m

- (void)km_pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
    UIViewController *disappearingViewController = self.viewControllers.lastObject;
    if (!disappearingViewController) {
        // 若push时无控制器,则交由pushViewController方法去处理
        return [self km_pushViewController:viewController animated:animated];
    }

    // 当self.km_transitionContextToViewController为空或者km_transitionNavigationBar为空时,给即将消失的disappearingViewController添加一条NavigationBar
    // push/pop时 km_transitionContextToViewController控制生成的Bar
    if (!self.km_transitionContextToViewController || !disappearingViewController.km_transitionNavigationBar) {
        [disappearingViewController km_addTransitionNavigationBarIfNeeded];
    }
    if (animated) {
    // 执行动画时,将本身的backgroundView隐藏
        self.km_transitionContextToViewController = viewController;
        if (disappearingViewController.km_transitionNavigationBar) {
            disappearingViewController.navigationController.km_backgroundViewHidden = YES;
        }
    }
    return [self km_pushViewController:viewController animated:animated];
}


- (UIViewController *)km_popViewControllerAnimated:(BOOL)animated {
    if (self.viewControllers.count < 2) {

        // 若self.viewControllers数量小于2时,交由系统方法处理
        return [self km_popViewControllerAnimated:animated];
    }

    // 即将消失控制器添加新的Bar
    UIViewController *disappearingViewController = self.viewControllers.lastObject;
    [disappearingViewController km_addTransitionNavigationBarIfNeeded];

    // pop回去显示的Vc
    UIViewController *appearingViewController = self.viewControllers[self.viewControllers.count - 2];

    // 设置self.navigationBar的样式
    if (appearingViewController.km_transitionNavigationBar) {
        UINavigationBar *appearingNavigationBar = appearingViewController.km_transitionNavigationBar;
        self.navigationBar.barTintColor = appearingNavigationBar.barTintColor;
        [self.navigationBar setBackgroundImage:[appearingNavigationBar backgroundImageForBarMetrics:UIBarMetricsDefault] forBarMetrics:UIBarMetricsDefault];
        self.navigationBar.shadowImage = appearingNavigationBar.shadowImage;
    }
    if (animated) {
        // 执行动画时将backgroundView隐藏
        disappearingViewController.navigationController.km_backgroundViewHidden = YES;
    }
    return [self km_popViewControllerAnimated:animated];
}

- (void)km_setViewControllers:(NSArray<UIViewController *> *)viewControllers animated:(BOOL)animated {
    UIViewController *disappearingViewController = self.viewControllers.lastObject;
    if (animated && disappearingViewController && ![disappearingViewController isEqual:viewControllers.lastObject]) {

        // 执行动画时,disappearingViewController添加新的Bar,隐藏系统Bar
        [disappearingViewController km_addTransitionNavigationBarIfNeeded];
        if (disappearingViewController.km_transitionNavigationBar) {
            disappearingViewController.navigationController.km_backgroundViewHidden = YES;
        }
    }
    return [self km_setViewControllers:viewControllers animated:animated];
}


- (void)km_addTransitionNavigationBarIfNeeded {
    if (!self.isViewLoaded || !self.view.window) { // view没加载或者view的window不存在时
        return;
    }
    if (!self.navigationController.navigationBar) { // 没有navigationBar
        return;
    }

    // 存在scrollView时,调整当前scrollView的offset
    [self km_adjustScrollViewContentOffsetIfNeeded];

    // 创建UINavigationBar及设置样式
    UINavigationBar *bar = [[UINavigationBar alloc] init];
    bar.km_isFakeBar = YES;
    bar.barStyle = self.navigationController.navigationBar.barStyle;
    if (bar.translucent != self.navigationController.navigationBar.translucent) {
        bar.translucent = self.navigationController.navigationBar.translucent;
    }
    bar.barTintColor = self.navigationController.navigationBar.barTintColor;
    [bar setBackgroundImage:[self.navigationController.navigationBar backgroundImageForBarMetrics:UIBarMetricsDefault] forBarMetrics:UIBarMetricsDefault];
    bar.shadowImage = self.navigationController.navigationBar.shadowImage;
    [self.km_transitionNavigationBar removeFromSuperview];
    self.km_transitionNavigationBar = bar;

    // 重新设置navigationBar的尺寸
    [self km_resizeTransitionNavigationBarFrame];
    if (!self.navigationController.navigationBarHidden && !self.navigationController.navigationBar.hidden) {
        // 满足条件时添加新创建的NavigationBar
        [self.view addSubview:self.km_transitionNavigationBar];
    }
}

- (void)km_adjustScrollViewContentOffsetIfNeeded {
    UIScrollView *scrollView = self.km_visibleScrollView;
    if (scrollView) {
        UIEdgeInsets contentInset;
#ifdef __IPHONE_11_0
        if (@available(iOS 11.0, *)) { // iOS11中adjustedContentInset是scrollView内边距属性
            contentInset = scrollView.adjustedContentInset;
        } else {
            contentInset = scrollView.contentInset;
        }
#else
        contentInset = scrollView.contentInset;
#endif

        // 调整scrollView的offset最合适的位置
        const CGFloat topContentOffsetY = -contentInset.top;
        const CGFloat bottomContentOffsetY = scrollView.contentSize.height - (CGRectGetHeight(scrollView.bounds) - contentInset.bottom);
    
        CGPoint adjustedContentOffset = scrollView.contentOffset;
        if (adjustedContentOffset.y > bottomContentOffsetY) {
            adjustedContentOffset.y = bottomContentOffsetY;
        }
        if (adjustedContentOffset.y < topContentOffsetY) {
            adjustedContentOffset.y = topContentOffsetY;
        }
        [scrollView setContentOffset:adjustedContentOffset animated:NO];
    }
}

/** 设置ScrollView的样式 **/
- (void)km_viewWillAppear:(BOOL)animated {
    [self km_viewWillAppear:animated];
    
    // 通过转场对象获取toViewController
    id<UIViewControllerTransitionCoordinator> tc = self.transitionCoordinator;
    UIViewController *toViewController = [tc viewControllerForKey:UITransitionContextToViewControllerKey];
    
    // push时presentationStyle为UIModalPresentationNone
    if ([self isEqual:self.navigationController.viewControllers.lastObject] && [toViewController isEqual:self]  && tc.presentationStyle == UIModalPresentationNone) {
        [self km_adjustScrollViewContentInsetAdjustmentBehavior];
        dispatch_async(dispatch_get_main_queue(), ^{
            if (self.navigationController.navigationBarHidden) {

                // navigationBarHidden为YES时,重新调整scrollView的样式
                [self km_restoreScrollViewContentInsetAdjustmentBehaviorIfNeeded];
            }
        });
    }
}

/** 重新设置系统Bar的样式 **/
- (void)km_viewDidAppear:(BOOL)animated {
    [self km_restoreScrollViewContentInsetAdjustmentBehaviorIfNeeded];
    UIViewController *transitionViewController = self.navigationController.km_transitionContextToViewController;

    // Appear时,重新设置Bar样式,移除self.km_transitionNavigationBar
    if (self.km_transitionNavigationBar) {
        self.navigationController.navigationBar.barTintColor = self.km_transitionNavigationBar.barTintColor;
        [self.navigationController.navigationBar setBackgroundImage:[self.km_transitionNavigationBar backgroundImageForBarMetrics:UIBarMetricsDefault] forBarMetrics:UIBarMetricsDefault];
        [self.navigationController.navigationBar setShadowImage:self.km_transitionNavigationBar.shadowImage];
        if (!transitionViewController || [transitionViewController isEqual:self]) {
            [self.km_transitionNavigationBar removeFromSuperview];
            self.km_transitionNavigationBar = nil; 
        }
    }
    if ([transitionViewController isEqual:self]) {
        self.navigationController.km_transitionContextToViewController = nil;
    }

    // 让Bar显示
    self.navigationController.km_backgroundViewHidden = NO;
    [self km_viewDidAppear:animated];
}

/** 生成新的Bar并设置样式,将其置顶显示 **/
- (void)km_viewWillLayoutSubviews {
    id<UIViewControllerTransitionCoordinator> tc = self.transitionCoordinator;
    UIViewController *fromViewController = [tc viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toViewController = [tc viewControllerForKey:UITransitionContextToViewControllerKey];
    
    if ([self isEqual:self.navigationController.viewControllers.lastObject] && [toViewController isEqual:self] && tc.presentationStyle == UIModalPresentationNone) {
        if (self.navigationController.navigationBar.translucent) {
            [tc containerView].backgroundColor = [self.navigationController km_containerViewBackgroundColor];
        }
        fromViewController.view.clipsToBounds = NO;
        toViewController.view.clipsToBounds = NO;
        if (!self.km_transitionNavigationBar) {

            // toViewController显示时创建新的NavigationBar,将backgroundView隐藏
            [self km_addTransitionNavigationBarIfNeeded];
            self.navigationController.km_backgroundViewHidden = YES;
        }

        // 设置Bar的frame
        [self km_resizeTransitionNavigationBarFrame];
    }
    if (self.km_transitionNavigationBar) {

        // 若km_transitionNavigationBar存在时,将其放在最上面显示
        [self.view bringSubviewToFront:self.km_transitionNavigationBar];
    }
    [self km_viewWillLayoutSubviews];
}

总结:框架实现原理比较简单,而且可以将不同UINavigationBar的样式实现分布于不同的控制器中,实现在不同控制器中定义不同的Bar样式

 

参考文章:

Objective-C Method Swizzling 的最佳实践

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值