相信很多同学都知道在iOS7之后调整导航栏两侧按钮距离左右间距,其实就是在左右barButtonItem的数组中添加一个宽度为负的占位item。

- (void)addLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem
{
UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
space.width = -8;
[self setLeftBarButtonItems:@[space, leftBarButtonItem]];
}

但是在iOS11之后,发现该方法失效了。新的思路和iOS7的完全不一样,我们给UINavigationBarContentView加一条约束。怎么加呢?

1 自定义一个customView,使用initWithCustomView创建UIBarButtonItem。

2 在customView的layoutSubviews方法中找到UINavigationBarContentView,添加customView和UINavigationBarContentView之间的约束。

customView定义如下:

#import "UIView.h"

typedef NS_ENUM(NSInteger, LFBarButtonItemViewType) {
LFBarButtonItemViewTypeLeft,
LFBarButtonItemViewTypeRight,
}; @interface LFBarButtonItemView : UIView @property (nonatomic, assign) LFBarButtonItemViewType type; @end
@implementation LFBarButtonItemView

- (void)layoutSubviews {
[super layoutSubviews];
if (iOSVersion < 11.0) {
return;
}
//Here is a workaround on iOS 11 UINavigationBarItem init with custom view, position issue
UIView *view = self;
while (![view isKindOfClass:[UINavigationBar class]] && [view superview] != nil)
{
view = [view superview];
if ([view isKindOfClass:[UIStackView class]] && [view superview] != nil)
{
if (self.type == LFBarButtonItemViewTypeLeft) {
CGFloat margin = 0.0f;
// 5.5寸plus间距大一点
if ([[TWDeviceManager sharedManager] iPhone55]) {
margin = 4.0f;
}
[view.superview addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:view.superview attribute:NSLayoutAttributeLeading multiplier:1.0 constant:margin]];
break;
} else if (self.type == LFBarButtonItemViewTypeRight) {
CGFloat margin = 0.0f;
// 5.5寸plus间距大一点
if ([[TWDeviceManager sharedManager] iPhone55]) {
margin = -4.0f;
}
[view.superview addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:view.superview attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:margin]];
break;
}
}
}
}

在创建self.navigationItem.rightBarButtonItem的地方:

- (void)initNavigationBar {
// rightItem
UIImage *image = [UIImage imageNamed:@"share-icon-dark"];
LFBarButtonItemView *rightItemCustomView = [[LFBarButtonItemView alloc] initWithFrame:CGRectMake(, , image.size.width, image.size.height)];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didClickOnShareButton)];
[rightItemCustomView addGestureRecognizer:tap];
rightItemCustomView.type = LFBarButtonItemViewTypeRight;
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[imageView sizeToFit];
[rightItemCustomView addSubview:imageView];
UIBarButtonItem *rightItem = [[UIBarButtonItem alloc] initWithCustomView:rightItemCustomView];
self.navigationItem.rightBarButtonItem = rightItem;
}

但是这个方法会有约束冲突问题,所以我们把产生冲突的约束删除。可以用xcode查看视图层次,以方便理解。

- (void)layoutSubviews {
[super layoutSubviews];
if (iOSVersion < 11.0) {
return;
}
//Here is a workaround on iOS 11 UINavigationBarItem init with custom view, position issue
UIView *view = self;
while (![view isKindOfClass:[UINavigationBar class]] && [view superview] != nil)
{
view = [view superview];
if ([view isKindOfClass:[UIStackView class]] && [view superview] != nil)
{
if (self.type == LFBarButtonItemViewTypeLeft) {
CGFloat margin = kAppAdaptHeight();
//删除原来的leading约束
for (NSLayoutConstraint *constraint in view.superview.constraints) {
if ([constraint.firstItem isKindOfClass:[UILayoutGuide class]] &&
constraint.firstAttribute == NSLayoutAttributeTrailing) {
[view.superview removeConstraint:constraint];
}
}
//添加新约束
NSLayoutConstraint *leadingConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:view.superview attribute:NSLayoutAttributeLeading multiplier:1.0 constant:margin];
leadingConstraint.priority = UILayoutPriorityRequired;
[view.superview addConstraint:leadingConstraint]; break;
} else if (self.type == LFBarButtonItemViewTypeRight) {
CGFloat margin = -kAppAdaptHeight(); //删除原来的leading约束
for (NSLayoutConstraint *constraint in view.superview.constraints) {
if ([constraint.firstItem isKindOfClass:[UILayoutGuide class]] &&
constraint.firstAttribute == NSLayoutAttributeTrailing) {
[view.superview removeConstraint:constraint];
}
} NSLayoutConstraint *trailingConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:view.superview attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:margin];
trailingConstraint.priority = UILayoutPriorityRequired;
[view.superview addConstraint:trailingConstraint];
break;
}
}
}
}

