美文网首页MacDeveloper
NSView绕中心点旋转

NSView绕中心点旋转

作者: _我和你一样 | 来源:发表于2020-01-02 16:36 被阅读0次

    我们可以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()
        }
    }
    

    如果以上内容对您有所帮助,希望能给作者点个赞,鼓励一下。。。谢谢。

    相关文章

      网友评论

        本文标题:NSView绕中心点旋转

        本文链接:https://www.haomeiwen.com/subject/essooctx.html