cocoacabasicanimationnsimageview

Rotate NSImageView at its Center to Make it Spin


Swift 4, macOS 10.13

I have read a variety of answers on SO and still can't get an NSImageView to spin at its center instead of one of its corners.

Right now, the image looks like this (video): http://d.pr/v/kwiuwS

Here is my code:

//`loader` is an NSImageView on my storyboard positioned with auto layout

loader.wantsLayer = true

let oldFrame = loader.layer?.frame
loader.layer?.anchorPoint = CGPoint(x: 0.5, y: 0.5)
loader.layer?.position = CGPoint(x: 0.5, y: 0.5)
loader.layer?.frame = oldFrame!

let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(-1 * .pi * 2.0)
rotateAnimation.duration = 2
rotateAnimation.repeatCount = .infinity

loader.layer?.add(rotateAnimation, forKey: nil)

Any ideas what I am still missing?


Solution

  • I just created a simple demo which contains the handy setAnchorPoint extension for all views.

    The main reason you see your rotation from a corner is that your anchor point is somehow reset to 0,0.

    import Cocoa
    
    @NSApplicationMain
    class AppDelegate: NSObject, NSApplicationDelegate {
    
    @IBOutlet weak var window: NSWindow!
    var imageView: NSImageView!
    
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Insert code here to initialize your application
    
        // Create red NSImageView
        imageView = NSImageView(frame: NSRect(x: 100, y: 100, width: 100, height: 100))
        imageView.wantsLayer = true
        imageView.layer?.backgroundColor = NSColor.red.cgColor
    
        window.contentView?.addSubview(imageView)
    }
    
    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }
    
    func applicationDidBecomeActive(_ notification: Notification) {
        // Before animate, reset the anchor point
        imageView.setAnchorPoint(anchorPoint: CGPoint(x: 0.5, y: 0.5))
        // Start animation
        if imageView.layer?.animationKeys()?.count == 0 || imageView.layer?.animationKeys() == nil {
            let rotate = CABasicAnimation(keyPath: "transform.rotation")
            rotate.fromValue = 0
            rotate.toValue = CGFloat(-1 * .pi * 2.0)
            rotate.duration = 2
            rotate.repeatCount = Float.infinity
    
            imageView.layer?.add(rotate, forKey: "rotation")
        }
    }
    }
    
    extension NSView {
        func setAnchorPoint(anchorPoint:CGPoint) {
            if let layer = self.layer {
                var newPoint = NSPoint(x: self.bounds.size.width * anchorPoint.x, y: self.bounds.size.height * anchorPoint.y)
                var oldPoint = NSPoint(x: self.bounds.size.width * layer.anchorPoint.x, y: self.bounds.size.height * layer.anchorPoint.y)
    
            newPoint = newPoint.applying(layer.affineTransform())
            oldPoint = oldPoint.applying(layer.affineTransform())
    
            var position = layer.position
    
            position.x -= oldPoint.x
            position.x += newPoint.x
    
            position.y -= oldPoint.y
            position.y += newPoint.y
    
    
            layer.anchorPoint = anchorPoint
            layer.position = position
        }
    }
    }
    

    enter image description here