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样式
参考文章: