IOS11 navigation bar button location problem solved

This article is iOS11 in beta when written, was mainly to solve the problem, and did not think too much optimization, later found that the amount of code is relatively large, and there will be some problems, such as the Settings of multiple buttons, such as the loss of constraints, now for a new idea to achieve

A new solution iOS11 navigation button location problem — new blog.csdn.net/spicyShrimp…

Of course, this article can still be implemented as a solution. Although iOS11 has not come out yet, as a developer, I believe that many developers are using Xcode beta for debugging, to prevent iOS11 when it comes out, the existing application incompatible problems can not be solved in time, I also do so. I have to say the iOS 11 changes are pretty big. The estimatedRowHeight in the table, the navigation header, the safe zone… .Of course that’s not what I’m talking about. As a developer, it’s a minimum requirement to adapt to a new SDK or framework as soon as possible, and it would be great if it was compatible with a beta.

So without further ado, as an iOS developer, we all know that it’s not the interface, it’s not the feature, it’s the navigation bar that we often don’t care about. The reason is simple.

  • Automatic System Management
  • High encapsulation, difficult to modify
  • There are many layers, many layers that you can’t use interspersed among them, resulting in the development effect

For example, we set the background color of NavigationBar, we set the position offset of UIBarButtonItem, etc. Of course, these are all studied long ago, and there are many solutions

  • Nothing more than a completely custom NavigationBar, not applicable to the system’s NavigationBar
  • Modify NavigationBar or UIBarButtonItem of the system directly or indirectly

Here I will talk about the position of the navigation buttons

After iOS7, we set the UINavigationItem leftBarButtonItem, rightBarButtonItem leads to the location of the migration, although is not big, but with the UI design or habits of the people is a little difference. Of course, there is also a good solution to add a negative UIBarButtonItem with a negative width

+ (UIBarButtonItem *)fixedSpaceWithWidth:(CGFloat)width {

    UIBarButtonItem *fixedSpace = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
    fixedSpace.width = width;
    return fixedSpace;
}Copy the code

When we add the navigation bar button we can use it to meet the need to adjust the position of the button

[self.navigationItem setRightBarButtonItems:@[[UIBarButtonItem fixedSpaceWithWidth:-20], rightBarButtonItem]];Copy the code

But none of this works in iOS 11!!!! But none of this works in iOS 11!!!! But none of this works in iOS 11!!!!

Say important things three times.

One of the biggest changes to iOS 11 is the navigation bar, which has a new layer on top of the already complicated one!

Yes you do not have as _UINavigationBarContentView and _UIButtonBarStackView and _UITAMICAdaptorView And the leftBarButtonItem thing that we had before is now in the UIButtonBarStackView. And there’s more to it than that

<_UIButtonBarStackView: 0x7ff988074290; frame = (12 0; 48 44); layer = <CALayer: 0x60000042bc80>>
Printing description of $11:
<UIView: 0x7ff9880764a0; frame = (0 22; 8 0); layer = <CALayer: 0x60000042b7c0>>
Printing description of $12:
<_UITAMICAdaptorView: 0x7ff988076790; frame = (8 2; 40 40); autoresizesSubviews = NO; layer = <CALayer: 0x60000042b8a0>>Copy the code

We can see that a _UIButtonBarStackView takes up 12 pixels of the left constraint, and _UITAMICAdaptorView takes up 8 pixels of the left constraint, so we are speechless and occupy 20px. Unfortunately, they are private objects and not easy to modify!

So again, we set negative values to adjust constraints, and it failed. It didn’t work… Forced to think of something new,

  • Get rid of UIBarButtonItem, get rid of UINavigationBar, use custom view instead
  • Add a view in UINavigationBar with a fixed position and size add button
  • UIBarButtonItem. CustomView set migration (such as button to set the offset images View Settings tranform, etc.)
  • Modify the UIBarButtonItem layer structure (delete the layer, or modify the constraints)

Of course, completely use custom views instead of native UINavigationBar and UIBarButtonItem, which I don’t need to explain here. It’s a custom view. I’m sure we can solve it

