I am currently trying to implement some custom NSViews that will be animate by certain events in a macOS app. The controls are along the lines of slider you would find in an AudioUnit Plugin.
The question of animation has been answered in this question.
What I am uncertain of is how to alter a view's CALayer position and anchor point for the rotation and updating its frame for mouse events.
As a basic example, I wish to draw a square by creating an NSView and setting it's background colour. I then want to animate the rotation of the view, a la the previous link, on a mouseDown event.
With following setup, the view is moved to the centre of window, its anchor point is altered so that the view rotates around it and not around the origin of the window.contentView!
. However, moving the view.layer.position
and setting the view.layer!.anchorPoint = CGPoint(x: 0.5,y: 0.5)
does not also move the frame that will detect events.
@IBOutlet weak var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification)
{
let view = customView(frame: NSRect(x: 0, y: 0, width: 50, height: 50))
window.contentView!.wantsLayer = true
view.wantsLayer = true
view.layer?.backgroundColor = NSColor.blue.cgColor
window.contentView?.addSubview(view)
let containerFrame = window.contentView!.frame
let center = CGPoint(x: containerFrame.midX, y: containerFrame.midY)
view.layer?.position = center
view.layer?.anchorPoint = CGPoint(x: 0.5,y: 0.5)
}
The customView in this case is simply an NSView with the rotation animation from the previous link executed on mouseDown
override func mouseDown(with event: NSEvent)
{
let timeToRotate = 1
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = angle
rotateAnimation.duration = timeToRotate
rotateAnimation.repeatCount = .infinity
self.layer?.add(rotateAnimation, forKey: nil)
}
Moving the frame by setting view.frame = view.layer.frame
results in the view rotating around one of its corners. So, instead I have altered the view.setFrameOrigin()
@IBOutlet weak var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification)
{
let view = customView(frame: NSRect(x: 0, y: 0, width: 50, height: 50))
window.contentView!.wantsLayer = true
view.wantsLayer = true
view.layer?.backgroundColor = NSColor.blue.cgColor
window.contentView?.addSubview(view)
let containerFrame = window.contentView!.frame
let center = CGPoint(x: containerFrame.midX, y: containerFrame.midY)
let offsetFrameOrigin = CGPoint(x: center.x - view.bounds.width/2,
y: center.y - view.bounds.height/2)
view.setFrameOrigin(offsetFrameOrigin)
view.layer?.position = center
view.layer?.anchorPoint = CGPoint(x: 0.5,y: 0.5)
}
Setting the view.frame origin to an offset of the centre achieves what I want, but I cannot help but feel this is a little 'hacky' and that I may be approaching this the wrong way. Especially since any further change to view.layer
or view.frame
will result in either the animation being incorrect or events being detected outside what is drawn.
How can I alter NSView.layer so that it rotates around its centre at the same time as setting the NSView.frame so that mouse events are detected in the correct area?
Also, is altering NSView.layer.anchorPoint
a correct way to set it up for rotation around its centre?