This post walks through the Swift implementation of four NSView
animations to help you create smooth interactions on macOS:
NSView
Fade AnimationsNSView
Translation Animations- Smooth
NSView
Scale Animations NSView
Rotation Animations Around The View Center
While implementing NSView
animations on macOS, you may run into situations where the anchorPoint
property on an NSView
is not functioning as expected. This post also addresses two important topics:
a. When Setting the NSView
anchorPoint
Doesn’t Work
b. Setting the NSView
anchorPoint
Without the NSView Moving or Jumping
NSView Fade Animations
A fade animation will make the view disappear by smoothly animating from an alpha value of 1
to an alpha value of 0
. One option to implement fade animations is to use the view.animator()
proxy to configure the animation. To fade out a view, set view.animator().alphaValue
to 0
.
An easy way to manage a fade animation and set an animation duration is to use NSAnimationContext.runAnimationGroup
. NSAnimationContext
provides a block-based API for executing animations with support for a completion handler.
// NSView fade-out animation
NSAnimationContext.runAnimationGroup({ context in
// 1 second animation
context.duration = 1
// The view will animate to alphaValue 0
view.animator().alphaValue = 0
}) {
// Handle completion
}
NSView Translation Animations
A translation animation will make the NSView
move smoothly to a new location. Using the view.animator()
proxy, you can set the frame.origin
of the view to specify the location to animate to.
An easy way to manage a translation animation and set an animation duration is to use NSAnimationContext.runAnimationGroup
. NSAnimationContext
provides a block-based API for executing animations with support for a completion handler.
// NSView move animation
NSAnimationContext.runAnimationGroup({ context in
// 2 second animation
context.duration = 2
// Animate the NSView downward 20 points
var origin = view.frame.origin
origin.y -= 20
// The view will animate to the new origin
view.animator().frame.origin = origin
}) {
// Handle completion
}
Smooth NSView Scale Animations
Zoom animations on an NSView
are more complicated than fade and translation animations. One challenge is smoothly animating the subviews to zoom with the parent view. Another challenge is keeping the zooming view centered during the zoom animation.
One solution is to combine multiple animations to produce the desired effect:
- Set
scaleUnitSquare
to double the size - Create a
CABasicAnimation
and useCATransform3DMakeScale
to animate the zoom - Use
NSAnimationContext.runAnimationGroup
to translate the zooming view, keeping the zooming view centered during the animation
// Set the scale of the view to 2
let doubleSize = NSSize(width: 2, height: 2)
view.scaleUnitSquare(to: doubleSize)
// Set the frame to the scaled frame
view.frame = CGRect(
x: view.frame.origin.x,
y: view.frame.origin.y,
width: 2 * view.frame.width,
height: 2 * view.frame.height
)
// Create the scale animation
let animation = CABasicAnimation()
let duration = 1
animation.duration = duration
animation.fromValue = CATransform3DMakeScale(1.0, 1.0, 1.0)
animation.toValue = CATransform3DMakeScale(2.0, 2.0, 1.0)
// Trigger the scale animation
view.layer?.add(animation, forKey: "transform")
// Add a simultaneous translation animation to keep the
// view center static during the zoom animation
NSAnimationContext.runAnimationGroup({ context in
// Match the configuration of the scale animation
context.duration = duration
context.timingFunction = CAMediaTimingFunction(
name: CAMediaTimingFunctionName.linear)
// Translate the frame
origin.x -= view.frame.origin.width / 2
origin.y -= view.frame.origin.height / 2
// Trigger the animation
view.animator().frame.origin = origin
})
When Setting the NSView anchorPoint Doesn’t Work
One caveat of the view.anchorPoint
property relates to when anchorPoint
is set. If setting the anchorPoint
is not working, see if the anchorPoint is being during viewDidLoad()
, viewWillAppear(_:)
, or viewDidAppear(_:)
. If the anchorPoint
is being set during those view functions, try setting the anchorPoint
after those functions are called or right before the animation occurs.
Setting the NSView anchorPoint Without the NSView Moving or Jumping
Changing the anchorPoint
of an NSView
may cause the NSView
to jump or move to an unexpected position on screen. The CALayer
position
, if the NSView
is layer backed, is relative to the anchorPoint
. Therefore, changing the anchorPoint
requires a change in the layer position
to keep the view in the same place.
To prevent an NSView
from moving or jumping when modifying the anchorPoint
, update the position of the layer to point inside the view’s bounds where the anchorPoint
is set.
// Prevent the anchor point from modifying views location on screen. This
// example is for moving the anchorPoint to the view’s center
let newAnchorPoint = CGPoint(x: 0.5, y: 0.5)
view?.layer.anchorPoint = newAnchorPoint
let position = view.layer!.position
position.x += view.bounds.maxX * newAnchorPoint.x
position.y += view.bounds.maxY * newAnchorPoint.y
view.layer?.position = position
NSView Rotation Animations Around The View Center
To rotate an NSView
about the center, first update the anchorPoint
to specify the view’s center by setting the anchorPoint
to CGPoint(x: 0.5, y: 0.5)
. Next to prevent the view from moving or jumping when the anchorPoint
is set, update the view’s layer.position
property.
Finally, use a CABasicAnimation
animating the key path transform.rotation.z
to trigger a smooth NSView
rotate animation.
// Prepare the anchor point to rotate around the center
let newAnchorPoint = CGPoint(x: 0.5, y: 0.5)
view.layer?.anchorPoint = newAnchorPoint
// Prevent the anchor point from modifying views location on screen
let position = view.layer!.position
position.x += view.bounds.maxX * newAnchorPoint.x
position.y += view.bounds.maxY * newAnchorPoint.y
view.layer?.position = position
// Configure the animation
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotateAnimation.byValue = 2 * CGFloat.pi
rotateAnimation.duration = 2;
// Trigger the animation
CATransaction.begin()
view.layer?.add(rotateAnimation, forKey: "rotate")
CATransaction.setCompletionBlock {
// Handle animation completion
}
CATransaction.commit()
Animating NSView on macOS in Swift
That’s it! Following the examples in this post allows you to smoothly animate NSView
scaling, rotation, fading, and translation on macOS in Swift.