I’m going to do addSubview, add, remove, whatever, but that’s not what I want, right

As far as this is an offset, the results are also bleak and ineffective. I tried to set the rotation to work, but the setting position to the left will not work. Very has no language

Why bother with the code? Before iOS 11, most of our projects were managed by UINavigationBar and UIBarButtonItem, which is also the system. Now, if we have to modify too much code because of an offset problem, won’t it be very troublesome? Can it be done at a low cost? The answer is yes,

We might make a classification like this

@implementation UINavigationItem (SXFixSpace)+ (void)load {
    [self swizzleInstanceMethodWithOriginSel:@selector(setLeftBarButtonItem:)
                                 swizzledSel:@selector(sx_setLeftBarButtonItem:)];
    [self swizzleInstanceMethodWithOriginSel:@selector(setRightBarButtonItem:)
                                 swizzledSel:@selector(sx_setRightBarButtonItem:)]; } - (void)sx_setLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem{
    if (leftBarButtonItem.customView) {[self sx_setLeftBarButtonItem:nil];
        [self setLeftBarButtonItems:@[[UIBarButtonItem fixedSpaceWithWidth:-20], leftBarButtonItem]];
    }else{[self setLeftBarButtonItems:nil];
        [self sx_setLeftBarButtonItem:leftBarButtonItem];
    }
}

-(void)sx_setRightBarButtonItem:(UIBarButtonItem *)rightBarButtonItem{
    if (rightBarButtonItem.customView) {[self sx_setRightBarButtonItem:nil];
        [self setRightBarButtonItems:@[[UIBarButtonItem fixedSpaceWithWidth:-20], rightBarButtonItem]];
    }else{[self setRightBarButtonItems:nil];
        [selfsx_setRightBarButtonItem:rightBarButtonItem]; }}@endCopy the code

Before we got to iOS11, we used a classification like this to extend it so that we could use it in vc

self.navigationItem.leftBarButtonItem = [UIBarButtonItem itemWithTarget:self action:@selector(sx_pressBack:) image:[UIImage imageNamed:@"nav_back"]].Copy the code

We can adjust the position of our buttons

So how about iOS 11 without knowing the code?

So only a little bit more, in the category

- (void)sx_setLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem{
    if (leftBarButtonItem.customView) {
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 11) {
            // If you adjust it, implement it here, so that it does not affect the code
        }else{[self sx_setLeftBarButtonItem:nil];
            [self setLeftBarButtonItems:@[[UIBarButtonItem fixedSpaceWithWidth:-20], leftBarButtonItem]]; }}else{[self setLeftBarButtonItems:nil];
        [selfsx_setLeftBarButtonItem:leftBarButtonItem]; }}Copy the code

We can figure out where to write it, and how to write it next. My idea is that since it is a customView, can I extend it? We originally used a button as a customView directly, like this

[[UIBarButtonItem alloc] initWithCustomView:button];Copy the code

But what I want to do now is add the button to a view that we’ve defined as a customView so that the view can be relatively freely defined as a positioning view

- (void)sx_setLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem{
    if (leftBarButtonItem.customView) {
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 11) {
            UIView *customView = leftBarButtonItem.customView;
            BarView *barView = [[BarView alloc]initWithFrame:customView.bounds];
            [barView addSubview:customView];
            customView.center = barView.center;
            [barView setPosition:SXBarViewPositionLeft];// The left side of the view needs to be adjusted
            [self setLeftBarButtonItems:nil];
            [self sx_setLeftBarButtonItem:[[UIBarButtonItem alloc]initWithCustomView:barView]];
        }else{[self sx_setLeftBarButtonItem:nil];
            [self setLeftBarButtonItems:@[[UIBarButtonItem fixedSpaceWithWidth:-20], leftBarButtonItem]]; }}else{[self setLeftBarButtonItems:nil];
        [selfsx_setLeftBarButtonItem:leftBarButtonItem]; }}Copy the code

So we can do something with this view as well

typedef NS_ENUM(NSInteger, SXBarViewPosition) {
    SXBarViewPositionLeft,
    SXBarViewPositionRight
};