现在看起来问题解决了,但是某一个界面在push一个新界面之后再返回回来之后位置就还原了 
解决方案其实很简单,只要将设置leftItem的方法写在viewWillAppear中即可,这样即可保证约束不会被系统重置。但是,这样的方案,是不是觉得不完美!

现在有一个终极解决方案:

UINavigationBarContentView平铺在导航栏中作为iOS11的各个按钮的父视图,该视图的所有的子视图都会有一个layoutMargins被占用,也就是系统调整的占位,我们只要把这个置空就行了.那样的话该视图下的所有的子视图的空间就会变成我们想要的那样,当然为了保险起见,该视图的父视图也就是bar的layoutMargins也置空,这样 整个bar就会跟一个普通视图一样了 左右的占位约束就不存在了

给UINavigationBar写一个分类:

#import "UINavigationBar+iOS11Spacing.h"
#import <objc/runtime.h> #define kSpacerWidth kAppAdaptWidth(15) @implementation UINavigationBar (iOS11Spacing) +(void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzleInstanceMethodWithOriginSel:@selector(layoutSubviews)
swizzledSel:@selector(sx_layoutSubviews)];
});
} -(void)sx_layoutSubviews{
[self sx_layoutSubviews]; if (iOS11_OR_LATER && !kSpacerWidth) {//需要调节
self.layoutMargins = UIEdgeInsetsZero;
CGFloat space = kSpacerWidth;
for (UIView *subview in self.subviews) {
if ([NSStringFromClass(subview.class) containsString:@"ContentView"]) {
subview.layoutMargins = UIEdgeInsetsMake(, space, , space);//可修正iOS11之后的偏移
break;
}
}
}
} + (void)swizzleInstanceMethodWithOriginSel:(SEL)oriSel swizzledSel:(SEL)swiSel {
Method originAddObserverMethod = class_getInstanceMethod(self, oriSel);
Method swizzledAddObserverMethod = class_getInstanceMethod(self, swiSel); [self swizzleMethodWithOriginSel:oriSel oriMethod:originAddObserverMethod swizzledSel:swiSel swizzledMethod:swizzledAddObserverMethod class:self];
} + (void)swizzleMethodWithOriginSel:(SEL)oriSel
oriMethod:(Method)oriMethod
swizzledSel:(SEL)swizzledSel
swizzledMethod:(Method)swizzledMethod
class:(Class)cls {
BOOL didAddMethod = class_addMethod(cls, oriSel, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) {
class_replaceMethod(cls, swizzledSel, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
} else {
method_exchangeImplementations(oriMethod, swizzledMethod);
}
}

这样就有一个好处,在原来代码的基础上,判断iOS11,什么都不做,iOS7-iOS11之间版本使用老方法修改间距。

举一个设置LeftBarButtonItem的例子:

#import "UINavigationItem+iOS7Spacing.h"

#import <objc/runtime.h>

#define xSpacerWidth -8

@implementation UINavigationItem (iOS7Spacing)

- (UIBarButtonItem *)spacer

{

UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];

space.width = xSpacerWidth;

return space;

}

