通过上一篇文章,我们知道了图层阴影的形状是由图层 “内容” 计算得出的,但实时计算阴影非常消耗资源,尤其是图层有多个子图层,每个子图层还有一个有透明效果的寄宿图的时候。所以如果你事先知道你的阴影形状是什么,可以通过指定一个 shadowPath 来提高性能(CGPath 对象)。它可以是你想要的任何形状(下图)。
上图代码如下:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let img = UIImage(named:"IMG_0651")
let size = img!.size
let view1 = UIView(frame:CGRect(x:100, y:160, width:size.width, height:size.height))
let view2 = UIView(frame:CGRect(x:100, y:380, width:size.width, height:size.height))
view1.layer.contents = img?.cgImage
view2.layer.contents = img?.cgImage
view1.layer.contentsGravity = .center
view2.layer.contentsGravity = .center
view1.layer.contentsScale = UIScreen.main.scale
view2.layer.contentsScale = UIScreen.main.scale
view1.layer.shadowOpacity = 0.5
view2.layer.shadowOpacity = 0.5
let path1 = CGPath(rect: view1.bounds.insetBy(dx: -5, dy: -5), transform:nil)
view1.layer.shadowPath = path1
let path2 = CGMutablePath()
path2.addEllipse(in: view2.bounds.insetBy(dx: -5, dy: -5))
view2.layer.shadowPath = path2
view.addSubview(view1)
view.addSubview(view2)
}
}
其他复杂的 path 根据业务需求去画吧。
图层蒙板
上面是两个例子。蒙板的概念其实非常容易理解,原理如下:
现在有两张图片分别叫 image 和 mask,生成的图片叫 result。遍历 mask 中的像素,设位置 i 上的像素是 p,伪代码:
for (i, p) in mask:
if (p.alpha != 0)
result[i] = image[i]
else
result[i] = mask[i] // 透明
如果仍不理解,简单说就是,如果蒙板上某个点是透明的,那么最终图片该点就是透明的;否则该点的颜色是原图的颜色。整个过程只参考了蒙板的 alpha,蒙板的颜色我们不考虑。
落到 iOS 代码上,有略微的区别。有两种设置蒙板的方式,第一种是设置 CALayer 的属性 mask,这个属性是 CALayer 类型。
和上篇文章提到的计算阴影的方式一样,mask 图层的实际 “内容” 区域将被用作蒙板。(回顾一下:背景色,寄宿图,递归子图层,等结合)
另一种方式是设置 UIView 的属性 mask,这个属性是 UIView 类型,本质是用了 layer,一样的。蒙板相当于定义了视图的可见区域。
现在再看上面两个例子:第一个例子就是两个 UIImageView,其中一个作为 mask,太容易了。用 layer 的话设置寄宿图、mask 就完事了。
第二个例子乍一看挺炫的效果,实际仔细一想,下面是一个 UIImageView,然后自定义可见区域,可见区域是文字的轮廓,而 UILabel 恰好是绘制文字作为寄宿图,那就直接用 UILabel 作为 mask。代码如下:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let v = UIImageView(frame: CGRect(x: 100, y: 200, width: 200, height: 200))
v.image = UIImage(named: "IMG_0651")
v.contentMode = .scaleAspectFill
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
label.text = "TEST"
label.font = .boldSystemFont(ofSize: 50)
v.mask = label
view.addSubview(v)
}
}
如果想用 layer,可以直接用 label.layer;如果不想用 UILabel,可以用 CATextLayer;如果想用自定义的 path 作为 mask,可以用 CAShapeLayer... 这些专用图层后面还会提到。
(因为 view.mask 和 layer.mask 本质相通的,所以可以设置 layer.mask = view.layer)
前面的文章提到过 裁剪任意几个角是圆角,用上面的思路,只需要一个蒙板 mask 就可以了,用什么作蒙板呢?view 大小不一样,肯定是实时绘制蒙板,一个办法是自定义的 view drawRect 绘制寄宿图,另一个办法就是画一个 CGPath,然后构造一个 CAShapeLayer,这个 path 会在 CAShapeLayer 内算入 “内容” 区域,蒙板就实现了。具体链接:任两个角是圆角的 UIView 的实现。
最近两篇文章的核心其实都在于对图层 “内容” 的理解,这个概念对很多方面都有影响,属于比较重要的渲染机制。