@interface BarView : UIView
@property (nonatomic.assign) SXBarViewPosition position;
@property (nonatomic.assign) BOOL applied;
@end

@implementation BarView

- (void)layoutSubviews {
    [super layoutSubviews];
    if (self.applied || [[[UIDevice currentDevice] systemVersion] floatValue]  < 11) return;
    UIView *view = self;
    while(! [view isKindOfClass:UINavigationBar.class] && view.superview) {
        view = [view superview];
        if ([view isKindOfClass:UIStackView.class] && view.superview) {
            if (self.position == SXBarViewPositionLeft) {
                for (NSLayoutConstraint *constraint in view.superview.constraints) {
                    if ([constraint.firstItem isKindOfClass:[UILayoutGuide class]] &&
                     constraint.firstAttribute == NSLayoutAttributeTrailing) {
                        [view.superview removeConstraint:constraint];
                    }
                }
                [view.superview addConstraint:[NSLayoutConstraint constraintWithItem:view
                                                                           attribute:NSLayoutAttributeLeading
                                                                           relatedBy:NSLayoutRelationEqual
                                                                              toItem:view.superview
                                                                           attribute:NSLayoutAttributeLeading
                                                                          multiplier:1.0
                                                                            constant:0]].self.applied = YES;
            } else if (self.position == SXBarViewPositionRight) {
                for (NSLayoutConstraint *constraint in view.superview.constraints) {
                    if ([constraint.firstItem isKindOfClass:[UILayoutGuide class]] &&
                     constraint.firstAttribute == NSLayoutAttributeLeading) {
                        [view.superview removeConstraint:constraint];
                    }
                }
                [view.superview addConstraint:[NSLayoutConstraint constraintWithItem:view
                                                                           attribute:NSLayoutAttributeTrailing
                                                                           relatedBy:NSLayoutRelationEqual
                                                                              toItem:view.superview
                                                                           attribute:NSLayoutAttributeTrailing
                                                                          multiplier:1.0
                                                                            constant:0]].self.applied = YES;
            }
            break; }}}@endCopy the code

Code is not complicated, actually, is the parent view traverse the view, when in fact UIStackView, we modify the control constraints, but just change words will create constraint conflict, so we also need to remove constraint conflict about constraints in advance (if worry about effect, does not remove the no relationship, is simply the compiler will quote log constraint conflict, generation to generation Code freak will feel uncomfortable)

So with a slight extension of the original classification, our new classification is complete

#import "UINavigationItem+SXFixSpace.h"
#import "NSObject+SXRuntime.h"
#import <UIKit/UIKit.h>

typedef NS_ENUM(NSInteger, SXBarViewPosition) {
    SXBarViewPositionLeft,
    SXBarViewPositionRight
};

@interface BarView : UIView
@property (nonatomic.assign) SXBarViewPosition position;
@property (nonatomic.assign) BOOL applied;
@end

@implementation BarView

- (void)layoutSubviews {
    [super layoutSubviews];
    if (self.applied || [[[UIDevice currentDevice] systemVersion] floatValue]  < 11) return;
    UIView *view = self;
    while(! [view isKindOfClass:UINavigationBar.class] && view.superview) {
        view = [view superview];
        if ([view isKindOfClass:UIStackView.class] && view.superview) {
            if (self.position == SXBarViewPositionLeft) {
                for (NSLayoutConstraint *constraint in view.superview.constraints) {
                    if ([constraint.firstItem isKindOfClass:[UILayoutGuide class]] && 
                    constraint.firstAttribute == NSLayoutAttributeTrailing) {
                        [view.superview removeConstraint:constraint];
                    }
                }
                [view.superview addConstraint:[NSLayoutConstraint constraintWithItem:view
                                                                           attribute:NSLayoutAttributeLeading
                                                                           relatedBy:NSLayoutRelationEqual
                                                                              toItem:view.superview
                                                                           attribute:NSLayoutAttributeLeading
                                                                          multiplier:1.0
                                                                            constant:0]].self.applied = YES;
            } else if (self.position == SXBarViewPositionRight) {
                for (NSLayoutConstraint *constraint in view.superview.constraints) {
                    if ([constraint.firstItem isKindOfClass:[UILayoutGuide class]] && 
                    constraint.firstAttribute == NSLayoutAttributeLeading) {
                        [view.superview removeConstraint:constraint];
                    }
                }
                [view.superview addConstraint:[NSLayoutConstraint constraintWithItem:view
                                                                           attribute:NSLayoutAttributeTrailing
                                                                           relatedBy:NSLayoutRelationEqual
                                                                              toItem:view.superview
                                                                           attribute:NSLayoutAttributeTrailing
                                                                          multiplier:1.0
                                                                            constant:0]].self.applied = YES;
            }
            break; }}}@end

