-
Modernisez votre app AppKit
Mettez à jour votre app AppKit en appliquant les conventions modernes de macOS. Plongez dans la gestion des entrées avec les évènements de contrôle et les outils de reconnaissance de gestes, en allant au-delà des boucles de suivi traditionnelles. Améliorez la navigation au clavier dans votre app, mettez en place une restauration fluide de l'état après un redémarrage et tirez parti des nouvelles API de concentricité des coins qui permettent à votre interface de s'intégrer parfaitement à l'esthétique de macOS.
Chapitres
- 0:00 - Introduction
- 1:06 - Entrées modernes
- 1:27 - Gestion moderne des évènements avec des reconnaisseurs de gestes
- 2:25 - Sélection, menus contextuels et glisser-déposer
- 3:52 - Sélection de texte dans des vues personnalisées
- 4:26 - Évènements de contrôle et reconnaisseurs de gestes
- 5:51 - Navigation au clavier et éléments de barre d’état
- 8:57 - Continuité entre les lancements
- 9:08 - Fermeture en douceur de l’app
- 9:55 - Restauration de l’état
- 14:09 - Mises à jour du design
- 14:24 - Mises à jour de Liquid Glass dans macOS 27
- 15:41 - Concentricité
- 16:59 - Étapes suivantes
Ressources
- Use SwiftUI with AppKit
- Restoring your app’s state with AppKit
- Gestures
- TN3212: Adopting gesture recognizers for Sidecar touch support
- NSControl.Events
Vidéos connexes
WWDC26
-
Rechercher dans cette vidéo…
-
-
3:35 - Modern dragging delegate
// Modern dragging delegate methods func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> (any NSPasteboardWriting)? { let pasteboardItem = NSPasteboardItem() pasteboardItem.setString(..., forType: .string) return pasteboardItem } -
4:55 - Control events
// Use control events let button = NSButton() button.addTarget( self, action: #selector(trackingEndedOutsideHandler), for: .trackingEndedOutside ) -
5:44 - hitTest override
override func hitTest(_ point: NSPoint) -> NSView? { return nil } -
6:24 - autorecalculatesKeyViewLoop
window.autorecalculatesKeyViewLoop = true -
7:37 - Expanded interface delegate — setup
// Set the expanded interface delegate @main class LightAppDelegate: NSObject, NSApplicationDelegate { lazy var lightStatusItem: NSStatusItem = { ... }() func applicationDidFinishLaunching(_ notification: Notification) { // ... lightStatusItem.expandedInterfaceDelegate = self } } -
7:52 - Expanded interface delegate — methods
// Implement the delegate methods extension LightAppDelegate: NSStatusItemExpandedInterfaceDelegate { // ... func statusItem(_ statusItem: NSStatusItem, didBegin session: NSStatusItemExpandedInterfaceSession) { // Show window } func statusItemDidEndExpandedInterfaceSession( _ statusItem: NSStatusItem, animated: Bool) { // Hide window } func selectedAction() { // Take the action // Cancel session to request window dismissal lightStatusItem.expandedInterfaceSession?.cancel() } } -
8:16 - Expanded interface delegate — cancel
// Cancel the session when dismissing extension LightAppDelegate: NSStatusItemExpandedInterfaceDelegate { // ... func statusItem(_ statusItem: NSStatusItem, didBegin session: NSStatusItemExpandedInterfaceSession) { // Show window } func statusItemDidEndExpandedInterfaceSession( _ statusItem: NSStatusItem, animated: Bool) { // Hide window } func selectedAction() { // Take the action // Cancel session to request window dismissal lightStatusItem.expandedInterfaceSession?.cancel() } } -
9:45 - preventsApplicationTerminationWhenModal
window.preventsApplicationTerminationWhenModal = false -
10:18 - Set window identifiers for state restoration
// Set window identifiers for state restoration @MainActor class MainWindowController: NSWindowController, NSWindowDelegate { // ... convenience init() { let window = NSWindow( ... ) // ... window.identifier = NSUserInterfaceItemIdentifier(WindowIdentifiers.mainWindow) window.setFrameAutosaveName(WindowIdentifiers.mainWindow) window.isRestorable = true window.restorationClass = WindowRestorationHandler.self // ... } } -
11:04 - encodeRestorableState
// Preserve state to recreate the UI @MainActor class MainWindowController: NSWindowController, NSWindowDelegate { // ... override func encodeRestorableState(with coder: NSCoder) { super.encodeRestorableState(with: coder) // ... coder.encode(selectedProduct?.identifier.uuid, forKey: RestorationKeys.productIdentifier) // ... } // ... } -
11:50 - invalidateRestorableState
// Invalidate restorable state when the view hierarchy changes @MainActor class MainWindowController: NSWindowController, NSWindowDelegate { // ... convenience init() { // ... splitViewController.onProductSelected = { [weak self] product in self?.invalidateRestorableState() } } } -
12:26 - restoreWindow(withIdentifier:)
// Restore windows class WindowRestorationHandler: NSObject, NSWindowRestoration { static func restoreWindow( withIdentifier identifier: NSUserInterfaceItemIdentifier, state: NSCoder, completionHandler: @escaping (NSWindow?, Error?) -> Void ) { //... if identifier == .mainWindow, let window = appDelegate.mainWindowController?.window { completionHandler(window, nil) } else if identifier == .imageWindow { let controller = ImageWindowController() appDelegate.imageWindowControllers.append(controller) completionHandler(controller.window, nil) } else { completionHandler(nil, error) } } } -
13:29 - restoreState
// Restore window UI @MainActor class MainWindowController: NSWindowController, NSWindowDelegate { //... override func restoreState(with coder: NSCoder) { super.restoreState(with: coder) if let productId = coder.decodeObject( of: [NSString.self], forKey: RestorationKeys.productIdentifier) as? String { splitViewController?.selectedProductId = productId } //... } } -
16:11 - cornerConfiguration
// Subclass NSView to override cornerConfiguration class LocalWeatherView: NSView { // ... override var cornerConfiguration: NSViewCornerConfiguration? { let radius: NSViewCornerRadius = .containerConcentric(minimumCornerRadius) return .uniformCorners(radius: radius) } // ... }
-