Updating views automatically with observation tracking in AppKit
Use Swift Observation and automatic tracking to update your views in response to model data updates.
Overview
Swift Observation provides the Observable macro to mark your models for automatic change tracking. When you combine Observable models with AppKit, the system automatically watches for property changes and updates your views. You don’t need to manually invalidate anything — AppKit handles it for you.
AppKit provides methods in several objects where automatic observation tracking happens. In a view subclass, updateConstraints(), layout(), and draw(_:) are examples of methods that automatically track any Observable properties you read, and AppKit updates your views when those properties change.
Update view properties automatically
The viewWillLayout() method automatically tracks Observable properties and updates views when they change. For example, to show a message list with a status label that displays unread message information, start by creating an Observable model with the properties your view needs:
@Observable
class MessageModel {
var showStatus: Bool
var statusText: String
}Then, use these properties in your view controller’s viewWillLayout() method:
override func viewWillLayout() {
super.viewWillLayout()
statusLabel.alphaValue = model.showStatus ? 1.0 : 0.0
statusLabel.stringValue = model.statusText
}When the view first appears, AppKit runs viewWillLayout() and tracks that you read showStatus and statusText. If either property changes later, AppKit automatically runs viewWillLayout() again to update the label.
You can also automatically track changes in a custom view using layout().
Draw views automatically
AppKit automatically tracks any Observable properties you read inside your draw(_:) override. When those properties change, AppKit invalidates and redraws the view.
This automatic tracking also covers any methods that draw(_:) calls. This means that if you override drawing methods in a cell subclass, such as drawKnob(_:) and drawBar(inside:flipped:), AppKit also tracks those overrides.
For example, to draw a custom slider cell that responds to model changes, start by creating an Observable model with the visual properties your cell needs:
@Observable
class SliderAppearance {
var knobColor: NSColor
var trackColor: NSColor
}Then, override the drawing methods in your NSSliderCell subclass and read from the model inside each override:
class CustomSliderCell: NSSliderCell {
var appearance: SliderAppearance
override func drawKnob(_ knobRect: NSRect) {
appearance.knobColor.setFill()
NSBezierPath(ovalIn: knobRect).fill()
}
override func drawBar(inside rect: NSRect, flipped: Bool) {
appearance.trackColor.setFill()
NSBezierPath(roundedRect: rect, xRadius: 2, yRadius: 2).fill()
}
}When drawKnob(_:) and drawBar(inside:flipped:) run, AppKit tracks that they read knobColor and trackColor. If either property changes later, AppKit automatically redraws the slider.