@implementation UINavigationItem (SXFixSpace)+ (void)load {
    [self swizzleInstanceMethodWithOriginSel:@selector(setLeftBarButtonItem:)
                                 swizzledSel:@selector(sx_setLeftBarButtonItem:)];
    [self swizzleInstanceMethodWithOriginSel:@selector(setRightBarButtonItem:)
                                 swizzledSel:@selector(sx_setRightBarButtonItem:)]; } - (void)sx_setLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem{
    if (leftBarButtonItem.customView) {
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 11) {
            UIView *customView = leftBarButtonItem.customView;
            BarView *barView = [[BarView alloc]initWithFrame:customView.bounds];
            [barView addSubview:customView];
            customView.center = barView.center;
            [barView setPosition:SXBarViewPositionLeft];
            [self setLeftBarButtonItems:nil];
            [self sx_setLeftBarButtonItem:[[UIBarButtonItem alloc]initWithCustomView:barView]];
        }else{[self sx_setLeftBarButtonItem:nil];
            [self setLeftBarButtonItems:@[[UIBarButtonItem fixedSpaceWithWidth:-20], leftBarButtonItem]]; }}else{[self setLeftBarButtonItems:nil];
        [self sx_setLeftBarButtonItem:leftBarButtonItem];
    }
}

-(void)sx_setRightBarButtonItem:(UIBarButtonItem *)rightBarButtonItem{
    if (rightBarButtonItem.customView) {
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 11) {
            UIView *customView = rightBarButtonItem.customView;
            BarView *barView = [[BarView alloc]initWithFrame:customView.bounds];
            [barView addSubview:customView];
            customView.center = barView.center;
            [barView setPosition:SXBarViewPositionRight];
            [self setRightBarButtonItems:nil];
            [self sx_setRightBarButtonItem:[[UIBarButtonItem alloc]initWithCustomView:barView]];
        } else{[self sx_setRightBarButtonItem:nil];
            [self setRightBarButtonItems:@[[UIBarButtonItem fixedSpaceWithWidth:-20], rightBarButtonItem]]; }}else{[self setRightBarButtonItems:nil];
        [selfsx_setRightBarButtonItem:rightBarButtonItem]; }}@endCopy the code

Before using



After using



I don’t need to change any code on the interface. In iOS 11, the navigation bar button position problem was solved. Of course, you can also make extensions, which are offset by how much, modify the value of the constraint

In addition, in view of the problems that many people find when using this solution, I will also solve them here because this solution is only a temporary compromise, it can not be perfect, so you will find these problems when using it

The solution is actually very simple, just set leftItem method in viewWillAppear, so as to ensure that the constraint will not be reset by the system

2. Failed to find the method to reference the file

Solution 1:

This is a bug in Xcode and has nothing to do with the code. If the file path is set to target->build Setting ->other Link Flags, you can manually add the file path

Solution 2:

Find.m files that xcode does not recognize



Check target membership on the right

This appears to be a bug in xcode9

It has nothing to do with the project

3. The judgment on the validity of the deletion constraints in the demo is only my judgment on my personal project. Each developer’s project may have different influences due to various factors, so you can make your own judgment on the constraints to be deleted according to the project, or it is ok not to delete constraints

The demo address github.com/spicyShrimp…

In addition, if you have better solutions or solutions, welcome to discuss together