IOS controls navigation leftBarButtonItems posture correctly

LeftBarButtonItems structure analysis

hierarchy

I looked at the hierarchy of iOS9 and iOS 11, and here it is:





iOS9





iphone7 ios 11

As you can see, the NavigationBar hierarchy has changed significantly since iOS11.

conclusion

Through comparison, I find the following conclusions:

  1. By default, the width of the first button from the left edge of the screen is 16 on 320, 375-wide screens, and 20 on 414.
  2. By default, the space between BarButtonItems is 8 on a 320, 375-width screen, and 10 on a 414-width screen.
  3. In iOS11, all Items are included in the _UIButtonBarStackView, and you can control the left margin by controlling its X coordinate.
  4. In iOS9, all items are under NavigationBar, statistics side by side, so to control the left margin, you just control the left margin of the first element.

Need to customize the left margin of the screen

My approach is very rude, directly modify the above conclusion coordinates, the steps are as follows:

  1. Customized UINavigationBar.
  2. Rewrite (void) drawRect: CGRect) the rect;
  3. The call.
@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 * the view in the 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); } } } } @endCopy the code

Note: The above code only works if there is only one element in leftBarButtonItems. Because in the case of multiple elements, items are flat until iOS10, so directly under NavigationBar, modifying the first element does not change the coordinates of the element after the second.

Left margin if multiple elements are required

@implementation CustomUINavigationBar - (void)drawRect:(CGRect)rect { [super drawRect:rect]; If ([UIDevice currentDevice]. SystemVersion. FloatValue > = 11.0) {for (UIView * the view in the 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) There are several more lines than the previous code. 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); } } } } } @endCopy the code

Note: Please refer to note (1). For why to judge the screen width, please refer to Conclusion 1.

Customize the distance between items if multiple elements are required

  1. iOS 9





    Ios9 hierarchy

    As shown in the figure above: To calculate the distance between items is to calculate the x-coordinate position of B.

  2. iOS 11





    iOS 11

    As shown in the figure above, the width of UIView(B) can be seen from Xcode. The coordinates just match the interval between A and C, but the interval between items cannot be changed by changing the width of B. The spacing between items can only be changed by changing the x coordinate of _UITAMICAdaptorView.

  3. The specific use

NavigationBar UINavigationController *nav = [UINavigationController alloc] NavigationBar UINavigationController *nav = [UINavigationController alloc] initWithNavigationBarClass:[CustomUINavigationBar class] toolbarClass:nil]; ViewController *vc = [[ViewController alloc] init]; [nav setViewControllers:@[vc]]; self.window.rootViewController = nav; //2. Set the distance to the left after setting leftItems, or the distance between items /*.. your setting.. */ self.navigationItem.leftBarButtonItems = @[leftBar, leftBar1]; CustomUINavigationBar *navbar = (CustomUINavigationBar *)self.navigationController.navigationBar; navbar.leftValue = 10; [navbar setItemsSpace:0];Copy the code
  1. The final code
@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 * the view in the 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 can be seen through the hierarchy have this view, in this don't do any changes, keep the system intact. 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; }Copy the code

expand

If you are interested, you can continue to consider:

  1. How to deal with different items spacing?
  2. Can it be implemented using AOP?
  3. How to further encapsulate?

reference

Meituan neat fringe finishing

Communication group





QQ group number: 264706196