iOS11 导航栏按钮位置问题的解决

iOS11 导航栏按钮位置问题的解决

虽然现在iOS11正式版还没有出来,但是作为开发人员,相信很多开发者都在使用xcode beta进行着调试,防止iOS 11出来的时候,对现有的应用的不兼容造成问题无法及时解决,我也是这么做的. 
不得不说iOS 11的改动相当的大,表格中的estimatedRowHeight,导航栏大标题,安全区域…. 
当然这里不是我要说的东西. 
作为一个开发者,第一时间适配新的SDK或者开发框架,这是最起码的要求,当然如果在beta版的时候就能做好兼容,这是再好不过的了.

废话不多说了. 
作为一个iOS开发者,大家都知道开发中经常遇到问题的不是某一个界面,不是某一个功能,相反的,而是我们经常不在乎的导航栏.原因很简单.

  • 系统自动管理
  • 高度封装,修改难度大
  • 图层多,很多自己用不到的图层穿插其中,造成开发效果的影响

大致的举个例子,我们设置NavigationBar的背景色,我们设置UIBarButtonItem的位置偏移问题等等, 
当然,这些都是大家早就研究过的,解决方案也有很多

  • 无非是完全自定义导航栏,不适用系统的NavigationBar
  • 直接或者间接的修改系统的NavigationBar 或者 UIBarButtonItem等

这里我就说说导航栏按钮的位置问题

在iOS7之后,我们在设置UINavigationItem的leftBarButtonItem,rightBarButtonItem的时候都会造成位置的偏移,虽然不大,但是跟UI的设计或者国人的习惯有点区别.当然也有很好的解决方案,多添加一个消极的宽度为负值的UIBarButtonItem

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

    UIBarButtonItem *fixedSpace = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
    fixedSpace.width = width;
    return fixedSpace;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在我们添加导航栏按钮的时候 
我们使用就可以满足将按钮位置调整的需求

[self.navigationItem setRightBarButtonItems:@[[UIBarButtonItem fixedSpaceWithWidth:-20], rightBarButtonItem]];
  • 1

但是这些在iOS 11中都无效了!!!! 
但是这些在iOS 11中都无效了!!!! 
但是这些在iOS 11中都无效了!!!!

重要的事情说3遍.

iOS 11改动相当大的就是导航栏的部分,在原来的已经复杂的不要的图层中又新增了新的图层!

这里写图片描述

这里写图片描述

是的你没有看做,_UINavigationBarContentView和_UIButtonBarStackView和_UITAMICAdaptorView 
而我们之前的leftBarButtonItem什么的现在都在UIButtonBarStackView中了.更无语的是这些

<_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>>
  • 1
  • 2
  • 3
  • 4
  • 5

我们可以看到一个_UIButtonBarStackView占掉了12个像素的左边约束,_UITAMICAdaptorView又占据了8个像素的左边约束,所以说我们很无语的就被占据了20px,更可气的是,都是私有对象,不容易修改!

于是还是老套路,我们设置负值来调整约束,结果却失败了,无效… 
迫于无奈,我们只能想新的办法,

  • 放弃UIBarButtonItem,放弃UINavigationBar,使用自定义视图代替
  • 在UINavigationBar中使用添加视图的方式,固定位置固定大小添加按钮
  • UIBarButtonItem.customView 设置偏移(比如按钮设置图片偏移 视图设置tranform等)
  • 修改UIBarButtonItem图层结构(删除图层,或者修改约束)

当然,完全的使用自定义视图代替原生的UINavigationBar和UIBarButtonItem,这里我也不需要说明了.就是自定义视图蛮,肯定都能解决

使用addSubview:添加,之后remove什么的虽然可以,但是这个也不是我想要的

至于这是偏移,结果也嗯惨淡,无效.我尝试了设置旋转都可以,但是设置位置左移就失效了.很无语

