我们可以NSView添加一个扩展,实现一个开始动画的过程。我们可以使用CABasicAnimation来实现,通常我们的做法是这样的。
func startAnimtion(clockwise:Bool = true) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
let from:CGFloat = CGFloat.pi * 2
let end:CGFloat = 0
rotateAnimation.fromValue = clockwise ? from : end
rotateAnimation.toValue = clockwise ? end : from
rotateAnimation.duration = 1
rotateAnimation.isAdditive = true
rotateAnimation.repeatDuration = CFTimeInterval.infinity
layer?.add(rotateAnimation, forKey: "rotateAnimation")
}
在Mac上会有一个问题,循转点不对,并非绕视图的中心点旋转,而是在绕左下角旋转,旋转动画是参考anchorPoint进行的。所以,要修改anchorPoint,在Mac上,layer的anchorPoint默认是(0,0)而不是(0.5,0.5)
func startAnimtion(clockwise:Bool = true) {
layer?.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
let from:CGFloat = CGFloat.pi * 2
let end:CGFloat = 0
rotateAnimation.fromValue = clockwise ? from : end
rotateAnimation.toValue = clockwise ? end : from
rotateAnimation.duration = 1
rotateAnimation.isAdditive = true
rotateAnimation.repeatDuration = CFTimeInterval.infinity
layer?.add(rotateAnimation, forKey: "rotateAnimation")
}
如此一改,会发现视图位置发生了变化,是的,修改anchorPoint会引起frame的变化。这不是我们想要的,我们可以同时修改position来中和frame的变化,也可以简单的记录旧的frame,设置完anchorPoint之后,在设置回原来的值。
func startAnimtion(clockwise:Bool = true) {
wantsLayer = true
// 注意⚠️,修改了anchorPoint会变更frame,无法实现预期在效果。在macOS上视图自带layer的anchorPoint默认为(0,0)
let oldFrame = layer?.frame
layer?.anchorPoint = CGPoint(x: 0.5, y: 0.5)
layer?.frame = oldFrame!
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
let from:CGFloat = CGFloat.pi * 2
let end:CGFloat = 0
rotateAnimation.fromValue = clockwise ? from : end
rotateAnimation.toValue = clockwise ? end : from
rotateAnimation.duration = 1
rotateAnimation.isAdditive = true
rotateAnimation.repeatDuration = CFTimeInterval.infinity
layer?.add(rotateAnimation, forKey: "rotateAnimation")
}
这似乎是没有问题了。但是如果在动画执行过程中,切换到了其他的视图控制器,那么动画就回停止。再返回当前界面时,发现动画已经停止。
也许你会想,动画有个属性,动画结束时是否移除动画,isRemovedOnCompletion,默认是移除动画,若设置为false,则不会移除。
rotateAnimation.isRemovedOnCompletion = false。
是的,这么做,动画切换时的确没有移除。动画还在转,但是动画却不符合预期。从原来但绕中心点旋转,又变成了绕左下角旋转。这真是个头疼的事情。
原因也不难猜,根据视图控制器的生命周期,当切换回视图控制器时,视图出现,经历了viewWillAppear 、 viewWillLayout、viewDidLayout、viewDidAppear
此时的frame和已经发生了变化,视图layer的anchorPoint也回到了默认的状态。所以,我们可以在布局之后的viewDidAppear中,根据需要重新开启动画。实例代码如下。
// 如果在连接中切换到其他控制器,连接动画会结束,但动画并未完成,若设置动画结束时不移除,则返回时,动画出现不可预期的效果。
// 因此,动画按系统默认行为处理,动画结束时移除动画。在视图已经重新布局并出现之后,判断是否需要继续动画,如果需要,则新添加动画即可。如果不需要,则不处理
override func viewDidAppear() {
super.viewDidAppear()
if isProcessing {
// 执行动画
if isConnected {
// 断开连接,异时针
connectImgView.startAnimtion(clockwise: false)
}else {
// 连接,顺时针
connectImgView.startAnimtion()
}
}
}
关于NSNiew添加动画和移除动画的完整代码如下:
extension NSView {
func startAnimtion(clockwise:Bool = true) {
wantsLayer = true
// 注意⚠️,修改了anchorPoint会变更frame,无法实现预期在效果。在macOS上anchorPoint默认为(0,0)
let oldFrame = layer?.frame
layer?.anchorPoint = CGPoint(x: 0.5, y: 0.5)
layer?.frame = oldFrame!
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
let from:CGFloat = CGFloat.pi * 2
let end:CGFloat = 0
rotateAnimation.fromValue = clockwise ? from : end
rotateAnimation.toValue = clockwise ? end : from
rotateAnimation.duration = 1
rotateAnimation.isAdditive = true
rotateAnimation.repeatDuration = CFTimeInterval.infinity
layer?.add(rotateAnimation, forKey: "rotateAnimation")
}
func stopAnimation() {
layer?.removeAnimation(forKey: "rotateAnimation")
}
}
这应该是全网目前关于NSView绕自身中心点旋转最完整的实现了。
但,你以为就完了吗?没有,如果允许用户拉伸界面,则依然会引发frame变化,继而引发layer的anchorPoint发生变化,在用户拉伸过程中,动画又达不到预期效果。。。。该怎么办呢?如果你的界面不允许用户拉伸界面,那么上面的就够了。如果允许用户拉伸界面,那么方案在下面:
虽然说view自带Layer的anchorPoint为(0,0),但是新建Layer的anchorPoint默认为(0.5,0.5),那么我们可以新建layer来实现旋转动画。以下是个例子:
class ConnectImageView: NSImageView {
lazy var contentLayer:CALayer = {
let imgLayer = CALayer()
imgLayer.frame = self.layer!.bounds
let image = NSImage(named: "circle")!
imgLayer.contents = image.layerContents(forContentsScale: self.layer!.contentsScale)
imgLayer.contentsScale = self.layer!.contentsScale
return imgLayer
}()
func startCircleAnimtion(clockwise:Bool = true) {
wantsLayer = true
contentLayer.frame = bounds
layer?.addSublayer(contentLayer)
// print("layer archPoint:\(layer?.anchorPoint),new layer anchorPoint:\(newLayer.anchorPoint)")
// 注意⚠️,修改了anchorPoint会变更frame,无法实现预期在效果。在macOS上anchorPoint默认为(0,0),如果是新建一个Layer,则layer的anchorPoint默认为(0.5,0.5)
// 在拉伸视图时,默认layer的frame以及anchorPoint都发生了变化,造成动画不是预期的样子。
// 所以最终的解决方案是,单独新建一个子Layer,用来处理动画
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
let from:CGFloat = CGFloat.pi * 2
let end:CGFloat = 0
rotateAnimation.fromValue = clockwise ? from : end
rotateAnimation.toValue = clockwise ? end : from
rotateAnimation.duration = 1
rotateAnimation.isAdditive = true
rotateAnimation.repeatDuration = CFTimeInterval.infinity
contentLayer.add(rotateAnimation, forKey: "rotateAnimation")
}
func stopCircleAnimation() {
contentLayer.removeAnimation(forKey: "rotateAnimation")
contentLayer.removeFromSuperlayer()
}
}
如果以上内容对您有所帮助,希望能给作者点个赞,鼓励一下。。。谢谢。
网友评论