- (void)mk_setLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem

{

if (iOS11_OR_LATER) {

[self mk_setLeftBarButtonItem:leftBarButtonItem];

} else if (iOS7_OR_LATER) {

if (leftBarButtonItem && (leftBarButtonItem.customView !=nil || leftBarButtonItem.image !=nil)) {

[self mk_setLeftBarButtonItem:nil];

[self mk_setLeftBarButtonItems:@[[self spacer], leftBarButtonItem]];

} else {

if (iOS7_OR_LATER) {

[self mk_setLeftBarButtonItems:nil];

}

[self mk_setLeftBarButtonItem:leftBarButtonItem];

}

} else {

[self mk_setLeftBarButtonItem:leftBarButtonItem];

}

}

- (void)mk_setLeftBarButtonItems:(NSArray *)leftBarButtonItems

{

if (iOS7_OR_LATER && leftBarButtonItems && leftBarButtonItems.count > 0 ) {

NSMutableArray *items = [[NSMutableArray alloc] initWithCapacity:leftBarButtonItems.count + 1];

[items addObject:[self spacer]];

[items addObjectsFromArray:leftBarButtonItems];

[self mk_setLeftBarButtonItems:items];

} else {

[self mk_setLeftBarButtonItems:leftBarButtonItems];

}

}

- (void)mk_setRightBarButtonItem:(UIBarButtonItem *)rightBarButtonItem

{

if (iOS11_OR_LATER) {

[self mk_setRightBarButtonItem:rightBarButtonItem];

} else if (iOS7_OR_LATER) {

if (rightBarButtonItem && (rightBarButtonItem.customView !=nil || rightBarButtonItem.image != nil)) {

[self mk_setRightBarButtonItem:nil];

[self mk_setRightBarButtonItems:@[[self spacer], rightBarButtonItem]];

} else {

if (iOS7_OR_LATER) {

[self mk_setRightBarButtonItems:nil];

}

[self mk_setRightBarButtonItem:rightBarButtonItem];

}

} else {

[self mk_setRightBarButtonItem:rightBarButtonItem];

}

}

- (void)mk_setRightBarButtonItems:(NSArray *)rightBarButtonItems

{

if (iOS7_OR_LATER && rightBarButtonItems && rightBarButtonItems.count > 0) {

NSMutableArray *items = [[NSMutableArray alloc] initWithCapacity:rightBarButtonItems.count + 1];

[items addObject:[self spacer]];

[items addObjectsFromArray:rightBarButtonItems];

[self mk_setRightBarButtonItems:items];

} else {

[self mk_setRightBarButtonItems:rightBarButtonItems];

}

}

+ (void)mk_swizzle:(SEL)aSelector

{

SEL bSelector = NSSelectorFromString([NSString stringWithFormat:@"mk_%@", NSStringFromSelector(aSelector)]);

Method m1 = class_getInstanceMethod(self, aSelector);

Method m2 = class_getInstanceMethod(self, bSelector);

method_exchangeImplementations(m1, m2);

}

+ (void)load

{

[self mk_swizzle:@selector(setLeftBarButtonItem:)];

[self mk_swizzle:@selector(setLeftBarButtonItems:)];

[self mk_swizzle:@selector(setRightBarButtonItem:)];

[self mk_swizzle:@selector(setRightBarButtonItems:)];

}

参考文章:iOS11 导航栏按钮位置问题的解决

