iOS 11 中 UIBarButtonItem 的变化

随着 iOS 11 和 Xcode 9 的发布,导航栏UINavigationBar 里面的控件 UIBarButtonItem 有一些变化,下面我们一起看看有哪些变化。

变化

  • UIBarButtonItems 的custom view的点击区域更小了
  • 返回按钮的点击区域也更小了
  • 如果上一个屏幕的 title 有变化,则返回按钮的 label 不再自动更新(iOS 10 前会自动更新)
  • UIBarButtonSystemItemFixedSpace 类型的 UIBarButtonItem 现在至少有 8 个点的最小宽度,负数的 width 不再生效
  • 重写 alignmentRectInsets 可能会导致 custom views 超出点击区域

下面会详细的讲解上面几点变化。

点击区域(Tap Areas)

我们假设有一个 iOS 10 程序,其中包含两个有 custom view 的 UIBarButtonItem。然后我们使用 Debug View Hierarchy 去检查 custom 的真实大小。

1.png

他们的点击区域实际上会更大,和下面的红色方块区域大小一致。

2.png

然而在 iOS 11 上,显示的效果和上面的效果就不同了。点击区域大小和 custom view 的大小一致。

3.png

iOS 11 上,这种点击区域的大小的确会使得用户很难点击。为了扩大点击区域,custom view 的需要扩大边距。我们可以创建一个拥有最小的size 约束的 Wrapper view。

class WrapperView: UIView {
    let minimumSize: CGSize = CGSize(width: 44.0, height: 44.0)
    let underlyingView: UIView
    init(underlyingView: UIView) {
        self.underlyingView = underlyingView
        super.init(frame: underlyingView.bounds)

        underlyingView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(underlyingView)

        NSLayoutConstraint.activate([
            underlyingView.leadingAnchor.constraint(equalTo: leadingAnchor),
            underlyingView.trailingAnchor.constraint(equalTo: trailingAnchor),
            underlyingView.topAnchor.constraint(equalTo: topAnchor),
            underlyingView.bottomAnchor.constraint(equalTo: bottomAnchor),
            heightAnchor.constraint(greaterThanOrEqualToConstant: minimumSize.height),
            widthAnchor.constraint(greaterThanOrEqualToConstant: minimumSize.width)
        ])
    }
    // ...
}
4.png

返回按钮(Back Button)

点击区域(Tap Area)

当 view controller 的 title 很长的时候,iOS 11 上的返回按钮点击区域会变得更小。

在 iOS 10 上的返回按钮有一个比较大的最小宽度,利于用户点击。

5.png

然而在 iOS 11 上,返回按钮的点击区域变得很小。更糟糕的是 view controller 的 title,有时看起来像是返回按钮的 label。

6.png

动态更新(Label)

iOS 11 上面另外一个更新是返回按钮的 label。

举个例子,假设有一个能够自动更新 title 的 First(4) 页面 (UIViewController),

7.png

界面从 First(4) 页面 push 到 Second 页面,

8.png

在 iOS 10 上,当 First(4)的 title 从 First(4) 变化到 First(6) 之后,返回按钮也变成了 First(6),

9.png

而在 iOS 11 上,返回按钮的 label 并没有自动更新,而是保持不变。

10.png

自动布局(Auto Layout)

UINavigationBar 现在能够使用自动布局(Auto Layout)来布局它的 subviews(包括 custom views)。在今年的 WWDC上中的 Updating Your Apps for iOS 11 里面有提到。

在 iOS 11 中,我们能够从 Debug View Hierarchy 检查 UINavigationBar 看到,UINavigationBar 内部的 buttons 都被放置在一个 stack view 中。

11.png

当我们正确的实现了 custom view 的 sizeThatFitsintrinsicContentSizeUINavigationBar也会如以前那样工作正常。

但是,需要注意的是,当我们把custom view 的 translatesAutoresizingMaskIntoConstraints 设成 false,在 iOS 10(如果你的 App 还支持 iOS 10的话)上,可能出现一些莫名其妙的问题。比如可能导致custom view 的 top-left 方向布局错位。这种情况下,一个简单的保护代码如下:

