{"$schema": "https://raw.githubusercontent.com/guitaripod/wwdc-sessions/master/schema/transcript.schema.json", "id": "wwdc2026-272", "language": "eng", "source": "https://developer.apple.com/videos/play/wwdc2026/272", "wordCount": 1893, "segments": [{"start": 7.0, "text": "Hello, I'm David Nadoba, an engineer on the UI Frameworks team."}, {"start": 12.0, "text": "Today, I'm excited to talk to you about using SwiftUI"}, {"start": 15.0, "text": "with your existing AppKit or UIKit app."}, {"start": 18.0, "text": "SwiftUI was designed from the beginning to work great alongside AppKit and UIKit."}, {"start": 23.0, "text": "Just like Swift was designed to work together with Objective-C."}, {"start": 28.0, "text": "This is ideal for incremental adoption without the need to rewrite everything"}, {"start": 31.0, "text": "or start from scratch."}, {"start": 33.0, "text": "Apple has used this strategy throughout the years."}, {"start": 36.0, "text": "Logic Pro is using SwiftUI for plugins like the Quantec Room Simulator,"}, {"start": 42.0, "text": "or the Beat Breaker plugin for both macOS and iPadOS."}, {"start": 46.0, "text": "The Coding Assistant in Xcode was from start using SwiftUI"}, {"start": 50.0, "text": "and in Xcode 27 is expanding from the sidebar to the editor."}, {"start": 55.0, "text": "Even without explicit adoption, most apps use SwiftUI implicitly nowadays."}, {"start": 60.0, "text": "The UI Frameworks team used the new design as an opportunity"}, {"start": 63.0, "text": "to implement Controls in SwiftUI."}, {"start": 66.0, "text": "Now, even if you use AppKit types"}, {"start": 68.0, "text": "like NSSlider,"}, {"start": 71.0, "text": "NSSwitch,"}, {"start": 72.0, "text": "and NSSegmentedControl,"}, {"start": 74.0, "text": "SwiftUI is used under the hood to render these views and more."}, {"start": 78.0, "text": "Liquid Glass used in those controls and in other parts of the OS"}, {"start": 82.0, "text": "is also using SwiftUI to share large parts of the implementation"}, {"start": 85.0, "text": "across frameworks and platforms."}, {"start": 88.0, "text": "In this video I will share how you can start adopting SwiftUI in more places too."}, {"start": 94.0, "text": "I will focus on macOS,"}, {"start": 95.0, "text": "but the concepts apply to all other Apple platforms as well."}, {"start": 99.0, "text": "First, I will show how you can use @Observable"}, {"start": 102.0, "text": "to automatically update your NSView, even before using any SwiftUI."}, {"start": 107.0, "text": "Next, I will talk about when it is a good time to consider using SwiftUI"}, {"start": 111.0, "text": "and integrate it in an NSView hierarchy."}, {"start": 115.0, "text": "I will also show you how you can add an NSGestureRecognizer"}, {"start": 118.0, "text": "directly to your SwiftUI View."}, {"start": 121.0, "text": "Then, I will create menu items in SwiftUI"}, {"start": 124.0, "text": "and add them to the existing main menu."}, {"start": 127.0, "text": "Finally, I will cover how you can use SwiftUI Scenes"}, {"start": 130.0, "text": "from your existing NSApplicationDelegate."}, {"start": 133.0, "text": "Over the course of this presentation"}, {"start": 135.0, "text": "I will use a reduced version of an existing AppKit app that I made."}, {"start": 139.0, "text": "This app can control lights, like this addressable ring lamp on my desk."}, {"start": 144.0, "text": "It has controls to change the color,"}, {"start": 149.0, "text": "and run animations."}, {"start": 154.0, "text": "I'll walk you through how the sliders work"}, {"start": 156.0, "text": "and then demonstrate how the @Observable macro can help."}, {"start": 159.0, "text": "The app uses a color picker that is similar to the system color panel"}, {"start": 163.0, "text": "or color well, but is displayed inline to keep the controls always within reach."}, {"start": 169.0, "text": "The color is controlled through 3 sliders with a custom track gradient and knob."}, {"start": 174.0, "text": "As I move the knob of one slider,"}, {"start": 176.0, "text": "it redraws itself with the newly selected color."}, {"start": 179.0, "text": "At the same time, all other sliders also update accordingly."}, {"start": 185.0, "text": "A slider automatically redraws itself when its own value changes."}, {"start": 190.0, "text": "In my case, the changed value"}, {"start": 192.0, "text": "also influences the appearance of the other sliders,"}, {"start": 195.0, "text": "but AppKit doesn't automatically redraw them."}, {"start": 199.0, "text": "I currently need to manually tell AppKit"}, {"start": 201.0, "text": "to redraw the saturation and brightness slider"}, {"start": 204.0, "text": "whenever the hue value changes."}, {"start": 207.0, "text": "This is done by setting needsDisplay to true."}, {"start": 210.0, "text": "This also needs to be implemented similarly for value changes"}, {"start": 214.0, "text": "of all the remaining sliders and other external changes."}, {"start": 218.0, "text": "AppKit also supports automatic Observation of properties from @Observable types."}, {"start": 223.0, "text": "Leverage this by adding the @Observable macro to a Swift class."}, {"start": 228.0, "text": "All mutable variables then participate in the observation system."}, {"start": 234.0, "text": "The sliders are implemented as subclasses of NSSliderCell"}, {"start": 238.0, "text": "and customize the appearance"}, {"start": 239.0, "text": "by overriding certain draw methods like drawKnob."}, {"start": 243.0, "text": "I only need to access the properties of my new ColorModel"}, {"start": 246.0, "text": "inside the drawKnob method."}, {"start": 249.0, "text": "AppKit tracks each access and redraws whenever any accessed properties change."}, {"start": 255.0, "text": "No need for manually setting needsDisplay to true anymore."}, {"start": 259.0, "text": "This works for any draw method that is called as part of NSView draw"}, {"start": 263.0, "text": "like the drawKnob or this drawBar method from NSSliderCell."}, {"start": 268.0, "text": "NSView.draw(_:) is only one method that supports observation."}, {"start": 272.0, "text": "updateConstraints(), layout(), updateLayer(),"}, {"start": 276.0, "text": "and NSViewController equivalents support Observation too."}, {"start": 281.0, "text": "UIKit has even more methods that extend beyond UIView"}, {"start": 284.0, "text": "and UIViewController to UIButton, UICollectionViewCell and more."}, {"start": 290.0, "text": "You can back-deploy the integration to macOS 15"}, {"start": 293.0, "text": "by adding NSObservationTrackingEnabled to your Info.plist."}, {"start": 298.0, "text": "And to iOS 18 by adding UIObservationTrackingEnabled."}, {"start": 302.0, "text": "It is enabled by default with the 2026 releases and later."}, {"start": 307.0, "text": "For a closer look at Observation Tracking in UIKit"}, {"start": 310.0, "text": "watch \"What's new in UIKit\" from WWDC25."}, {"start": 315.0, "text": "Okay, here it is in action."}, {"start": 318.0, "text": "I will increase the brightness."}, {"start": 324.0, "text": "And change the hue to red."}, {"start": 332.0, "text": "Great, all sliders update"}, {"start": 334.0, "text": "and the new color is sent to the light over the network."}, {"start": 342.0, "text": "Adopting @Observable is a great start"}, {"start": 344.0, "text": "to get automatic updates in your NSView and NSViewController."}, {"start": 348.0, "text": "It also makes it easier to move to SwiftUI when you want to implement something new."}, {"start": 352.0, "text": "Speaking of something new,"}, {"start": 354.0, "text": "I have an idea for a different Color Picker design."}, {"start": 357.0, "text": "Hue starts with red, progresses through all the colors,"}, {"start": 361.0, "text": "and returns back to red."}, {"start": 363.0, "text": "I would like to represent this as a circular slider."}, {"start": 367.0, "text": "I can represent Saturation and Brightness"}, {"start": 370.0, "text": "as two Semicircles inside the outer hue ring."}, {"start": 375.0, "text": "Right in the middle"}, {"start": 376.0, "text": "I want to draw a preview of the resulting color as a circle."}, {"start": 380.0, "text": "The whole drawing code and interaction will completely change,"}, {"start": 383.0, "text": "so this is a great time to move to SwiftUI."}, {"start": 387.0, "text": "I can reuse the same @Observable ColorModel"}, {"start": 389.0, "text": "from the previous NSSlider based Color Picker."}, {"start": 393.0, "text": "In the view's body I use the Canvas view,"}, {"start": 395.0, "text": "which gives me access to an immediate mode drawing API."}, {"start": 399.0, "text": "Canvas is very similar to drawRect in AppKit or UIKit."}, {"start": 403.0, "text": "Each redraw calls your closure with a fresh GraphicsContext,"}, {"start": 407.0, "text": "and you issue draw commands like strokes, fills, transforms and filters,"}, {"start": 412.0, "text": "directly against it."}, {"start": 414.0, "text": "You can also reuse your existing CoreGraphics drawing code in SwiftUI"}, {"start": 419.0, "text": "by calling the withCGContext API."}, {"start": 422.0, "text": "For an introduction to Canvas,"}, {"start": 424.0, "text": "watch \"Add rich graphics to your SwiftUI app\" from WWDC21."}, {"start": 430.0, "text": "If you want to know how you can combine SwiftUI with your own Metal Shaders"}, {"start": 434.0, "text": "watch \"Compose advanced graphics effects with SwiftUI\" from WWDC26."}, {"start": 441.0, "text": "I still have a lot of places"}, {"start": 443.0, "text": "where the color picker is embedded in an NSView hierarchy."}, {"start": 446.0, "text": "I can wrap my SwiftUI view in an NSHostingView,"}, {"start": 450.0, "text": "which is a subclass of NSView."}, {"start": 453.0, "text": "Because I have already moved my model to @Observable,"}, {"start": 456.0, "text": "this is really all I need to do."}, {"start": 458.0, "text": "For an in-depth tour of NSHostingView and related types,"}, {"start": 461.0, "text": "watch \"Use SwiftUI with AppKit\" and \"Use SwiftUI with UIKit\" from WWDC 2022."}, {"start": 469.0, "text": "Before I show you this new Color Picker in action,"}, {"start": 471.0, "text": "I want to add one more feature."}, {"start": 474.0, "text": "I want to quickly reset the Brightness and Saturation to 100%"}, {"start": 478.0, "text": "with a single force click, which is a firm press on the trackpad."}, {"start": 482.0, "text": "I already have an NSGestureRecognizer for this"}, {"start": 485.0, "text": "that I use in other parts of the app."}, {"start": 488.0, "text": "I can bring this to a new SwiftUI View using NSGestureRecognizerRepresentable."}, {"start": 494.0, "text": "I start by creating a new struct that conforms to"}, {"start": 497.0, "text": "the NSGestureRecognizerRepresentable protocol."}, {"start": 500.0, "text": "In makeNSGestureRecognizer"}, {"start": 502.0, "text": "I initialize and return my NSGestureRecognizer subclass."}, {"start": 507.0, "text": "ForceClickGestureRecognizer is the type that I use in other parts of my app."}, {"start": 512.0, "text": "It recognizes when the pressure stage 2 is reached,"}, {"start": 515.0, "text": "which indicates that enough pressure has been applied to trigger a force click."}, {"start": 521.0, "text": "handleNSGestureRecognizerAction is called when the gesture is recognized."}, {"start": 526.0, "text": "This is the right place to reset the saturation and brightness to 100%."}, {"start": 531.0, "text": "Back in the HSBColorPicker SwiftUI view,"}, {"start": 534.0, "text": "I can now add this gesture with the .gesture modifier,"}, {"start": 537.0, "text": "just like a SwiftUI Gesture."}, {"start": 540.0, "text": "The ForceClickReset gesture works together with the existing drag gesture"}, {"start": 544.0, "text": "without any other changes."}, {"start": 547.0, "text": "SwiftUI also comes with more representable protocols"}, {"start": 550.0, "text": "like NSViewRepresentable"}, {"start": 552.0, "text": "that allows you to embed NSViews into your SwiftUI views."}, {"start": 556.0, "text": "A Force Click is not possible with all input devices,"}, {"start": 559.0, "text": "like the Magic Mouse or the trackpad of the MacBook Neo."}, {"start": 563.0, "text": "To make sure everyone can take advantage of this shortcut,"}, {"start": 566.0, "text": "I need to add a different way to access this feature."}, {"start": 569.0, "text": "In this case I will add a menu item with a keyboard shortcut."}, {"start": 574.0, "text": "My app is using AppKit's NSMenu for the main menu."}, {"start": 578.0, "text": "I will explain how to add the new menu item using SwiftUI."}, {"start": 582.0, "text": "I start by creating a new struct that conforms to the View protocol."}, {"start": 586.0, "text": "It has access to the shared ColorModel."}, {"start": 589.0, "text": "In the view's body I create a Button with a label"}, {"start": 593.0, "text": "and an action closure which resets the brightness and saturation to 100%."}, {"start": 598.0, "text": "Wrapping the modification in withAnimation makes SwiftUI animate the change."}, {"start": 605.0, "text": "To give quick access, I am adding a keyboardShortcut."}, {"start": 609.0, "text": "I have also added a Picker with the paletteStyle,"}, {"start": 611.0, "text": "to precisely select common colors."}, {"start": 615.0, "text": "I now need to add this SwiftUI View to the main menu."}, {"start": 620.0, "text": "I initialize an NSHostingMenu with the ColorMenu view for that."}, {"start": 625.0, "text": "NSHostingMenu is a subclass of NSMenu and therefore has properties"}, {"start": 629.0, "text": "like the title to configure the menu."}, {"start": 632.0, "text": "All that is left to do is to create an NSMenuItem,"}, {"start": 635.0, "text": "set that colorMenu as its submenu, and add that item to the mainMenu."}, {"start": 640.0, "text": "Now it is time to try it out."}, {"start": 642.0, "text": "I will turn it on."}, {"start": 647.0, "text": "And circle through all the hues to green."}, {"start": 657.0, "text": "I will press the Keyboard shortcut to decrease the brightness a couple times."}, {"start": 664.0, "text": "And then use the menu item to turn it off completely."}, {"start": 673.0, "text": "When I force click, my NSGestureRecognizer resets the brightness."}, {"start": 681.0, "text": "I incrementally added this custom SwiftUI control to my app."}, {"start": 685.0, "text": "The rest of my AppKit app continues to work, just like it did before."}, {"start": 690.0, "text": "As a final step, here is how you can bring complete SwiftUI Scenes to your app"}, {"start": 694.0, "text": "using your existing app delegate."}, {"start": 697.0, "text": "I always wanted to give people quick access"}, {"start": 699.0, "text": "to change the color or brightness of their lights."}, {"start": 702.0, "text": "For this, I can add a menu bar extra item."}, {"start": 705.0, "text": "SwiftUI's MenuBarExtra scene makes this possible with just a few lines."}, {"start": 710.0, "text": "NSHostingSceneRepresentation wraps a SwiftUI scene"}, {"start": 713.0, "text": "and allows it to be added dynamically from an existing AppKit app."}, {"start": 716.0, "text": "A good place to add a scene is applicationWillFinishLaunching"}, {"start": 720.0, "text": "in your NSApplicationDelegate."}, {"start": 723.0, "text": "Call addSceneRepresentation with your scenes,"}, {"start": 726.0, "text": "and SwiftUI will do the rest."}, {"start": 730.0, "text": "If you have a MenuBarExtra scene,"}, {"start": 731.0, "text": "it is also a good idea to make it possible for people to remove and insert it again."}, {"start": 737.0, "text": "A Settings scene is the perfect place to add a Toggle that controls"}, {"start": 741.0, "text": "whether the MenuBarExtra scene is inserted."}, {"start": 744.0, "text": "NSHostingSceneRepresentation has an environment property"}, {"start": 748.0, "text": "that exposes the openSettings() action."}, {"start": 751.0, "text": "It can be used from an @IBAction to open the settings window programmatically."}, {"start": 757.0, "text": "I'm opening the settings from the apps main menu."}, {"start": 763.0, "text": "And enable the menu bar extra item."}, {"start": 767.0, "text": "Let me quickly open the color picker."}, {"start": 771.0, "text": "And turn the light on one last time."}, {"start": 777.0, "text": "To learn more about SwiftUI scenes,"}, {"start": 779.0, "text": "watch \"Bring multiple windows to your SwiftUI app\" from WWDC22."}, {"start": 784.0, "text": "I have shown how you can mix SwiftUI and AppKit in different ways."}, {"start": 788.0, "text": "The right way to combine them depends on your app"}, {"start": 791.0, "text": "and the problem you are solving."}, {"start": 794.0, "text": "All APIs I have talked about today"}, {"start": 796.0, "text": "are available already on the 2026 releases or earlier."}, {"start": 801.0, "text": "A great first step is to try out @Observable"}, {"start": 804.0, "text": "to keep your model and NSViews automatically in sync"}, {"start": 807.0, "text": "and make the transition to SwiftUI seamless."}, {"start": 810.0, "text": "Consider SwiftUI when you implement a new component"}, {"start": 812.0, "text": "or rewrite an existing one."}, {"start": 816.0, "text": "Add your existing gesture recognizer subclasses to SwiftUI views."}, {"start": 820.0, "text": "Start with SwiftUI for new scenes even in your existing apps."}, {"start": 824.0, "text": "And remember, there are no expectations"}, {"start": 827.0, "text": "that an app needs to be entirely SwiftUI in order to take advantage of it."}, {"start": 832.0, "text": "Thank you for watching and thank you for building great apps!"}]}