iOS 正确控制导航leftBarButtonItems的姿势

leftBarButtonItems 结构分析

层级结构

我看了对比了iOS9 和iOS 11的层级结构,如下:

iOS9

iphone 7 iOS 11

可以看出,iOS11 之后NavigationBar的层级发生了较大变化。

结论

通过比较,我发现如下结论:

  1. 默认情况下,在320、375宽度的屏幕上,第一个按钮距离屏幕左边界的宽度是16,在414第一个按钮距离屏幕左边界的宽度是20。
  2. 默认情况下,在320、375宽度的屏幕上,BarButtonItems之间的间距是8,在414宽度的屏幕上,BarButtonItems之间的间距是10。
  3. iOS11 , 所有Items都包括在 _UIButtonBarStackView 下,控制它的X坐标即可控制左边距。
  4. iOS9,所有Item都在NavigationBar下,统计并排,所以控制左边距,只需要控制第一个元素的左边距。

需求自定义距屏幕左边距

我的做法很粗暴,直接去修改上面结论的坐标,步骤如下:

  1. 自定义UINavigationBar.
  2. 重写(void)drawRect:(CGRect)rect;
  3. 调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@interface CustomUINavigationBar : UINavigationBar
@property (nonatomic,assign) CGFloat leftValue;
@end
@implementation CustomUINavigationBar
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
if ([UIDevice currentDevice].systemVersion.floatValue >= 11.0) {
for (UIView *view in self.subviews) {
for (UIView *subView in view.subviews) {
if ([NSStringFromClass(subView.class) isEqualToString:@"_UIButtonBarStackView"]) {
subView.frame = CGRectMake(self.leftValue, subView.frame.origin.y, subView.frame.size.width, subView.frame.size.height);
}
}
}
}else{
for (int i=0; i<self.subviews.count; i++) {
UIView *t_view = self.subviews[i];
if (i==0) {
t_view.frame = CGRectMake(self.leftValue, t_view.frame.origin.y, t_view.frame.size.width, t_view.frame.size.height);
}
}
}
}
@end

注: 以上代码只适合leftBarButtonItems中只有一个元素的情况下。因为在多个元素的情况下,iOS10之前,items是平级的,直接在NavigationBar下,修改第一个元素无法修改第二个元素之后元素的坐标。

需求多个元素情况下距左边距

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@implementation CustomUINavigationBar
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
if ([UIDevice currentDevice].systemVersion.floatValue >= 11.0) {
for (UIView *view in self.subviews) {
for (UIView *subView in view.subviews) {
if ([NSStringFromClass(subView.class) isEqualToString:@"_UIButtonBarStackView"]) {
subView.frame = CGRectMake(self.leftValue, subView.frame.origin.y, subView.frame.size.width, subView.frame.size.height);
}
}
}
}else{
for (int i=0; i<self.subviews.count; i++) {
UIView *t_view = self.subviews[i];
if (i==0) {
t_view.frame = CGRectMake(self.leftValue, t_view.frame.origin.y, t_view.frame.size.width, t_view.frame.size.height);
}else{
// (1)相比前一段代码,多了如下几行。
if (SCREEN_WIDTH == 414) {
t_view.frame = CGRectMake(t_view.frame.origin.x-20+self.leftValue, t_view.frame.origin.y, t_view.frame.size.width, t_view.frame.size.height);
}else{
t_view.frame = CGRectMake(t_view.frame.origin.x-16+self.leftValue, t_view.frame.origin.y, t_view.frame.size.width, t_view.frame.size.height);
}
}
}
}
}
@end

注:请看注释(1)。为什么要对屏幕宽度判断,请参考结论1。

需求多个元素情况下,自定义items间的距离

  1. iOS 9

    ios9 层级结构

    如上图所示:计算items之间的元素距离就是计算b,的x坐标位置。

  2. iOS 11

    iOS 11

    如上图, 通过Xcode可以看出UIView(b)的宽度,坐标刚好符合 a c 之间的间隔,但是通过修改b的宽度确实无法实现修改items之间的间隔。只能通过修改 _UITAMICAdaptorView的x坐标来改边items之间的间距。

  3. 具体使用

