-
Moderniza tu app de AppKit
Actualiza tu app de AppKit para que se ajuste a las convenciones modernas de macOS. Explora el manejo de entradas mediante eventos de control y reconocedores de gestos, y trasciende los bucles de seguimiento tradicionales. Mejora la navegación por teclado en tu app, implementa una restauración fluida del estado tras los reinicios y aprovecha las nuevas API de concentricidad de esquinas, que permiten que tu interfaz se integre a la perfección con la estética de macOS.
Capítulos
- 0:00 - Introducción
- 1:06 - Métodos de entrada modernos
- 1:27 - Procesamiento moderno de eventos mediante reconocedores de gestos
- 2:25 - Selección, menús contextuales y arrastrar y soltar
- 3:52 - Selección de texto en vistas personalizadas
- 4:26 - Eventos de control y reconocedores de gestos
- 5:51 - Navegación con teclado y elementos de estado
- 8:57 - Continuidad entre lanzamientos
- 9:08 - Cierre controlado de la app
- 9:55 - Restauración del estado
- 14:09 - Actualizaciones de diseño
- 14:24 - Actualizaciones de Liquid Glass en macOS 27
- 15:41 - Concentricidad
- 16:59 - Próximos pasos
Recursos
- Use SwiftUI with AppKit
- Restoring your app’s state with AppKit
- Gestures
- TN3212: Adopting gesture recognizers for Sidecar touch support
- NSControl.Events
Videos relacionados
WWDC26
-
Buscar este video…
-
-
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) } // ... }
-