为什么非要大动代码呢?在iOS 11之前,我们的项目绝大部分都是使用UINavigationBar和UIBarButtonItem,也就是系统的来管理,现在如果因为一个偏移问题,我们就要修改过多代码,岂不是很麻烦? 
能否有较小代价实现? 
答案是有的,

我们可能会做这样的一个分类

@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];
        [self sx_setRightBarButtonItem:rightBarButtonItem];
    }
}
@end
  • 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

在我们iOS11之前,我们使用这样的一个分类来扩展, 
使得我们在vc中就能这样使用

self.navigationItem.leftBarButtonItem = [UIBarButtonItem itemWithTarget:self action:@selector(sx_pressBack:) image:[UIImage imageNamed:@"nav_back"]];
  • 1

就能调整好我们的按钮位置

那么能不能不懂这些代码也满足iOS 11呢?

那么只有在加一点点东西了,在分类中

-(void)sx_setLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem{
    if (leftBarButtonItem.customView) {
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 11) {
            //如果调整,在这里实现,这样就能达到不影响代码的效果
        }else {
            [self sx_setLeftBarButtonItem:nil];
            [self setLeftBarButtonItems:@[[UIBarButtonItem fixedSpaceWithWidth:-20], leftBarButtonItem]];
        }
    }else {
        [self setLeftBarButtonItems:nil];
        [self sx_setLeftBarButtonItem:leftBarButtonItem];
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在什么地方写我们都能想明白,接下来是怎么写的问题了 
我的思路是既然他是一个customView,那么我能否扩展这个customView呢? 
我们原来将一个按钮直接用作customView,比如这样

[[UIBarButtonItem alloc] initWithCustomView:button];
  • 1

但是现在我想的是按钮添加在一个我们定义的view中,view作为customView 
这样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];//说明这个view需要调整的是左边
            [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];
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

那么这个view我们也能干些事情了

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
  • 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

代码其实不复杂,就是遍历view的父视图,当其实UIStackView的时候,我们修改其左右约束,但是仅仅修改的话会造成约束冲突,所以我们还需要提前移除约束冲突的左右约束(如果担心影响问题,不移除没有关系,仅仅是编译器会报约束冲突log,代码洁癖的话会感觉不舒服)

于是在原来的分类中稍作扩展,我们的新的分类就完成了

#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];
        [self sx_setRightBarButtonItem:rightBarButtonItem];
    }
}

@end
  • 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
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111

使用前 
这里写图片描述 
这里写图片描述

使用后 
这里写图片描述 
这里写图片描述

我不需要需改任何界面上的代码,在iOS 11下解决了导航栏按钮位置问题 
当然你也能在做扩展,是偏移多少,修改约束的值即可 
上面部分代码省略,完整demo请移步下载

另外,鉴于很多人在使用时发现的问题,我这里也统一解决一下 
因为此方案只是临时的折中方案,不可能完美, 
所以在使用的时候会发现这些问题

1.某一个界面在push一个新界面之后再返回回来之后位置就还原了 
解决方案其实很简单,只要将设置leftItem的方法写在viewWillAppear中即可,这样即可保证约束不会被系统重置

2.引用文件却仍找不到方法,运行崩溃 
解决方案1: 
这个是个案,一般是不会发生这个情况的,出现这些情况的项目,只要在target->build setting->other link flags中手动添加文件路径即可,这个是xcode的一个bug,跟代码无关 
解决方案2: 
找到xcode无法识别的.m文件 
这里写图片描述 
勾选右侧的target membership 
目前看来这个是xcode9的一个bug 
跟项目本身无关

3.删除约束的判断是否有效 
demo中的删除约束的判断仅仅是我个人项目中的判断,每个开发者的项目因为各种因素可能会有不同的影响,大家可以根据项目自行判断需要删除的约束条件,亦或者是不删除约束也是可以的

demo地址 
https://github.com/spicyShrimp/iOS-11-UINavigationItem-SXFixSpace

此外,如果大家有更好的方案或者解决思路,欢迎一起探讨

