SwiftUI & UI Frameworks • iOS, macOS • 14:03
Discover how to incrementally adopt SwiftUI in your existing AppKit or UIKit app. We’ll show you how to use the Observation framework to automatically update your views, integrate SwiftUI components into an existing view hierarchy, and bring gesture recognizers into SwiftUI. We’ll also explore how to add complete SwiftUI scenes to your app without changing your overall architecture.
Speaker: David Nadoba
Downloads from Apple
Transcript
Hello, I’m David Nadoba, an engineer on the UI Frameworks team. Today, I’m excited to talk to you about using SwiftUI with your existing AppKit or UIKit app. SwiftUI was designed from the beginning to work great alongside AppKit and UIKit. Just like Swift was designed to work together with Objective-C.
This is ideal for incremental adoption without the need to rewrite everything or start from scratch. Apple has used this strategy throughout the years. Logic Pro is using SwiftUI for plugins like the Quantec Room Simulator, or the Beat Breaker plugin for both macOS and iPadOS. The Coding Assistant in Xcode was from start using SwiftUI and in Xcode 27 is expanding from the sidebar to the editor.
Even without explicit adoption, most apps use SwiftUI implicitly nowadays. The UI Frameworks team used the new design as an opportunity to implement Controls in SwiftUI. Now, even if you use AppKit types like NSSlider, NSSwitch, and NSSegmentedControl, SwiftUI is used under the hood to render these views and more.
Liquid Glass used in those controls and in other parts of the OS is also using SwiftUI to share large parts of the implementation across frameworks and platforms. In this video I will share how you can start adopting SwiftUI in more places too. I will focus on macOS, but the concepts apply to all other Apple platforms as well. First, I will show how you can use @Observable to automatically update your NSView, even before using any SwiftUI.
Next, I will talk about when it is a good time to consider using SwiftUI and integrate it in an NSView hierarchy. I will also show you how you can add an NSGestureRecognizer directly to your SwiftUI View. Then, I will create menu items in SwiftUI and add them to the existing main menu.
Finally, I will cover how you can use SwiftUI Scenes from your existing NSApplicationDelegate. Over the course of this presentation I will use a reduced version of an existing AppKit app that I made. This app can control lights, like this addressable ring lamp on my desk. It has controls to change the color, and run animations.
I’ll walk you through how the sliders work and then demonstrate how the @Observable macro can help. The app uses a color picker that is similar to the system color panel or color well, but is displayed inline to keep the controls always within reach. The color is controlled through 3 sliders with a custom track gradient and knob. As I move the knob of one slider, it redraws itself with the newly selected color. At the same time, all other sliders also update accordingly.
A slider automatically redraws itself when its own value changes. In my case, the changed value also influences the appearance of the other sliders, but AppKit doesn’t automatically redraw them. I currently need to manually tell AppKit to redraw the saturation and brightness slider whenever the hue value changes. This is done by setting needsDisplay to true. This also needs to be implemented similarly for value changes of all the remaining sliders and other external changes. AppKit also supports automatic Observation of properties from @Observable types. Leverage this by adding the @Observable macro to a Swift class. All mutable variables then participate in the observation system.
The sliders are implemented as subclasses of NSSliderCell and customize the appearance by overriding certain draw methods like drawKnob. I only need to access the properties of my new ColorModel inside the drawKnob method. AppKit tracks each access and redraws whenever any accessed properties change. No need for manually setting needsDisplay to true anymore.
This works for any draw method that is called as part of NSView draw like the drawKnob or this drawBar method from NSSliderCell. NSView.draw(_:) is only one method that supports observation. updateConstraints(), layout(), updateLayer(), and NSViewController equivalents support Observation too. UIKit has even more methods that extend beyond UIView and UIViewController to UIButton, UICollectionViewCell and more.
You can back-deploy the integration to macOS 15 by adding NSObservationTrackingEnabled to your Info.plist. And to iOS 18 by adding UIObservationTrackingEnabled. It is enabled by default with the 2026 releases and later. For a closer look at Observation Tracking in UIKit watch “What’s new in UIKit” from WWDC25. Okay, here it is in action. I will increase the brightness. And change the hue to red.
Great, all sliders update and the new color is sent to the light over the network. Adopting @Observable is a great start to get automatic updates in your NSView and NSViewController. It also makes it easier to move to SwiftUI when you want to implement something new. Speaking of something new, I have an idea for a different Color Picker design. Hue starts with red, progresses through all the colors, and returns back to red. I would like to represent this as a circular slider.
I can represent Saturation and Brightness as two Semicircles inside the outer hue ring. Right in the middle I want to draw a preview of the resulting color as a circle. The whole drawing code and interaction will completely change, so this is a great time to move to SwiftUI.
I can reuse the same @Observable ColorModel from the previous NSSlider based Color Picker. In the view’s body I use the Canvas view, which gives me access to an immediate mode drawing API. Canvas is very similar to drawRect in AppKit or UIKit. Each redraw calls your closure with a fresh GraphicsContext, and you issue draw commands like strokes, fills, transforms and filters, directly against it. You can also reuse your existing CoreGraphics drawing code in SwiftUI by calling the withCGContext API. For an introduction to Canvas, watch “Add rich graphics to your SwiftUI app” from WWDC21.
If you want to know how you can combine SwiftUI with your own Metal Shaders watch “Compose advanced graphics effects with SwiftUI” from WWDC26. I still have a lot of places where the color picker is embedded in an NSView hierarchy. I can wrap my SwiftUI view in an NSHostingView, which is a subclass of NSView.
Because I have already moved my model to @Observable, this is really all I need to do. For an in-depth tour of NSHostingView and related types, watch “Use SwiftUI with AppKit” and “Use SwiftUI with UIKit” from WWDC 2022. Before I show you this new Color Picker in action, I want to add one more feature.
I want to quickly reset the Brightness and Saturation to 100% with a single force click, which is a firm press on the trackpad. I already have an NSGestureRecognizer for this that I use in other parts of the app. I can bring this to a new SwiftUI View using NSGestureRecognizerRepresentable.
I start by creating a new struct that conforms to the NSGestureRecognizerRepresentable protocol. In makeNSGestureRecognizer I initialize and return my NSGestureRecognizer subclass. ForceClickGestureRecognizer is the type that I use in other parts of my app. It recognizes when the pressure stage 2 is reached, which indicates that enough pressure has been applied to trigger a force click.
handleNSGestureRecognizerAction is called when the gesture is recognized. This is the right place to reset the saturation and brightness to 100%. Back in the HSBColorPicker SwiftUI view, I can now add this gesture with the .gesture modifier, just like a SwiftUI Gesture. The ForceClickReset gesture works together with the existing drag gesture without any other changes.
SwiftUI also comes with more representable protocols like NSViewRepresentable that allows you to embed NSViews into your SwiftUI views. A Force Click is not possible with all input devices, like the Magic Mouse or the trackpad of the MacBook Neo. To make sure everyone can take advantage of this shortcut, I need to add a different way to access this feature. In this case I will add a menu item with a keyboard shortcut.
My app is using AppKit’s NSMenu for the main menu. I will explain how to add the new menu item using SwiftUI. I start by creating a new struct that conforms to the View protocol. It has access to the shared ColorModel. In the view’s body I create a Button with a label and an action closure which resets the brightness and saturation to 100%. Wrapping the modification in withAnimation makes SwiftUI animate the change.
To give quick access, I am adding a keyboardShortcut. I have also added a Picker with the paletteStyle, to precisely select common colors. I now need to add this SwiftUI View to the main menu. I initialize an NSHostingMenu with the ColorMenu view for that. NSHostingMenu is a subclass of NSMenu and therefore has properties like the title to configure the menu. All that is left to do is to create an NSMenuItem, set that colorMenu as its submenu, and add that item to the mainMenu. Now it is time to try it out. I will turn it on. And circle through all the hues to green.
I will press the Keyboard shortcut to decrease the brightness a couple times. And then use the menu item to turn it off completely. When I force click, my NSGestureRecognizer resets the brightness. I incrementally added this custom SwiftUI control to my app. The rest of my AppKit app continues to work, just like it did before. As a final step, here is how you can bring complete SwiftUI Scenes to your app using your existing app delegate.
I always wanted to give people quick access to change the color or brightness of their lights. For this, I can add a menu bar extra item. SwiftUI’s MenuBarExtra scene makes this possible with just a few lines. NSHostingSceneRepresentation wraps a SwiftUI scene and allows it to be added dynamically from an existing AppKit app. A good place to add a scene is applicationWillFinishLaunching in your NSApplicationDelegate. Call addSceneRepresentation with your scenes, and SwiftUI will do the rest.
If you have a MenuBarExtra scene, it is also a good idea to make it possible for people to remove and insert it again. A Settings scene is the perfect place to add a Toggle that controls whether the MenuBarExtra scene is inserted. NSHostingSceneRepresentation has an environment property that exposes the openSettings() action. It can be used from an @IBAction to open the settings window programmatically. I’m opening the settings from the apps main menu.
And enable the menu bar extra item. Let me quickly open the color picker. And turn the light on one last time. To learn more about SwiftUI scenes, watch “Bring multiple windows to your SwiftUI app” from WWDC22. I have shown how you can mix SwiftUI and AppKit in different ways. The right way to combine them depends on your app and the problem you are solving.
All APIs I have talked about today are available already on the 2026 releases or earlier. A great first step is to try out @Observable to keep your model and NSViews automatically in sync and make the transition to SwiftUI seamless. Consider SwiftUI when you implement a new component or rewrite an existing one.
Add your existing gesture recognizer subclasses to SwiftUI views. Start with SwiftUI for new scenes even in your existing apps. And remember, there are no expectations that an app needs to be entirely SwiftUI in order to take advantage of it. Thank you for watching and thank you for building great apps!