Use SwiftUI with AppKit and UIKit
Incrementally adopt SwiftUI in AppKit or UIKit apps using Observation, hosting views and menus, gesture recognizer representables, and SwiftUI scenes.
TL;DR
- Use
@Observablemodels to let AppKit and UIKit automatically invalidate drawing, layout, constraints, and other UI updates based on property reads. - Embed new SwiftUI components in an existing AppKit hierarchy with
NSHostingView; the session demonstrates replacing slider-based drawing with a SwiftUICanvascolor picker. - Reuse existing AppKit gesture recognizers in SwiftUI with
NSGestureRecognizerRepresentable, and build AppKit main-menu content from SwiftUI usingNSHostingMenu. - Add complete SwiftUI scenes from an
NSApplicationDelegatewithNSHostingSceneRepresentation, includingMenuBarExtraandSettingsscenes.
Incremental SwiftUI adoption strategy
SwiftUI is intended to coexist with AppKit and UIKit, so existing apps can adopt it feature by feature rather than through a rewrite. The session uses a macOS lighting-control app as the running example, but the general approach applies across Apple platforms.
The recommended migration path starts with shared observable models, then moves individual controls, menus, gestures, and whole scenes to SwiftUI where they make sense.
- Start by moving shared mutable UI state into
@Observablemodel types. - Use SwiftUI for new or substantially rewritten UI components, especially when drawing and interaction code would change significantly.
- Keep the existing AppKit or UIKit lifecycle and view hierarchy where it is still working.
- Adopt SwiftUI scenes for new windows, settings, or menu bar extras without converting the whole app.
Observation in AppKit and UIKit
AppKit can automatically track reads from @Observable model properties during supported view and controller update methods. When those properties change, the relevant views update without manually setting needsDisplay.
The example replaces manual invalidation between hue, saturation, and brightness sliders with a shared ColorModel. AppKit tracks property reads inside drawing methods such as NSSliderCell.drawKnob and redraws when accessed values change.
- AppKit observation applies to
NSView.draw(_:), drawing paths called by view drawing such asNSSliderCelldrawing methods,updateConstraints(),layout(),updateLayer(), and relatedNSViewControllermethods. - UIKit has similar Observation tracking across
UIView,UIViewController, and additional UIKit types such as controls and collection view cells. - Back deploy AppKit integration to macOS 15 with
NSObservationTrackingEnabledinInfo.plist. - Back deploy UIKit integration to iOS 18 with
UIObservationTrackingEnabled; tracking is enabled by default in the 2026 releases and later.
Observable model shared by AppKit and SwiftUI
Adding @Observable makes mutable properties participate in Observation so AppKit, UIKit, and SwiftUI can react to changes.
import Observation
@Observable
@MainActor
final class ColorModel {
var hue: Double = 0.6
var saturation: Double = 1.0
var brightness: Double = 1.0
}Host SwiftUI controls inside AppKit
When a component's rendering and interaction model changes substantially, rebuilding it in SwiftUI can be simpler than evolving the existing AppKit view. The sample replaces three custom slider cells with a circular HSB color picker drawn using SwiftUI Canvas.
Canvas provides an immediate-mode drawing model similar to AppKit or UIKit drawing: each redraw receives a fresh GraphicsContext, and code issues strokes, fills, transforms, and filters. Existing Core Graphics drawing can be reused through withCGContext.
- The SwiftUI color picker uses the same
@ObservableColorModelas the previous AppKit implementation. - The picker draws a hue ring, saturation and brightness semicircles, and a center color preview.
- The SwiftUI view is embedded in the existing
NSViewhierarchy usingNSHostingView, which is itself anNSViewsubclass.
Embed a SwiftUI view in AppKit
Use NSHostingView to place a SwiftUI component inside an AppKit view hierarchy.
NSHostingView(
rootView: HSBColorPicker(model: model)
)SwiftUI Canvas shape of the color picker
The session's full sample draws with Canvas; this abbreviated version shows the immediate-mode drawing structure.
struct HSBColorPicker: View {
var model: ColorModel
var body: some View {
Canvas { context, size in
let metrics = PickerMetrics(size: size)
drawPicker(in: &context, metrics: metrics,
hue: model.hue,
saturation: model.saturation,
brightness: model.brightness)
}
.contentShape(Circle())
.aspectRatio(1, contentMode: .fit)
}
}Bring AppKit gestures and menus into SwiftUI
Existing AppKit gesture recognizers do not need to be rewritten as SwiftUI gestures. NSGestureRecognizerRepresentable lets a SwiftUI view create and handle an NSGestureRecognizer subclass, then attach it with the standard .gesture modifier.
The sample adds a Force Click gesture that resets saturation and brightness to full intensity. Because Force Click is not available on every input device, the same action is also exposed through a SwiftUI-built main menu with keyboard shortcuts.
- Implement
makeNSGestureRecognizer(context:)to create the AppKit recognizer. - Implement
handleNSGestureRecognizerAction(_:context:)to respond when the recognizer fires. - Attach the representable gesture to a SwiftUI view using
.gesture. - Build menu content as a SwiftUI
View, then wrap it inNSHostingMenuand assign it as an AppKitNSMenuItemsubmenu.
Wrap an AppKit gesture recognizer for SwiftUI
NSGestureRecognizerRepresentable reuses an existing AppKit recognizer from a SwiftUI view.
struct ForceClickReset: NSGestureRecognizerRepresentable {
var model: ColorModel
func makeNSGestureRecognizer(context: Context) -> ForceClickGestureRecognizer {
ForceClickGestureRecognizer()
}
func handleNSGestureRecognizerAction(_ recognizer: ForceClickGestureRecognizer,
context: Context) {
withAnimation {
model.saturation = 1
model.brightness = 1
}
}
}Host SwiftUI menu content in the AppKit main menu
NSHostingMenu is an NSMenu subclass, so it can be inserted into an existing AppKit menu tree.
let colorMenu = NSHostingMenu(rootView: ColorMenu(model: colorModel))
colorMenu.title = "Color"
let colorMenuItem = NSMenuItem()
colorMenuItem.submenu = colorMenu
mainMenu.addItem(colorMenuItem)Add SwiftUI scenes from an AppKit lifecycle
SwiftUI scenes can be added dynamically from an existing AppKit app without moving to the SwiftUI app lifecycle. The sample uses NSHostingSceneRepresentation from NSApplicationDelegate.applicationWillFinishLaunching to add a MenuBarExtra scene and a Settings scene.
The menu bar extra provides quick access to light controls. The settings window contains a toggle bound to app state that inserts or removes the menu bar extra, and NSHostingSceneRepresentation exposes an environment action for opening settings programmatically.
- Create an
NSHostingSceneRepresentationwith one or more SwiftUI scenes. - Register it with
NSApplication.shared.addSceneRepresentation(...). - Use
MenuBarExtra(..., isInserted:)to let state control whether the menu bar item is present. - Use the representation's environment
openSettings()action from an@IBActionwhen an AppKit menu command should open the SwiftUI settings scene.
Register SwiftUI scenes from an AppKit app delegate
NSHostingSceneRepresentation bridges SwiftUI scenes into an app that still uses NSApplicationDelegate.
func applicationWillFinishLaunching(_ notification: Notification) {
let scenes = NSHostingSceneRepresentation {
LightMenuBarExtra(appModel: model)
LightSettings(appModel: model)
}
NSApplication.shared.addSceneRepresentation(scenes)
openSettingsAction = { scenes.environment.openSettings() }
}
@IBAction func openSettings(_ sender: Any?) {
openSettingsAction?()
}Related Sessions
- Compose advanced graphics effects with SwiftUI
- What's new in UIKit
- Bring multiple windows to your SwiftUI app
- Use SwiftUI with AppKit
- Use SwiftUI with UIKit
- Add rich graphics to your SwiftUI app
Code-along: Build powerful drag and drop in SwiftUI
Build SwiftUI drag-and-drop interactions with reorderable content, multi-item drag containers, preview formations, and move-aware drop configuration.
WidgetKit foundations
Learn WidgetKit fundamentals: build SwiftUI widgets with timelines, reload policies, app integration, supported families, and adaptive styling.