2022 EssentialsSwiftUI & UI Frameworks
WWDC22 · 19 min · Essentials / SwiftUI & UI Frameworks
Use SwiftUI with AppKit
Discover how the Shortcuts app uses both SwiftUI and AppKit to create a top-tier experience on macOS. Follow along with the Shortcuts team as we explore how you can host SwiftUI views in AppKit code, handle layout and sizing, participate in the responder chain, enable navigational focus, and more. We’ll also show you how to host AppKit views, helping you migrate existing code into a SwiftUI layout within your app.
Watch at developer.apple.com ↗Code shown on screen · 13 snippets
SidebarView and SidebarItem
struct SidebarView: View {
private var selectedItem: SidebarItem
var body: some View {
List(selection: $selectedItem) {
...
Section("Shortcuts") { ... }
Section("Folders") { ... }
}
}
}
enum SidebarItem: Hashable {
case gallery
case allShortcuts
...
case folder(Folder)
} Hosting SwiftUI sidebar
let splitViewController = NSSplitViewController()
let sidebar = NSHostingController(rootView: SidebarView(...))
let splitViewItem = NSSplitViewItem(viewController: sidebar)
splitViewController.addSplitViewItem(splitViewItem) Sidebar selection model
class SelectionModel: ObservableObject {
var selectedItem: SidebarItem = .allShortcuts
}
// AppKit Window Controller
cancellable = selectionModel.$selectedItem.sink { newItem in
// update the NSSplitViewController detail
} Collection view item hosting SwiftUI
class ShortcutItemView: NSCollectionViewItem {
private var hostingView: NSHostingView<ShortcutView>?
func displayShortcut(_ shortcut: Shortcut) {
let shortcutView = ShortcutView(shortcut: shortcut)
if let hostingView = hostingView {
hostingView.rootView = shortcutView
} else {
let newHostingView = NSHostingView(rootView: shortcutView)
view.addSubview(newHostingView)
setupConstraints(for: newHostingView)
self.hostingView = newHostingView
}
}
} Popover presentation
viewController.present(NSHostingController(rootView: ...),
asPopoverRelativeTo: rect, of: view,
preferredEdge: .maxY, behavior: .transient) Sheet presentation
viewController.presentAsSheet(NSHostingController(rootView: ...)) Modal window presentation
let hostingController = NSHostingController(rootView: ModalView())
hostingController.title = "Window Title"
viewController.presentAsModalWindow(hostingController) Sizing options
hostingController.sizingOptions = [.minSize, .intrinsicContentSize, .maxSize] Copy, Cut, and Paste commands
Image(...)
.focusable()
.copyable { ... }
.cuttable { ... }
.pasteDestination(payloadType: Image.self) { ... } Respond to standard commands
struct ShortcutsEditorView: View {
var body: some View {
ScrollView { ... }
.onMoveCommand { moveSelection(direction: $0) }
.onExitCommand { cancelOperations() }
.onCommand(#selector(NSResponder.selectAll(_:)) { selectAllActions() }
.onCommand(#selector(moveActionUp(_:)) { moveSelectedAction(.up) }
.onCommand(#selector(moveActionDown(_:)) { moveSelectedAction(.down) }
}
} Script editor
class ScriptEditorView: NSView {
var sourceCode: String
var isEditable: Bool
weak var delegate: ScriptEditorViewDelegate?
}
protocol ScriptEditorViewDelegate: AnyObject {
func sourceCodeDidChange(in view: ScriptEditorView) -> Void
} Script editor container
struct ScriptEditorContainerView: View {
var sourceCode: String = ""
var body: some View {
VStack {
CompileButton { compile(code: sourceCode) }
Divider()
ScriptEditorRepresentable(sourceCode: $sourceCode)
}
}
} Script editor representable
struct ScriptEditorRepresentable: NSViewRepresentable {
var sourceCode: String
func makeNSView(context: Context) -> ScriptEditorView {
let scriptEditor = ScriptEditorView(frame: .zero)
scriptEditor.delegate = context.coordinator
return scriptEditor
}
func updateNSView(_ nsView: ScriptEditorView, context: Context) {
if sourceCode != scriptEditor.sourceCode {
scriptEditor.sourceCode = sourceCode
}
scriptEditor.isEditable = context.environment.isEnabled
// Make sure coordinator has a reference to the current value
// of the binding:
context.coordinator.representable = self
}
func makeCoordinator() -> Coordinator {
Coordinator(representable: self)
}
}
class Coordinator: NSObject, ScriptEditorViewDelegate {
var representable: ScriptEditorRepresentable
init(representable: ScriptEditorRepresentable) { ... }
func sourceCodeDidChange(in view: ScriptEditorView) {
representable.sourceCode = view.sourceCode
}
} Resources
Related sessions
-
23 min -
11 min