demo主要包含以下几个模块
- 自定义UINavigationBar、UITabBar;
- 每一个UIViewController对应一个UINavigationController的封装;
- UITabBar凸出按钮的处理;
- 影响页面布局的几种属性探究;
先来看看项目中的实际效果图:
然后看看demo中的效果图:
一. 每个ViewController配置一个UINavigationController
1. 首先我们需要一个导航控制器类SXNavigationController
实际vc的导航控制器,并配置一些基本信息(如侧滑返回等)
2. 然后需要一个SXWrapViewController类
对所有实际vc(如SXHomeViewController)进行包装
所有页面最终都转换成 SXWrapViewController
3. 一个SXWrapNavigationController
不同SXWrapViewController对应的不同导航控制器类
最终我们push到的页面是SXWrapViewController类(对vc进行包装后的类),而不是实际的vc类,它的导航控制器是SXWrapNavigationController(单独对应),修改navigationbar也就是对SXWrapViewController对应的SXWrapNavigationController.navigationBar进行的修改
核心代码如下:
SXWrapNavigationController.m
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
//设置vc的导航控制器
viewController.sx_navigationController = (SXNavigationController *)self.navigationController;
if (self.viewControllers.count > 0) {
UIBarButtonItem *leftBarButtonItem = [UIBarButtonItem barButtonItemWithTarget:self andWithAction:@selector(didTapBackButton) andWithImage:@"Return"];
viewController.navigationItem.leftBarButtonItem = leftBarButtonItem;
viewController.hidesBottomBarWhenPushed = YES;
}
/* push 到的页面 是 SXWrapViewController 类 (对实际vc进行的包装) */
[self.navigationController pushViewController:[SXWrapViewController wrapViewControllerWithViewController:viewController] animated:animated];
}
SXWrapViewController.m
/*
* 不同vc对应单独UINavigationController的核心实现
* 实际vc(viewController) 最终都转换成 SXWrapViewController 进行包装
* SXWrapViewController的导航控制器是 SXWrapNavigationController
* 所以你在vc界面进行的navigationBar操作 都是对当前SXWrapViewController的导航控制器SXWrapNavigationController进行的(就此实现了vc与navi的单独对应)
* @param viewController 实际vc
*
*/
+ (SXWrapViewController *)wrapViewControllerWithViewController:(UIViewController *)viewController {
//单独控制实际vc
SXWrapNavigationController *wrapNavigationController = [[SXWrapNavigationController alloc] init];
wrapNavigationController.viewControllers = @[viewController];
//载体 push的vc最终都转化成了SXWrapViewController,SXWrapViewController的导航控制器是SXWrapNavigationController
SXWrapViewController *wrapViewController = [[SXWrapViewController alloc] init];
[wrapViewController.view addSubview:wrapNavigationController.view];
[wrapViewController addChildViewController:wrapNavigationController];
return wrapViewController;
}
这样我们就实现了每个viewController对应一个UINavigationController
那么下面就是对UINavigationBar的操作了
首先我们来看看navigationBar的层级结构(不同的ios系统层级结构不同)
ios9-10下的层级结构:
iOS11下的层级结构,这里我就不贴图了
_UINavigationBarBackground -> _UIBarBackground
同时_UIBarBackground下除了原先的UIImageView(下划线)又多了一层UIImageView
ios11.2.6的时候跟ios11.1没多大差别,就是标题view由
UINavigationItemView -> UINavigationContentView
另外如果你设置了navigationBar.backgroundColor或者translucent(透明度相关),他的层级结构又是另一番景象了(多2-3层),这里就不做展示了;
修改navigationBar相关信息以前走了很多弯路,现在记得的要么就是你使用系统方法
- (void)setBackgroundImage:(nullable UIImage *)backgroundImage forBarMetrics:(UIBarMetrics)barMetrics NS_AVAILABLE_IOS(5_0) UI_APPEARANCE_SELECTOR;
要么是向UINavigationBar最上面添加一个自定义view
if (!self.overlayView) {
[self setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
self.overlayView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.bounds), TOP_HEIGHT)];
self.overlayView.userInteractionEnabled = NO;
self.overlayView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[[self.subviews firstObject] insertSubview:self.overlayView atIndex:0];
}
self.overlayView.backgroundColor = backgroundColor;
最近两个项目我使用的都是第二种方法(各种实现都方便)
- 隐藏navigationBar
self.navigationController.navigationBarHidden = YES;
- navigationBar颜色渐变
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// 改变自定义view的alpha值实现渐变
CGFloat alphaRatio = contentOffSetY/(180 - 64) > 1 ? 1 : contentOffSetY/(180 - 64);
[self.navigationController.navigationBar setOverlayViewAlpha:alphaRatio];
}
- 改变navigationBar背景颜色
//修改的无非就是自定义view的背景色
[self.navigationController.navigationBar setOverlayViewBackgroundColor:[UIColor purpleColor]];
然后就是UINavigationBar下划线的处理
[[UINavigationBar appearance] setShadowImage:[UIImage imageWithColor:[UIColor redColor]]];
这里要注意的是只有在setBackgroundImage:forBarMetrics:这个方法实现的情况下,使用setShadowImage:方法修改下划线背景色才会生效(如下声明)
/* Default is nil. When non-nil, a custom shadow image to show instead of the default shadow image. For a custom shadow to be shown, a custom background image must also be set with -setBackgroundImage:forBarMetrics: (if the default background image is used, the default shadow image will be used).
*/
@property(nullable, nonatomic,strong) UIImage *shadowImage
二. UITabBar
UITabBar的自定义没什么可说的,说说一些细节问题吧
如何实现凸出按钮凸出部分点击事件的触发
这里写了一个继承自UITabBar的子类SXCustomTabBar,重写了它的- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;方法
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
//self.isHidden == NO 说明当前页面是有tabbar的,那么肯定是在导航控制器的根控制器页面
//在导航控制器根控制器页面,那么我们就需要判断手指点击的位置是否在发布按钮身上
//是的话让发布按钮自己处理点击事件,不是的话让系统去处理点击事件就可以了
NSLog(@"point: %@",NSStringFromCGPoint(point));
if (self.isHidden == NO) {
//将当前tabbar的触摸点转换坐标系,转换到发布按钮的身上,生成一个新的点
CGPoint newP = [self convertPoint:point toView:self.tabBarView.centerBtn];
NSLog(@"newpoint:%@",NSStringFromCGPoint(newP));
//判断如果这个新的点是在发布按钮身上,那么处理点击事件最合适的view就是发布按钮
if ( [self.tabBarView.centerBtn pointInside:newP withEvent:event]) {
return self.tabBarView.centerBtn;
}else{//如果点不在发布按钮身上,直接让系统处理就可以了
return [super hitTest:point withEvent:event];
}
}else {//tabbar隐藏了,那么说明已经push到其他的页面了,这个时候还是让系统去判断最合适的view处理就好了
return [super hitTest:point withEvent:event];
}
}
然后使用kvc替换掉系统的UITabBar
SXCustomTabBar *customTabBar = [[SXCustomTabBar alloc]init];
[self setValue:customTabBar forKeyPath:@"tabBar"];
这样就实现了点击tabbar外部触发点击事件
修改tabbar上面边线跟navigationbar相同
//删除tabbar顶部分割线
[[UITabBar appearance] setShadowImage:[UIImage new]];
[[UITabBar appearance] setBackgroundImage:[[UIImage alloc]init]];
同理,只有在实现setBackgroundImage:方法的情况下,setShadowImage:才会生效
其中凸出按钮(centerButton)是单独的一个按钮,因为中心凸出按钮的缘故,我是把shadowImage隐藏了,然后添加了左、右两条横线,宽度使用勾股定理计算sqrt()得出;
三. 其它详情参见demo
基本都会满足你现在UINavigationController+UITabBar框架的使用