let customView = createCustomView()
if #available(iOS 11, *) {
    customView.translatesAutoresizingMaskIntoConstraints = false
}
navigationItem.rightBarButtonItem = UIBarButtonIte(customView: customView)

自定义对齐(Custom Alignment)

很多时候,我们想能够自由的控制 custom UIBarButtonItem 的布局(对齐),特别是和屏幕两边的边距大小。在 iOS 10(及以前)来说,我们经常用到两种小技巧:

  • 使用fixed space item,即UIBarButtonItem(barButtonSystemItem: .fixedSpace …),并且赋值一个负数,比如-15
  • 重写 custom view 的 alignmentRectInsets

但是在 iOS 11 上,上面两个方法都有点变化,第一个办法已经失效,而第二个办法也有点问题。

Fixed Space Items

当我们使用 custom view 的时候, 默认的边距是16个点,

12.png

假设我们想将边距减少到 8 个点,那么在 iOS 10 上面,我们需要设置fixed space UIBarButtonItem 的宽度为 -8 就可以达到我们想要的效果。

13.png
let spacer = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
spacer.width = -8
let barButtonsItems = [
    spacer,
    UIBarButtonItem(customView: createCustomButton()),
    UIBarButtonItem(customView: createCustomButton())
]
navigationItem.rightBarButtonItems = barButtonItems
14.png

然而在 iOS 11 上,修改fixed space UIBarButtonItem 的宽度为 -8 会不生效。

Alignment Rect Insets

而重写 custom view 的 alignmentRectInsets,在 iOS 10 上面也能够修改布局边距

class CustomView: UIView {
    var alignmentRectInsetsOverride: UIEdgeInsets?
    override var alignmentRectInsets: UIEdgeInsets {
        return alignmentRectInsetsOverride ?? super.alignmentRectInsets
    }
    // ...
}

设置 alignmentRectInsets 的值为 UIEdgeInsets(top: 0, left: -8, bottom: 0, right: 8),也能够和前面的修改起到相同的作用

15.png

上面的代码在 iOS 11 上面依然能够起到作用,但是有一个小的问题是,item 里面有一小部分超出了 stack view 的范围。超出的区域不能点击,也就意味着点击区域变小了。

16.png

Workaround

根据前面fixed space的例子,我们发现相对于未使用 custom view 的情况,边距有所变小。

17.png

当我们未使用 custom view 的时候,边距到屏幕边缘是 8 个点

18.png

当我们使用了 custom view 的时候,边距到屏幕是 16 个点

19.png

根据上面的发现,我们的目标是:让 custom view 的边距在 iOS 11 上面依然是 8 个点。添加一个 fix space item 将会使得内部的 stack view 减少边距,那么修改 alignmentRectInsets 的值去移动 custom view 的 frame,可以使内部的 stack view 对齐。

func alignedBarButtonItems() -> [UIBarButtonItem] {
    let spacer = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
    spacer.width = 8
    let barButtonsItems = [
        spacer,
        UIBarButtonItem(customView: createCustomButton(offset: spacer.width)),
        UIBarButtonItem(customView: createCustomButton(offset: spacer.width)),
        ]
    return barButtonsItems
}

func createCustomButton(offset: CGFloat = 0) -> UIButton {
    let button = CustomButton(frame: CGRect(x:0, y: 0, width: 24, height: 24))
    button.alignmentRectInsetsOverride = UIEdgeInsets(top: 0, left: -offset, bottom: 0, right: offset)
    button.translatesAutoresizingMaskIntoConstraints = false
    return button
}
20.png

目前来说,这个 workaround 只能将边距设置为 8,如果我们将这个值设为一个更小的值,那么custom view 依然会超出 stack view。

参考

  • UIBarButtonItem & iOS 11

你可能感兴趣的:(iOS 11 中 UIBarButtonItem 的变化)