先看看最终要实现的效果

一开始看到这种效果我有点懵,这和普通的阴影不太一样,并不是带有颜色的蒙层,绞尽脑汁想了一会,应该是在上面加一个 CAGradientLayer 吧,但是这层 layer 的颜色到底是什么才对呢?翻了翻 CALayer 的文档,发现了这样一个属性 mask: CALayer?,它是这样说的,

A layer whose alpha channel is used as a mask to select between the layer’s background and the result of compositing the layer’s contents with its filtered background. Defaults to nil. When used as a mask the layer’s compositingFilter and backgroundFilters properties are ignored. When setting the mask to a new layer, the new layer must have a nil superlayer, otherwise the behavior is undefined. Nested masks (mask layers with their own masks) are unsupported.

大概的解释就是,这个 mask 的 alpha 通道会作用在当前的 layer 上,你可以把它想像成一个 filter,layer 后面的 backgroud 如果能透过这个 alpha 层,那它就能与当前 layer 在一起显示。经过一些实验,这个 mask 并不会影响它后面内容的显示。那我们就可以用这个 layer 的 mask 属性搞一些事情出来了。

要想实现上图的效果大概需要要做以下几个事情,

创建一个 CAGradientLayer

1
2
3
4
5
6
7
8
9
10
lazy var gradientLayer: CAGradientLayer = {
let v = CAGradientLayer()
// 这里的颜色是什么都无所谓,因为只有 alpha 通道会起作用
v.colors = [UIColor.clear.withAlphaComponent(0).cgColor, UIColor.clear.withAlphaComponent(1).cgColor]
v.locations = [0.0, 1]
v.rasterizationScale = UIScreen.main.scale
v.startPoint = CGPoint(x: 0, y: 1)
v.endPoint = CGPoint(x: 0, y: 0.8)
return v
}()

因为要透过后面内容的显示,tableView 的背景需要是透明的,

1
tableView.backgroundColor = UIColor.clear

考虑到列表是会滚动的,把这个 layer 加到 tableView 的 mask 上的固定位置,那列表一滚动,效果就不对了,所以需要把 tableView 加到一个 background 上面去,然后把 layer.mask 设置成这我们之前创建好的 gradientLayer

1
2
3
4
5
6
lazy var tableViewBackgroundView: UIView = {
let v = UIView()
v.backgroundColor = UIColor.clear
v.layer.mask = self.gradientLayer
return v
}()

由于是 AutoLayout 布局,而 layer 却不能加约束,所以我们监听 backgroundView 的 bounds 手动更新 layer 的 frame(这里用了 RxSwift,其它方式也都 OK)

1
2
3
4
5
tableViewBackgroundView.rx.observe(CGRect.self, "bounds")
.subscribe(onNext: { [weak self] bounds in
guard let bounds = bounds else { return }
self?.gradientLayer.frame = bounds
}).addDisposableTo(bag)

Boom,带有边界渐隐效果的列表就 OK 了,是不是很方便捏