iOS知识碎片八

1、适配iOS11、iOS10导航栏返回按钮

2、CocoaPods 间接依赖静态库问题

适配iOS11、iOS10导航栏返回按钮

关于导航栏返回按钮的定制,很多时候都是通过设置 navigationItem 的 rightBarButtonItem 属性实现的。这种方式可以设置自己的 customView ,灵活度高。但是和原生的返回按钮相比较,设置 customView 的返回按钮会偏右,而且左边大概会有15个像素的空间,点击不会触发回调。

针对返回按钮偏右问题,一般设置 UIButton 的 contentEdgeInsets 就可以解决。不过自定义返回按钮默认距离屏幕左边15像素的问题,处理起来就比较麻烦了。

一般来说,iOS11 以下版本可以通过 UIBarButtonSystemItemFixedSpace 类型的 SystemItem 来填充距离,只要把 width 设置成负数,就可以让自定义的按钮往两边靠:

1
2
3
4
5
6
7
8
9
10
static const CGFloat kTDFNBCButtonMargin = 15.0f;

UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
item.width = -kTDFNBCButtonMargin;
self.navigationItem.rightBarButtonItems = @[item, [[UIBarButtonItem alloc] initWithCustomView:self.tdf_sureButton]];

UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
item.width = -kTDFNBCButtonMargin;

self.navigationItem.leftBarButtonItems = @[item, [[UIBarButtonItem alloc] initWithCustomView:self.tdf_backButton]];

iOS11 以上 FixedSpace 方案就不生效了,因为整个层级都变了,中间多了两层父控件,所以需要手动更改父控件的布局,可以用 hook UINavigationBar 或者继承的方式重写 layoutSubviews

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
self.layoutMargins = UIEdgeInsetsZero;
for (UIView *view in self.subviews) {
if ([NSStringFromClass([view class]) containsString:@"ContentView"]) {
UIEdgeInsets inset = view.layoutMargins;
view.layoutMargins = UIEdgeInsetsMake(inset.top, 0, inset.bottom, 0);
}

for (UIView *subview in view.subviews) {
if([subview isKindOfClass:[UIStackView class]]) {
for(NSLayoutConstraint *layout in subview.constraints) {
if(layout.firstAttribute == NSLayoutAttributeWidth ||
layout.secondAttribute == NSLayoutAttributeWidth) {
layout.constant = 0.f;
}
}
}
}
}

需要注意的时,如果是iOS11,那么就不需要设置 FixedSpace SystemItem 了,否则会干扰第二种方式,出现不必要的动画效果。还有由于iOS11下 customView 多了 UIStackView 父控件,并且尺寸和 customView 一致,这就导致无法使用 customView 的 hitTest 对不在其 frame 内的事件进行拦截,因为父控件就已经不满足条件了了。

这种解决方式是在采用原生导航栏的情况下不得已而为之的,如果需要的导航栏花样比较复杂,还是隐藏系统的,自己实现来的舒服。

CocoaPods Pod 间接依赖静态库问题

CocoaPods 在 CocoaPods 0.36 - Framework and Swift Support 一文中,宣布了支持 use_frameworks!。其中 And there were Swift & Dynamic Frameworks on iOS 一节说到

This is an all or nothing approach per integrated targets, because we can’t ensure to properly build frameworks, whose transitive dependencies are static libraries

当工程中存在间接依赖静态库时,使用 CocoaPods 的 use_frameworks! 会提示以下错误:

1
target has transitive dependencies that include static binaries

结合上诉提示,我们可以手动将静态库包装成 Embed Framework ,将其作为一个 Pod 。也可以向承载静态库的 Pod 中加入空的 .m 文件,触发 CocoaPods 生成 Target,以便让其帮我们将静态库包装成动态库。至于在什么情况下 CocoaPods 不会生成 Target,可以参照 Cocoapods does not create framework target for Google analytics