本文解决的问题
- 如何深度定制
NavigationBar
样式 - 统一设置APP的返回按钮
- 解决自定义leftItem后手势失效的问题
1. 自定义NavigationBar
有些APP的导航条定制程度很高,这边抛砖引玉说一下自己用过的一个简单的办法.
当你直接更改NavigationController的NavigationBar,会报一个错误.这个属性是get-only
的
! Cannot assign to property: 'navigationBar' is a get-only property
navigationBar = UINavigationBar()
可能你是用KVC赋值能够更改NavigationBar,但是我要说的思路也可以作为一个参考.
我们先看一下功能需求点,以及实现的效果
- 返回按钮图片自定义
- 返回按钮在侧滑手势激活的时候,跟随划出的控制器一起移动.(系统自带的
backItem
会留在原地渐渐变淡.) - navBar中的样式随着
ScrollView.contentOffset.y
动态改变.
1.1自定义样式.
- 设置自带的NavBar为全透明
- 创建一个和NavBar一样大小的view添加到NavBar的下面
//实例化navBarView
ZFHomeShopNavigationBarView *navBar = [[ZFHomeShopNavigationBarView alloc]init];
[self.view insertSubview:navBar aboveSubview:self.navigationController.navigationBar];
//设置约束
[navBar mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.leading.trailing.equalTo(self.view);
make.height.mas_equalTo(64);
}];
1.2 返回按钮在侧滑手势激活的时候,跟随界面一起移动.
其实这个效果在第一步就实现了. 我创建的navBar
对象实际上是一个view,返回按钮是固定在view内部的约束. 所以当我们将navBar的约束的leading
约束到控制器的view
上,控制器开始转场我们的navBar自然跟随着控制器移动.
1.3 动画改变样式.
这个案例中,navBar的背景色是从全透明慢慢的转为不透明的.
- 重写了
setAlpha:
方法,在方法内改变navBar的内部空间. - 其他的控件的颜色由白->黑
- 需要创建两套一模一样的
ImageView
,他们本身的素材颜色就是黑白两种颜色. 如果只有一套过渡效果在由黑变白的时候会不自然.如果自己需要实现可以先试一下一套 - 通过alpha,正反比设置两套控件的透明度
- (void)setAlpha:(CGFloat)alpha
{
//背景颜色成正比.
self.backgroundColor = [UIColor colorWithWhite:1 alpha:alpha];
//黑色控件透明度成正比
self.iv_ShareBlack.alpha = alpha;
self.iv_SearchBlack.alpha = alpha;
self.iv_BackBlack.alpha = alpha;
self.lblTitle.alpha = alpha;
self.blackLine.alpha = alpha;
//白色成反比
CGFloat alpha1 = 1 - alpha;
self.iv_BackWhite.alpha = alpha1;
self.iv_ShareWhite.alpha = alpha1;
self.iv_SearchWhite.alpha = alpha1;
}
1.4 需要增加两个item来监听点击事件
刚才在navBar这个view里面创建的都是ImageView.所以我们还需要在Nav上创建两个真实的item来接收点击事件. 在navBar里面的button接收不到点击
//创建2个item监听点击
self.navigationItem.rightBarButtonItems = @[[[UIBarButtonItem alloc]initWithImage:[UIImage imageNamed:@"navItemBackground"] style:UIBarButtonItemStyleDone target:self action:@selector(clickShare)],[[UIBarButtonItem alloc]initWithImage:[UIImage imageNamed:@"navItemBackground"] style:UIBarButtonItemStyleDone target:self action:@selector(clickSearch)]];
那么以上就是第一点,实现navBar的单个界面深度定制.这个办法比较傻比较简单.而且通用性强.不会影响到自带的手势,是一个不错的解决方案..
2. 统一设置APP的返回按钮
2.1 隐藏返回按钮文字
//设置导航条文字
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
这行代码可以写在BaseViewController中.也就是抽取的VC的基类.或者重写NavigationController.在push方法之前,对VC进行设置.不过需要判断栈内是否有VC,通常栈底的rootVC是不进行设置的.
2.2 更改navBar背景色,隐藏尴尬的黑线
//设置背景图片
[self.navigationBar setBackgroundImage:[UIImage imageNamed:@"nav"] forBarMetrics:UIBarMetricsDefault];
//设置阴影图片
[self.navigationBar setShadowImage:[UIImage new]];
- 背景色可以使用图片素材来更改
- 阴影是navBar下面会有一条很细的黑线,如果是白色的背景会看的比较明显.
- 可以写在Nav的基类中,或者在UINavigationController分类中重写loadView方法,貌似也可以.
2.3 统一设置返回按钮样式
//获取navBar的全局代理对象
UINavigationBar *allNavBar = [UINavigationBar appearance];
//返回按钮的图片
UIImage *backImage = [[UIImage imageNamed:@"home_nav_back"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
//设置返回按钮样式
[allNavBar setBackIndicatorImage:backImage];
[allNavBar setBackIndicatorTransitionMaskImage:backImage];
这句代码可以写在AppDelegate中,在设置完window的rootVC,在经典架构下rootVC通常是tabBar,tabBar的每一页通常是NavigationController.此时获取全局代理对象,进行统一的设置,以后都不需要管理了.
3. 解决自定义leftItem后手势失效的问题
以下为Swift代码,OC代码参考文章
3.1 继承NavigationController.
使用自己的Nav,并遵守两个协议
import UIKit
class ZFNavigationController: UINavigationController,UIGestureRecognizerDelegate
3.2 可以在push方法中进行按钮的统一设置
/// 重写push方法,每次push都为下一个控制器修改统一的返回按钮以及手势.
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
//判断当前的栈内有几个视图.为0的话是根控制器,根控制器不需要设置返回按钮以及返回手势
if self.childViewControllers.count > 0 {
//设置返回按钮,这里使用的分类中自定义的创建方法.
viewController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "返回" , image: #imageLiteral(resourceName: "navigationbar_back_withtext"), highlightedImage: #imageLiteral(resourceName: "navigationbar_back_withtext_highlighted"), target: self, action: #selector(back))
}
//调用父类方法
super.pushViewController(viewController, animated: animated)
/// 结束重写
}
3.3 解决手势问题.
/// 重写push方法,每次push都为下一个控制器修改统一的返回按钮以及手势.
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
//判断当前的栈内有几个视图.为0的话是根控制器,根控制器不需要设置返回按钮以及返回手势
if self.childViewControllers.count > 0 {
//设置返回按钮,这里使用的分类中自定义的创建方法.分类在3.4
viewController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "返回" , image: #imageLiteral(resourceName: "navigationbar_back_withtext"), highlightedImage: #imageLiteral(resourceName: "navigationbar_back_withtext_highlighted"), target: self, action: #selector(back))
//获取返回手势的代理
let target = interactivePopGestureRecognizer?.delegate
//自定义手势添加到返回手势的代理上
let pan = UIScreenEdgePanGestureRecognizer(target: target, action: NSSelectorFromString("handleNavigationTransition:"))
pan.edges = UIRectEdge.left
//如果需要使用全屏侧滑返回,可以直接使用pan手势,并且很奇妙的是也只能从左往右滑动生效
//let pan = UIPanGestureRecognizer(target: target, action: NSSelectorFromString("handleNavigationTransition:"))
//将自定义手势添加到控制器的View上
viewController.view.addGestureRecognizer(pan)
//设置代理,控制手势是否开启,以及滑动的方向距离等.若不需要可以不设置
pan.delegate = self
// 设置返回手势
interactivePopGestureRecognizer?.isEnabled = true
}
//调用父类方法
super.pushViewController(viewController, animated: animated)
/// 结束重写
}
/// 返回按钮绑定的点击事件
@objc private func back() {
popViewController(animated: true)
}
/// 手势的代理,开启手势识别.弱不需要额外的控制可以不设置
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
3.4 创建barItem的分类
import UIKit
extension UIBarButtonItem {
convenience init(title : String = "" , image : UIImage? , highlightedImage : UIImage? , target: Any?, action: Selector) {
let barButton = UIButton()
//设置title
barButton.setTitle(title, for: .normal)
barButton.setTitleColor(UIColor.init(red: 0.24, green: 0.24, blue: 0.24, alpha: 1), for: .normal)
barButton.setTitleColor(UIColor.init(red: 1, green: 0.46, blue: 0, alpha: 1), for: .highlighted)
barButton.titleLabel?.font = UIFont.systemFont(ofSize: 14)
//设置图片
if let image = image {
barButton.setImage(image, for: .normal)
}
//高亮图片
if let highlightedImage = highlightedImage {
barButton.setImage(highlightedImage, for: .highlighted)
}
//添加target
barButton.addTarget(target, action: action, for: .touchUpInside)
//尺寸适应
barButton.sizeToFit()
//构造.
self.init()
//添加到CustomView
customView = barButton
}
}