UIView Layer Mask Animate

蓝咒 提交于 2019-11-30 14:31:02
Hamish

You'll want to use a CABasicAnimation on the path property of your mask layer

This will allow you to animate your mask from a given path, to another given path. Core Animation will automatically interpolate the intermediate steps for you.

Something like this should do the trick:

// define your new path to animate the mask layer to
let path = UIBezierPath(roundedRect: CGRectInset(view.bounds, 100, 100), cornerRadius: 20.0)
path.appendPath(UIBezierPath(rect: view.bounds))

// create new animation
let anim = CABasicAnimation(keyPath: "path")

// from value is the current mask path
anim.fromValue = maskLayer.path

// to value is the new path
anim.toValue = path.CGPath

// duration of your animation
anim.duration = 5.0

// custom timing function to make it look smooth
anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)

// add animation
maskLayer.addAnimation(anim, forKey: nil)

// update the path property on the mask layer, using a CATransaction to prevent an implicit animation
CATransaction.begin()
CATransaction.setDisableActions(true)
maskLayer.path = path.CGPath
CATransaction.commit()

Result:

One thing to note with this is that there appears to be a bug in Core Animation, where it's unable to correctly animate from a path that includes rectangle, to a path that includes a rounded rect with a corner radius. I have filed a bug report with Apple for this.

There is a simple fix for this, which is to use a rounded rect with a corner radius of negligible value on the original path (0.0001 works okay for me). A bit hacky – but you can't notice the difference when using the app.


Bug report response from Apple

Unfortunately, Apple considers this to be "expected behaviour". They said the following:

For the animation to render correctly, the animated paths should have the same number of elements/segments (ideally of the same type). Otherwise, there is no reasonable way to interpolate between the two.

For instance, if a first path has four elements and the second path has six elements, we can easily create the first four points by averaging the first four start and end points with a weight for current time, but it's impossible to come up with a general purpose way to calculate the intermediate positions for the fifth and sixth points. This is why the animation works just fine for corner radius 0.001 (the same number of elements), but fails for plain rectangular path (different number of elements).

I personally disagree, if you specify a rounded rect of corner radius 0 and then animate to a rounded rect with a corner radius > 0 – an incorrect animation is still obtained, even though both paths should have the "same number of elements". I suspect the reason this doesn't work is the internal implementation checks for a radius of 0, and tries to optimise by using a rectangular path with 4 elements instead, which is why it doesn't work.

I will submit another bug report explaining this when I get the time.


A couple of other things...

You're force unwrapping your manualWBMaskView quite a bit. Don't. You need to learn how to properly deal with optionals. Every time you force unwrap something - your app could crash.

You also have a few lines like this:

let maskLayer: CAShapeLayer = CAShapeLayer()

The : CAShapeLayer here is not only unnecessary - but goes against good practise. You should always let Swift infer the type of a value where it can.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!