最近又在更新 Best Before.app,算是把同步写好了,但是还是打算顺便更新一下 UI。之前加入了自定义分类的功能,还允许用户给分类设置一个颜色,但是这个颜色却没有什么实际的作用,在这次更新中我打算在 Navigation Bar 上面显示出这个颜色。

old

应该怎么显示呢,一开始我的想法是让分类名称的第一个字符设置为这个颜色。尽管 UINavigationBar 并没有给我们提供一个 API 去做这个事情,我们还是能够很暴力地从其众多的 subviewssubviews 当中,找到这些 UILabel。如果 iOS 还没到 11,这就足够了,但是 iOS 11 中却引入了 Large Title,而即便我们修改了 Large Title 的 attributedString,它也会在我们拖动底下的 scroll view 时打回原形。

很讨厌。

这时我想起了 Sketch 中 Blend Mode,在 Sketch 用 Blend Mode Lighten 能够将颜色附着在图标上,虽然没法简单地定位到刚好只覆盖到第一个字符,但好像也挺拉风的,于是我在 Sketch 里面做了一些修改。

new

那 iOS 上是否有这样的功能呢。

居然有,我们只需要在 UINavigationBar 上添加一个 CALayer,并将其的 compositingFilter 赋值为 lightenBlendMode 就可以了。同时还要 observe Navigation Bar 的 frame 以随着它的高度变动修改这个 layer 的 path

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func createNavigationBarBlending() {
blendLayer = {
let it = CAShapeLayer()
it.compositingFilter = "lightenBlendMode"
navigationBar.layer.addSublayer(it)
let observation = navigationBar.observe(\.frame) { bar, _ in
it.path = UIBezierPath(rect: CGRect(x: 0, y: 0,
width: 50,
height: bar.frame.height)).cgPath
}
disposables.append(observation)
return it
}()
}

最后我们只需要在显示分类列表时,修改 blendLayerfillColor 就行了。值得一提的是 UINavigationBar.frame 恰好只包含到了 Large Title View,并不包括搜索框。

当然如果再花些功夫,也还是能让它只覆盖到第一个字符的,但是也没必要了吧。