1
2
3
4
5
6
7
8
9
10
11
12
//1. 使用自定义的NavigationBar
UINavigationController *nav = [[UINavigationController alloc] initWithNavigationBarClass:[CustomUINavigationBar class] toolbarClass:nil];
ViewController *vc = [[ViewController alloc] init];
[nav setViewControllers:@[vc]];
self.window.rootViewController = nav;
//2. 在设置完leftItems之后设置距离左边的距离,或者items之间的间距
/*..your setting..*/
self.navigationItem.leftBarButtonItems = @[leftBar, leftBar1];
CustomUINavigationBar *navbar = (CustomUINavigationBar *)self.navigationController.navigationBar;
navbar.leftValue = 10;
[navbar setItemsSpace:0];
  1. 最终代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
@interface CustomUINavigationBar : UINavigationBar
@property (nonatomic,assign) CGFloat leftValue;
- (void)setItemsSpace:(CGFloat)space;
@end
#define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
@interface CustomUINavigationBar()
@property (nonatomic, assign)CGFloat spaceBetweenItems;
@end
@implementation CustomUINavigationBar
- (instancetype)init
{
self = [super init];
if (self) {
self.spaceBetweenItems = -1024;
}
return self;
}
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
if ([UIDevice currentDevice].systemVersion.floatValue >= 11.0) {
for (UIView *view in self.subviews) {
for (UIView *subView in view.subviews) {
if ([NSStringFromClass(subView.class) isEqualToString:@"_UIButtonBarStackView"]) {
NSInteger count = 0;
for(int i= 1; i<subView.subviews.count; i++) {
UIView *t_subview = subView.subviews[i];
if ([NSStringFromClass(t_subview.class) isEqualToString:@"_UITAMICAdaptorView"] ) {
count ++;
if (SCREEN_WIDTH == 414) {
t_subview.frame = CGRectMake(t_subview.frame.origin.x - (10-self.spaceBetweenItems), t_subview.frame.origin.y, t_subview.frame.size.width, t_subview.frame.size.height);
}else{
t_subview.frame = CGRectMake(t_subview.frame.origin.x - (8-self.spaceBetweenItems), t_subview.frame.origin.y, t_subview.frame.size.width, t_subview.frame.size.height);
}
}
}
if (SCREEN_WIDTH == 414) {
subView.frame = CGRectMake(self.leftValue, subView.frame.origin.y, subView.frame.size.width - (count-1)*(10 - _spaceBetweenItems), subView.frame.size.height);
}else{
subView.frame = CGRectMake(self.leftValue, subView.frame.origin.y, subView.frame.size.width - (count-1)*(8 - _spaceBetweenItems), subView.frame.size.height);
}
}
}
}
}else{
for (int i=0; i<self.subviews.count; i++) {
UIView *t_view = self.subviews[i];
NSString *class = NSStringFromClass(t_view.class);
//_UINavigationBarBackIndicatorView 通过层级结构可以看出有这个view, 在这个不做任何修改,保持系统原样。
if ([class isEqualToString:@"_UINavigationBarBackIndicatorView"]) {
return;
}
if (i==0) {
t_view.frame = CGRectMake(self.leftValue, t_view.frame.origin.y, t_view.frame.size.width, t_view.frame.size.height);
}else{
if (SCREEN_WIDTH == 414) {
t_view.frame = CGRectMake((t_view.frame.origin.x-20+self.leftValue)-(10-self.spaceBetweenItems), t_view.frame.origin.y, t_view.frame.size.width, t_view.frame.size.height);
}else{
t_view.frame = CGRectMake((t_view.frame.origin.x-16+self.leftValue) -(8-self.spaceBetweenItems), t_view.frame.origin.y, t_view.frame.size.width, t_view.frame.size.height);
}
}
}
}
}
-(CGFloat)spaceBetweenItems {
if (_spaceBetweenItems == -1024) {
if (SCREEN_WIDTH == 414) {
return 10;
} else {
return 8;
}
}else{
return _spaceBetweenItems;
}
}
- (void)setItemsSpace:(CGFloat)space {
self.spaceBetweenItems = space;
}

拓展

如果你有兴趣可以继续往下考虑:

  1. items间距不一样如何处理?
  2. 能不能使用AOP实现?
  3. 如何进一步封装?

参考

美团齐刘海整理