iOS11UINavigationBar的item左右间距调整的更多相关文章

  1. 如何自定义RecycleView item的间距

    引言 在以前使用ListView和GridView时,设置item之间的间距还是相对比较简单的,因为它们的基本属性里面Android已经定义好了,可以直接设置属性值即可.但Google为了通用性和灵活 ...

  2. css中字间距调整(转)

    css中字间距调整 (2014-01-02 13:51:07) 标签: 汉字 英文 间距 letter-spacing word-spacing 杂谈 分类: 网页制作 汉字间字间距设置:letter ...

  3. WPF文字描边的解决方法(二)——支持文字竖排和字符间距调整

    原文:WPF文字描边的解决方法(二)--支持文字竖排和字符间距调整 自前天格式化文本效果出来后,今天又添加文本竖排和调整字符间距的功能.另外,由于上次仓促,没来得及做有些功能的设计时支持,这次也调整好 ...

  4. swiper2 swiper-slide 之间的间距调整

    1.在css中调整间距(我的这个是一行显示三个) swiper-slide-active 可以对这个类进行操作 这个类是显示在页面上的第一个元素 2.设置js //热门新闻 swiper var ne ...

  5. R 多图间距调整

    在R中多图画到一起的时候,各图间距通常默认的较远. 如下图: 1 par(mfcol=c(2,1)) 2 plot(1:100) 3 plot(1:100) 调整图片间距这时我们要用到par()函数中 ...

  6. UILabel字体间距调整

    思路: 写一个 UILbel的子类:在子类里面重新布置UILbel的字体间距: 如代码 .h #import <UIKit/UIKit.h> @interface AdjustableUI ...

  7. Android ExpandableListView group的item有间距child间隔不变

    <ExpandableListView android:id="@+id/lv" android:layout_width="fill_parent" a ...

  8. [Flex] ButtonBar系列——flex3 ButtonBar各项之间的间距调整

    <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="h ...

  9. zabbix 部分item采集间隔调整

随机推荐

  1. 【微信小程序开发】秒懂,架构及框架

    今天1024程序员节,写文章庆祝!!! 今天的文章是讲微信小程序开发的,按理解把架构与框架说说.有不对之处请大神指点…… 微信小程序与web应用很像,但是原理不同,微信小程序是运行在微信应用内的,不是 ...

  2. 阿里JAVA开发手册零度的思考理解(一)

    转载请注明原创出处,谢谢! 缘由 阿里JAVA开发手册已经发表有很长时间了,值得认真研究思考推广 阿里官方的Java代码规范标准,这份开发手册不仅规范了一些开发细节,也提出了很多工程开发的哲学,值得好 ...

  3. Mac下如何安装JDK

    1.访问Oracle官网 http://www.oracle.com,浏览到首页的底部菜单 ,然后按下图提示操作: 2.点击"JDK DOWNLOAD"按钮: 3.选择" ...

  4. java线程池ThreadPool

    package com.java.concurrent; import java.util.concurrent.ExecutorService; import java.util.concurren ...

  5. ligerUI---ligerGrid中treegrid(表格树)的使用

    写在前面: 表格树是在普通ligerGrid的基础上,做了一点改变,使数据以表格树的形式显示出来,适用于有级别的数据比如菜单(有父菜单,父菜单下面有子菜单).表格树的显示有两种方法,可以根据自己的项目 ...

  6. Windows 配置 allure report 环境

    1:配置Java环境(运行allure 需要) 2:安装powershell 3:安装scoop方法 :运行 powershell 输入 : iex (new-object net.webclient ...

  7. Js的闭包,这篇写的是比较清晰明了的

    一.变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域无非就是两种:全局变量和局部变量. Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量 ...

  8. PHP基础入门(三)【PHP中的数组】

    PHP数组的分类 按照下标的不同,PHP数组分为关联数组与索引数组: 索引数组:下标从0开始,依次增长: 关联数组: 下标为字符串格式,每个下标字符串与数组的值一一关联对应.(有点像对象的键值对) 关 ...

  9. 动态代理:JDK动态代理和CGLIB代理的区别

    代理模式:代理类和被代理类实现共同的接口(或继承),代理类中存有指向被代理类的索引,实际执行时通过调用代理类的方法.实际执行的是被代理类的方法. 而AOP,是通过动态代理实现的. 一.简单来说: JD ...

  10. C#调用DLL文件时参数对应表

    Wtypes.h中的非托管类型  非托管 C语言类型    托管类名               说明HANDLE                  void*               Syste ...