iOS11 导航栏按钮位置问题的解决

虽然现在iOS11正式版还没有出来,但是作为开发人员,相信很多开发者都在使用xcode beta进行着调试,防止iOS 11出来的时候,对现有的应用的不兼容造成问题无法及时解决,我也是这么做的. 
不得不说iOS 11的改动相当的大,表格中的estimatedRowHeight,导航栏大标题,安全区域…. 
当然这里不是我要说的东西. 
作为一个开发者,第一时间适配新的SDK或者开发框架,这是最起码的要求,当然如果在beta版的时候就能做好兼容,这是再好不过的了.

废话不多说了. 
作为一个iOS开发者,大家都知道开发中经常遇到问题的不是某一个界面,不是某一个功能,相反的,而是我们经常不在乎的导航栏.原因很简单.

  • 系统自动管理
  • 高度封装,修改难度大
  • 图层多,很多自己用不到的图层穿插其中,造成开发效果的影响

大致的举个例子,我们设置NavigationBar的背景色,我们设置UIBarButtonItem的位置偏移问题等等, 
当然,这些都是大家早就研究过的,解决方案也有很多

  • 无非是完全自定义导航栏,不适用系统的NavigationBar
  • 直接或者间接的修改系统的NavigationBar 或者 UIBarButtonItem等

这里我就说说导航栏按钮的位置问题

在iOS7之后,我们在设置UINavigationItem的leftBarButtonItem,rightBarButtonItem的时候都会造成位置的偏移,虽然不大,但是跟UI的设计或者国人的习惯有点区别.当然也有很好的解决方案,多添加一个消极的宽度为负值的UIBarButtonItem

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

    UIBarButtonItem *fixedSpace = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
    fixedSpace.width = width;
    return fixedSpace;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在我们添加导航栏按钮的时候 
我们使用就可以满足将按钮位置调整的需求

[self.navigationItem setRightBarButtonItems:@[[UIBarButtonItem fixedSpaceWithWidth:-20], rightBarButtonItem]];
  • 1

但是这些在iOS 11中都无效了!!!! 
但是这些在iOS 11中都无效了!!!! 
但是这些在iOS 11中都无效了!!!!

重要的事情说3遍.

iOS 11改动相当大的就是导航栏的部分,在原来的已经复杂的不要的图层中又新增了新的图层!

这里写图片描述

这里写图片描述

是的你没有看做,_UINavigationBarContentView和_UIButtonBarStackView和_UITAMICAdaptorView 
而我们之前的leftBarButtonItem什么的现在都在UIButtonBarStackView中了.更无语的是这些

<_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>>
  • 1
  • 2
  • 3
  • 4
  • 5

我们可以看到一个_UIButtonBarStackView占掉了12个像素的左边约束,_UITAMICAdaptorView又占据了8个像素的左边约束,所以说我们很无语的就被占据了20px,更可气的是,都是私有对象,不容易修改!

于是还是老套路,我们设置负值来调整约束,结果却失败了,无效… 
迫于无奈,我们只能想新的办法,

  • 放弃UIBarButtonItem,放弃UINavigationBar,使用自定义视图代替
  • 在UINavigationBar中使用添加视图的方式,固定位置固定大小添加按钮
  • UIBarButtonItem.customView 设置偏移(比如按钮设置图片偏移 视图设置tranform等)
  • 修改UIBarButtonItem图层结构(删除图层,或者修改约束)

当然,完全的使用自定义视图代替原生的UINavigationBar和UIBarButtonItem,这里我也不需要说明了.就是自定义视图蛮,肯定都能解决

使用addSubview:添加,之后remove什么的虽然可以,但是这个也不是我想要的

至于这是偏移,结果也嗯惨淡,无效.我尝试了设置旋转都可以,但是设置位置左移就失效了.很无语

为什么非要大动代码呢?在iOS 11之前,我们的项目绝大部分都是使用UINavigationBar和UIBarButtonItem,也就是系统的来管理,现在如果因为一个偏移问题,我们就要修改过多代码,岂不是很麻烦? 
能否有较小代价实现? 
答案是有的,

我们可能会做这样的一个分类

@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];
        [self sx_setRightBarButtonItem:rightBarButtonItem];
    }
}
@end
  • 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

在我们iOS11之前,我们使用这样的一个分类来扩展, 
使得我们在vc中就能这样使用

self.navigationItem.leftBarButtonItem = [UIBarButtonItem itemWithTarget:self action:@selector(sx_pressBack:) image:[UIImage imageNamed:@"nav_back"]];
  • 1

就能调整好我们的按钮位置

那么能不能不懂这些代码也满足iOS 11呢?

那么只有在加一点点东西了,在分类中

-(void)sx_setLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem{
    if (leftBarButtonItem.customView) {
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 11) {
            //如果调整,在这里实现,这样就能达到不影响代码的效果
        }else {
            [self sx_setLeftBarButtonItem:nil];
            [self setLeftBarButtonItems:@[[UIBarButtonItem fixedSpaceWithWidth:-20], leftBarButtonItem]];
        }
    }else {
        [self setLeftBarButtonItems:nil];
        [self sx_setLeftBarButtonItem:leftBarButtonItem];
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在什么地方写我们都能想明白,接下来是怎么写的问题了 
我的思路是既然他是一个customView,那么我能否扩展这个customView呢? 
我们原来将一个按钮直接用作customView,比如这样

[[UIBarButtonItem alloc] initWithCustomView:button];
  • 1

但是现在我想的是按钮添加在一个我们定义的view中,view作为customView 
这样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];//说明这个view需要调整的是左边
            [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];
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

那么这个view我们也能干些事情了

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
  • 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

代码其实不复杂,就是遍历view的父视图,当其实UIStackView的时候,我们修改其左右约束,但是仅仅修改的话会造成约束冲突,所以我们还需要提前移除约束冲突的左右约束(如果担心影响问题,不移除没有关系,仅仅是编译器会报约束冲突log,代码洁癖的话会感觉不舒服)

于是在原来的分类中稍作扩展,我们的新的分类就完成了

#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];
        [self sx_setRightBarButtonItem:rightBarButtonItem];
    }
}

@end
  • 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
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111

使用前 
这里写图片描述 
这里写图片描述

使用后 
这里写图片描述 
这里写图片描述

我不需要需改任何界面上的代码,在iOS 11下解决了导航栏按钮位置问题 
当然你也能在做扩展,是偏移多少,修改约束的值即可 
上面部分代码省略,完整demo请移步下载

另外,鉴于很多人在使用时发现的问题,我这里也统一解决一下 
因为此方案只是临时的折中方案,不可能完美, 
所以在使用的时候会发现这些问题

1.某一个界面在push一个新界面之后再返回回来之后位置就还原了 
解决方案其实很简单,只要将设置leftItem的方法写在viewWillAppear中即可,这样即可保证约束不会被系统重置

2.引用文件却仍找不到方法,运行崩溃 
解决方案1: 
这个是个案,一般是不会发生这个情况的,出现这些情况的项目,只要在target->build setting->other link flags中手动添加文件路径即可,这个是xcode的一个bug,跟代码无关 
解决方案2: 
找到xcode无法识别的.m文件 
这里写图片描述 
勾选右侧的target membership 
目前看来这个是xcode9的一个bug 
跟项目本身无关

3.删除约束的判断是否有效 
demo中的删除约束的判断仅仅是我个人项目中的判断,每个开发者的项目因为各种因素可能会有不同的影响,大家可以根据项目自行判断需要删除的约束条件,亦或者是不删除约束也是可以的

demo地址 
https://github.com/spicyShrimp/iOS-11-UINavigationItem-SXFixSpace

此外,如果大家有更好的方案或者解决思路,欢迎一起探讨

猜你喜欢

转载自blog.csdn.net/qq_29680975/article/details/78188705