Dokumentacja Touch na Macu: migracja, nota dla gier, HIG
Co Apple opublikowało dla Touch na Macu po talkach? Do wieczora 28 października artykuł AppKit „Adopting gesture recognizers in your app”, który w południe zwracał 404, był już dostępny, z czterowierszową tabelą, która każdemu rodzajowi kodu obsługującego mysz wskazuje następny krok; drugi artykuł, „Handling AppKit gestures”, opisuje widoki wyłączne, dwusekundowy limit czasu dla zablokowanych recognizerów i dwa klucze user defaults do debugowania; nowa nota techniczna, TN3216, dotyczy gier i kolejności emulowanych zdarzeń myszy; a trzy strony Human Interface Guidelines mają wpisy w dzienniku zmian z datą 28 października dotyczące dotyku na Macu.1234 Kafelek „Documentation resources” na stronie docelowej linkuje teraz artykuł i przykład z obietnicami plików, kafelek „Design resources” linkuje trzy strony HIG, a kafelek bety Xcode 27.3 nadal mówi „Coming soon”.5
TL;DR: Wpis dla programistów, opublikowany 28 października o 13:47 czasu pacyficznego, opierał się na trzech Tech Talkach i czerwcowej nocie technicznej; potrzebny mu artykuł AppKit zwracał w południe 404. O 21:51 ten artykuł był już dostępny, a obok niego dwa kolejne dokumenty. Artykuł o migracji dzieli kod na cztery wiersze: standardowe kontrolki i recognizery nie wymagają niczego; nadpisania mouseDown(with:) i pętle śledzenia dostają emulację jednego palca i powinny stać się recognizerami; podklasa kontrolki, która nadpisuje metodę myszy, wraca do pętli śledzenia; własny recognizer, który nigdy nie kończy, zatrzymuje swój widok wyłączny.1 Reguła stojąca za trzecim wierszem mówi, że AppKit rozstrzyga na podstawie tego, czy metoda jest nadpisana, „not on what the override does” (a nie tego, co robi nadpisanie), więc wywołanie super nie pomaga.1 NSPanGestureRecognizer tylko dla dotyku z buttonMask 0 obok pętli, którą się zachowuje, jest teraz udokumentowany, wraz z dokładnym skutkiem pozostawienia maski bez zmian: przeciągnięcie myszą uruchamia recognizer, a mouseDown(with:) nigdy się nie wykonuje.1 Widoki wyłączne są domyślnie włączone, gdy w Info.plist brak klucza, a szablon macOS App w Xcode zapisuje klucz jako NO tylko dla projektu, którego cyklem życia aplikacji jest app delegate AppKit, a nie dla domyślnego cyklu życia SwiftUI; odczytałem to w szablonach wewnątrz Xcode 27.1 i bety 27.2 na tym Macu.27 Zablokowany recognizer jest anulowany dwie sekundy po zakończeniu interakcji, a podczas debugowania jeden klucz user defaults wywołuje awarię przy brakującym nadpisaniu, drugi zaś zastępuje ciche anulowanie awarią.2 Nota techniczna dla gier wymienia dokładne zdarzenia myszy, które wytwarzają dotknięcie i przeciągnięcie, każe silnikom nie sumować przesunięć i wyjaśnia, że każda powierzchnia dotykowa pojawia się jako dodatkowa emulowana GCMouse, dopóki nie ustawi się NSIsTouchNative.3 HIG nazywa dotyk „a secondary input mode” (drugorzędnym trybem wprowadzania), zaleca „tap targets bigger, not the controls themselves” (większe obszary dotknięcia, a nie większe same kontrolki) i dodaje sześć reguł dla kontrolek, które pojawiały się tylko po najechaniu kursorem.4 Nadal brak bety Xcode 27.3 i trzech przełączników gestów systemowych, które o 21:51 czasu pacyficznego zwracają 404.56
Co się pojawiło i kiedy
Przy każdej pozycji poniżej podaję godzinę, o której ją sprawdziłem, ponieważ znaczniki samego serwera nie zgadzają się z tym, co strony pokazywały w ciągu dnia; wpis na żywo zawiera zapis dnia, z późnym wierszem dotyczącym tej publikacji.
- Artykuł AppKit. „Adopting gesture recognizers in your app” zwracał 404 z endpointu dokumentacji Apple w południe czasu pacyficznego; o 21:51 był dostępny, serwowany ze znacznikiem modyfikacji 14:30 czasu pacyficznego, wymieniony w sekcji Essentials AppKit po „Adopting Liquid Glass” oraz pod „Gesture handling” na stronie Gestures frameworku, a pierwszy zapis w Wayback Machine pochodzi z 18:08 czasu pacyficznego (potwierdzenia w źródłach).1 Strona Tech Talka „Bring Touch to your Mac app” nadal do niego nie linkuje.5
- Drugi artykuł. „Handling AppKit gestures” znajduje się obok niego na stronie Gestures. Nie ma historii wersji, czerwcowa nota techniczna do niego nie linkuje, Wayback Machine nie ma jego zapisu, a znacznik serwera to 22:35 czasu pacyficznego 27 października; nie było go wśród stron, które sprawdzałem w południe, więc nie mogę powiedzieć, kiedy się pojawił.2
- Nowa nota techniczna. „TN3216: Supporting touch input for games on macOS” ma jednowierszową historię wersji, „2026-10-28 First published”, i znacznik serwera 14:09 czasu pacyficznego.3
- Trzy strony HIG. „Designing for macOS”, „Gestures” i „Pointing devices” kończą się teraz wierszami dziennika zmian z datą 28 października 2026. Kopie pobrane w południe nie zawierały nic z nowego tekstu; kopie pobrane o 21:51 go zawierają. Nagłówek modyfikacji serwera dla nowej strony „Gestures” wskazuje 5:11 UTC 27 października, czyli wcześniej niż kopia z południa, w której tekstu brakowało; podaję oba fakty i żadnej minuty publikacji.4
- Strona docelowa. O 11:49 wszystkie trzy kafelki pod „Tools and documentation” miały status „Coming soon”, a ich linki były ukryte w komentarzach HTML. O 21:51 kafelek „Documentation resources” („Learn how to build and optimize your app for touch input.”) linkuje artykuł i przykład z obietnicami plików, a kafelek „Design resources” („Create experiences that take advantage of touch user interface patterns.”) linkuje trzy strony HIG oraz Apple Design Resources, których lista ramek produktów obejmuje „MacBook Pro PROLED XDR Display”. Kafelek „Xcode 27.3 beta” nadal ma status „Coming soon”, a komentarz HTML
TK skillnadal jest na stronie.5 - Przykład. Strona „Supporting drag and drop through file promises” mówi teraz o swoich recognizerach, że „The recognizers work the same way with a pointer or with direct touches on a Mac with a touch display” (recognizery działają tak samo ze wskaźnikiem i z bezpośrednim dotykiem na Macu z ekranem dotykowym).6
- Nic więcej się nie zmieniło. Najnowszą pozycją kanału aktualności dla programistów jest nadal „Say hello to Touch on Mac” z 10:00:55 PDT; najnowsze pozycje kanału wydań to wydania cząstkowe z 26 października, bez bety Xcode 27.3 ani macOS 27.3; lista Tech Talków zawiera te same trzy talki; strona ActivityKit ma znacznik z 8 października i nadal nie wymienia platformy macOS.56
Popołudniowy wpis mówił, że reguły na piśmie to talki i czerwcowa nota techniczna. Ten wpis dotyczy tego, co reguły na piśmie dodają, i koryguje popołudniowy wpis tam, gdzie wieczorne sprawdzenie przyniosło inne informacje; lista jest na końcu.
Tabela, która segreguje kod
Sekcja artykułu „Prepare your app for a Mac with a touch display” to tabela z trzema kolumnami (obsługa wejścia, zachowanie przy dotyku i następny krok) oraz czterema wierszami. Kolumna zachowania Apple jest cytowana; dwie pozostałe są skrócone, z wyjątkiem następnego kroku w trzecim wierszu, który jest sformułowaniem Apple:1
| Twój kod | „Behavior with touch” według Apple | Następny krok |
|---|---|---|
| Standardowe kontrolki AppKit i gesture recognizery | „Works with touch, with no changes needed.” | Przetestować. |
Nadpisania mouseDown(with:) i pętle śledzenia |
„AppKit emulates a single-finger tap or drag, but supports multi-finger gestures and individual touches only through gesture recognizers.” | Zastąpić je recognizerami. |
Podklasy NSButton, NSSlider i innych widoków AppKit, które nadpisują metody obsługi myszy |
„AppKit falls back to a tracking loop.” | „Replace the overrides with target-action pairs for tracking events.” |
Własne podklasy NSGestureRecognizer |
„A recognizer that never reaches a terminal state keeps its exclusive view, which can make your app appear unresponsive.” | Obsłużyć anulowanie i osiągnąć stan końcowy. |
Test, który następuje po tabeli, ma cztery wiersze i warto go przeprowadzić dokładnie tak, jak jest zapisany: „Controls respond to a tap, and buttons highlight and activate as expected”, „Dragging a view, selecting text, and scrolling work with one finger”, „Pinch and rotate work where your app supports them” oraz „Small targets, such as resize grips and grab handles, are easy to hit with a finger” (kontrolki reagują na dotknięcie, a przyciski podświetlają się i aktywują zgodnie z oczekiwaniami; przeciąganie widoku, zaznaczanie tekstu i przewijanie działają jednym palcem; szczypanie i obrót działają tam, gdzie aplikacja je obsługuje; małe cele, takie jak uchwyty zmiany rozmiaru i uchwyty do chwytania, łatwo trafić palcem).1 Lista wyszukiwania rozszerza przeprowadzony w talku audyt nadpisań mouseDown(with:) do pięciu symboli: „search your project for nextEvent(matching:until:inMode:dequeue:) and trackEvents(matching:timeout:mode:handler:), because tracking loops typically call these methods. Also search for mouseDown(with:) and mouseDragged(with:) overrides, and for uses of currentEvent in control action methods.”1 Ostatnie sprawdzenie dotyczy czwartego wiersza: „confirm that your app stays responsive after a touch interaction ends, and that every gesture recognizer action method handles the NSGestureRecognizer.State.cancelled state” (czyli upewnić się, że aplikacja reaguje po zakończeniu interakcji dotykowej i że każda metoda akcji gesture recognizera obsługuje stan NSGestureRecognizer.State.cancelled).1
Dwa zdania z Overview rozstrzygają kwestię, którą talk pozostawił do wywnioskowania. Emulacja jest jednopalcowa: „AppKit emulates mouse events from a single finger tapping or dragging” oraz „Relying on this emulation, however, means you miss out on multi-finger gestures, like pinch and rotate, that gesture recognizers handle” (poleganie na tej emulacji oznacza jednak utratę gestów wielopalcowych, takich jak szczypanie i obrót, które obsługują gesture recognizery).1 A stanowisko testowe to to, które wskazał talk, teraz w formie wskazówki: „Use Sidecar to test touch input without a Mac that has a touch display”, ponieważ „AppKit then routes touch on its screen the same way it does on a Mac with a touch display”.1
Reguła nadpisania
Za trzecim wierszem tabeli stoi reguła, której ani talk, ani czerwcowa nota techniczna nie sformułowały tak wprost. Jeśli dziedziczy się po NSButton i nadpisuje mouseDown(with:), aby dodać animację podświetlenia, „AppKit treats your override as a replacement for its own click handling, rather than as a side effect” (AppKit traktuje nadpisanie jako zastępstwo własnej obsługi kliknięć, a nie efekt uboczny), a przycisk wraca do pętli śledzenia.1 Dalej: „AppKit decides based on whether your subclass overrides the method, not on what the override does”, więc ten powrót obowiązuje nawet wtedy, gdy nadpisanie wywołuje super.mouseDown(with:), a „Overriding mouseDragged(with:) or mouseUp(with:) has the same effect”.1
Zastępstwem jest wzorzec zdarzeń kontrolek z talku, a listing w artykule różni się od listingu z talku jednym szczegółem, który warto skopiować. Listing z talku w 2:24 rejestrował jedną akcję dla [.trackingExited, .trackingEndedInside] i osobną dla .trackingCancelled; zbiór ukrywania w artykule to [.trackingExited, .trackingEndedInside, .trackingEndedOutside, .trackingCancelled], co dodaje naciśnięcie kończące się poza przyciskiem.19 trackingEndedOutside nie jest nowe: dokumentacja Apple podaje je przy macOS 11.0, a SDK macOS 27.0 deklaruje je w NSControl.h jako NSControlEventTrackingEndedOutside = 1 << 7 (analiza autora).67 Artykuł zaznacza też, że metody akcji muszą być udostępnione Objective-C przez @objc oraz że jeśli potrzebna jest tylko aktywacja, primaryActionTriggered wywołuje się „regardless of whether someone used a mouse, trackpad, or touch to produce it” (niezależnie od tego, czy wywołała ją mysz, gładzik czy dotyk).1
Widoki kontenerowe mają tę samą pułapkę: „If you start a drag from mouseDragged(with:) in an NSTableView, NSCollectionView, NSOutlineView, or NSBrowser, AppKit falls back to a tracking loop for the interaction”, a wyjściem są metody przeciągania źródła danych i delegata, dla tabeli tableView(_:pasteboardWriterForRow:).1
Dla dwóch typowych nadpisań mouseDown(with:) artykuł podaje pary przed i po. „Most mouseDown(with:) overrides do one of two things: mark an item as selected when someone clicks it, or show a menu when someone Control-clicks” (większość nadpisań zaznacza element po kliknięciu albo pokazuje menu po kliknięciu z Control).1 W przypadku zaznaczenia należy zamiast tego obserwować isSelected na elemencie widoku kolekcji lub widoku wiersza tabeli, z zastrzeżeniem, że obserwator „also runs when your code changes the selection programmatically”; w przypadku menu należy nadpisać menu(for:) i pozwolić AppKit je wyświetlić albo ustawić menu lub defaultMenu, gdy menu nie zależy od kliknięcia.1
Recognizer tylko dla dotyku obok zachowanej pętli
Talk polecał recognizer przesunięcia tylko dla dotyku obok istniejącej pętli śledzenia jako drogę na wydanie cząstkowe. Artykuł dokumentuje ten sam przepis w sekcji „Add touch support to your custom tracking loop”, ze wspólnymi funkcjami początku, kontynuacji, końca i anulowania, a następnie opisuje działanie każdej właściwości z precyzją, na którą w talku nie było miejsca:1
let touchRecognizer = NSPanGestureRecognizer(target: self, action: #selector(handleTouchPan))
touchRecognizer.buttonMask = 0
touchRecognizer.allowedTouchTypes = .direct
addGestureRecognizer(touchRecognizer)
„setting buttonMask to 0 means the gesture recognizer only recognizes touches, not mouse or trackpad input, so your tracking loop keeps handling the mouse and trackpad exactly as before” (ustawienie buttonMask na 0 sprawia, że recognizer rozpoznaje tylko dotyk, a pętla obsługuje mysz i gładzik tak jak dotąd).1 Jeśli zostawi się maskę z wartością domyślną, recognizer „also responds to the primary mouse button. A mouse drag then begins the gesture recognizer, and AppKit doesn’t call your mouseDown(with:) override, so the tracking loop doesn’t run. A click that doesn’t move still reaches your tracking loop” (reaguje też na główny przycisk myszy: przeciągnięcie uruchamia recognizer, nadpisanie mouseDown(with:) nie jest wywoływane i pętla się nie wykonuje, a kliknięcie bez ruchu nadal do niej dociera).1 Maska nie jest więc drobiazgiem; bez niej pętla po cichu przestaje dostawać przeciągnięcia. Wiersz z allowedTouchTypes to dodatkowe zabezpieczenie: „Direct touches are already the default for allowedTouchTypes, and setting it to .direct states that intent explicitly”.1 Własna ocena Apple tego podejścia: „This is often the fastest, lowest-risk way to add touch support to code with a tracking loop you’d rather not rewrite all at once” (to często najszybszy i najmniej ryzykowny sposób dodania dotyku do kodu z pętlą, której nie chce się przepisywać od razu).1
Jeśli natomiast zastępuje się pętlę, przykład przesunięcia w artykule przywraca podpisowi początkowe położenie przy .cancelled, a pętla, która rozpoczynała sesję przeciągania, przekazuje do beginDraggingSession(items:gesture:source:) recognizer zamiast zdarzenia.1
Filtrowanie dwiema metodami delegata
Sekcją najbardziej nową względem talków jest „Filter gesture recognizer candidates”. Recognizery są zbierane przez hit-testing, a nie przez łańcuch responderów: „Gesture recognizers don’t use the responder chain.”1 Gdy AppKit je zbiera, „not every recognizer it finds is actually applicable to the interaction” (nie każdy znaleziony recognizer dotyczy danej interakcji), a aplikacja usuwa te nieadekwatne za pomocą dwóch metod delegata: gestureRecognizer(_:shouldAttemptToRecognizeWith:) „to inspect the initiating NSEvent for pointer-based input” oraz gestureRecognizer(_:shouldReceive:) „to inspect each new NSTouch for touch input”.1 Sens obu: „Either method gives you the initial input, something the recognizer doesn’t otherwise expose, and lets you stop the recognizer from handling that event stream or touch” (każda z metod daje dostęp do początkowego wejścia, którego recognizer inaczej nie udostępnia, i pozwala powstrzymać go przed obsługą tego strumienia zdarzeń lub dotyku).1 A podział jest ścisły: „Direct touches don’t call this method. To filter them, implement gestureRecognizer(_:shouldReceive:) instead, as shown above.”1 Filtrowanie wyłącznie przez metodę zdarzeń pozostawia bezpośredni dotyk niefiltrowany przez tę metodę delegata.
Oba przykłady to wzorce, które, jak się spodziewam, będą kopiowane. Uchwyt zmiany rozmiaru: recognizer zmiany rozmiaru dołącza się do całego widoku, a w obu metodach delegata zwraca się informację, czy położenie leży w ramce uchwytu, przy czym true zwraca się „only to give the gesture recognizer a chance to attempt recognition, not to guarantee it succeeds” (tylko po to, by dać recognizerowi szansę na próbę rozpoznania, a nie by zagwarantować sukces).1 Oś czasu z klipami: jeden recognizer na osi czasu, a w metodzie zdarzeń hit-test klipu, zapamiętanie go dla metody akcji i zwrócenie false, gdy klipu nie ma.1 Towarzyszące ostrzeżenie dla małych celów: „If a custom control expects a touch to land inside its visible area, update it to handle touches that land outside that area” (jeśli własna kontrolka zakłada, że dotknięcie trafi w jej widoczny obszar, należy ją zaktualizować tak, by obsługiwała dotknięcia poza tym obszarem).1
Priorytet między recognizerami, które przetrwały filtrowanie, ustala się wymaganiem niepowodzenia, a artykuł powtarza ostrzeżenie z drugiego artykułu, „Use this sparingly, because the waiting recognizer responds more slowly”, i podaje przykład, że „making a single click wait for a double click to fail makes clicks feel sluggish” (gdy pojedyncze kliknięcie czeka na niepowodzenie podwójnego, kliknięcia wydają się ociężałe).1
currentEvent, hover i przesunięcia
„Handle touch input” powtarza w formie dokumentacji zdanie porządkujące z talku: AppKit „doesn’t deliver direct touches (NSTouch) through the responder chain or through event monitors” (nie dostarcza bezpośrednich dotknięć przez łańcuch responderów ani przez monitory zdarzeń), a „Gesture recognizers are the only way to respond to multi-finger gestures or to receive individual touches from a Mac with a touch display or a Sidecar-connected iPad” (gesture recognizery to jedyny sposób na gesty wielopalcowe i pojedyncze dotknięcia z Maca z ekranem dotykowym lub z iPada podłączonego przez Sidecar).1 Strona przeglądu wejścia w AppKit mówi to samo od strony respondera: „The responder chain receives touch events only from indirect touches on a trackpad, if a view asks for them. It doesn’t receive touch events from direct touches on a Mac with a touch display.”6
Rada dotycząca sprawdzania możliwości urządzenia brzmi: unikać go. „In most cases, instead of checking whether a device supports touch, write your event-handling code to work the same way regardless of input source, and decide on alternate presentations based on the interaction type” (w większości przypadków, zamiast sprawdzać, czy urządzenie obsługuje dotyk, kod obsługi zdarzeń powinien działać tak samo niezależnie od źródła wejścia, a o alternatywnej prezentacji należy decydować na podstawie typu interakcji).1 Jedynym podanym przykładem dla touchCapabilities jest „capturing a signature on a touch display” (przechwytywanie podpisu na ekranie dotykowym).1
currentEvent ma własną sekcję. Ponieważ interakcje z kontrolkami przechodzą przez recognizery, „currentEvent no longer reliably returns the event that initiated an action” (currentEvent nie zwraca już niezawodnie zdarzenia, które zainicjowało akcję), a para przed i po zastępuje flagi modyfikatorów zdarzenia klasowym NSEvent.modifierFlags, z zastrzeżeniem: „These report the state at the time you query them, not at the time of the original event. A touch doesn’t register in pressedMouseButtons.”1 Co się nie zmienia: „Keyboard events and scroll-wheel events behave the same on every input source: AppKit delivers them through the responder chain. Hover tracking, which relies on mouseMoved(with:) and NSTrackingArea, applies to pointer input only, because a touch display has no hover” (zdarzenia klawiatury i kółka przewijania działają tak samo przy każdym źródle wejścia, bo AppKit dostarcza je przez łańcuch responderów; śledzenie hover, oparte na mouseMoved(with:) i NSTrackingArea, dotyczy tylko wskaźnika, bo ekran dotykowy nie ma hover).1
Przesunięcia palcem (swipe) nie są tłumaczone: „AppKit doesn’t translate touch swipes for you.”1 Jeśli używa się trackSwipeEvent(options:dampenAmountThresholdMin:max:usingHandler:), trzeba zaimplementować własną obsługę, a przy nawigacji wstecz i do przodu najpierw sprawdzić NSEvent.isTouchSwipeNavigationEnabled, „because the person can turn off swipe navigation in System Settings” (ponieważ użytkownik może wyłączyć nawigację przesunięciem w Ustawieniach systemowych).1 Ta właściwość jest nowa: dokumentacja Apple podaje ją przy macOS 27.0, a NSEvent.h w SDK macOS 27.0 deklaruje ją w wierszu 496 jako API_AVAILABLE(macos(27)) (analiza autora).67
Własne recognizery: kontrakt, limit czasu i dwa klucze
Wytyczne artykułu dla własnej podklasy NSGestureRecognizer to kontrakt w siedmiu punktach: obsłużyć anulowanie w mouseCancelled(with:) i touchesCancelled(with:); w metodach zdarzeń jedynie aktualizować prywatny stan śledzenia i ustawiać state; reagować w metodzie akcji, a nie w wywołaniu zwrotnym zdarzenia; zapamiętać źródłowe NSEvent lub NSTouch dla metody akcji; „Override reset() to clear your cached state, so it doesn’t leak from one gesture into the next” (nadpisać reset(), aby wyczyścić zapamiętany stan i nie przeciekał on z jednego gestu do następnego); „Always end an active gesture recognizer by transitioning to NSGestureRecognizer.State.ended, NSGestureRecognizer.State.failed, or NSGestureRecognizer.State.cancelled. Otherwise, other recognizers can’t activate” (zawsze kończyć aktywny recognizer przejściem do stanu końcowego, inaczej inne recognizery nie mogą się aktywować); oraz nadpisać location(in:), ponieważ „If you skip this override and build your app with the macOS 27 SDK and later, AppKit returns NSZeroPoint and logs a console error”.1 Przykład DragTrackingRecognizer, który następuje dalej, jest szkieletem: treści jego nadpisań mouse-down, dragged i up to komentarze nakazujące wywołać jedną prywatną metodę track(_:event:state:), treść nadpisania anulowania każe ustawić .cancelled, a metoda akcji odczytuje z powrotem zapamiętane położenie i zdarzenie.1
Konsekwencje złamania kontraktu ilościowo opisuje dopiero drugi artykuł. „A gesture recognizer is stuck if it hasn’t reached a terminal state within 2 seconds after the person stops interacting with your app, such as when they release the mouse button or lift their fingers” (recognizer jest zablokowany, jeśli nie osiągnie stanu końcowego w ciągu 2 sekund od zakończenia interakcji z aplikacją, np. po zwolnieniu przycisku myszy lub podniesieniu palców).2 Przy widokach wyłącznych „A stuck gesture recognizer keeps holding its exclusive view”, co blokuje zbieranie poza jego hierarchią, a systemowe lekarstwo działa automatycznie: „To keep a single stuck gesture recognizer from blocking your app indefinitely, the system automatically transitions it to the NSGestureRecognizer.State.cancelled state, allowing gesture gathering to resume normally” (aby pojedynczy zablokowany recognizer nie blokował aplikacji bez końca, system automatycznie przenosi go do stanu NSGestureRecognizer.State.cancelled, dzięki czemu zbieranie gestów normalnie się wznawia).2 To mechanizm stojący za czwartym wierszem tabeli i oznacza on, że wadliwy recognizer w wydanej aplikacji blokuje zbieranie gestów poza swoją wyłączną hierarchią, przez co reszta aplikacji wydaje się nie reagować, aż do automatycznego anulowania dwie sekundy po zakończeniu interakcji, przy czym cel akcji nadal odpowiada za obsługę tego anulowania.
Dwa klucze user defaults sprawiają, że podczas pracy nad aplikacją te problemy kończą się awarią ze stosem wywołań: jeden przy brakującym nadpisaniu, drugi w miejsce cichego anulowania. „Set the NSGestureRecognizerCrashOnMissingOverrides key in UserDefaults to YES” albo przekazać -NSGestureRecognizerCrashOnMissingOverrides YES jako argument uruchomienia, aby wywołać awarię, gdy brakuje metody zdarzenia, którą należało nadpisać; oraz ustawić NSCrashOnStuckGestureTimeout „to a small number of seconds, such as 2” albo przekazać -NSCrashOnStuckGestureTimeout 2, aby zamiast anulowania zablokowanego recognizera nastąpiła awaria.2 Pierwszy nie wyłapuje wszystkiego: „doesn’t detect a gesture recognizer that correctly transitions to NSGestureRecognizer.State.cancelled and sends its action, if the action’s target doesn’t handle the canceled state” (nie wykrywa recognizera, który poprawnie przechodzi do anulowania i wysyła akcję, jeśli cel akcji nie obsługuje stanu anulowania).2 Ostrzeżenie, które następuje, należy do tych, które trafiają na listę kontrolną: „Comment out or remove NSGestureRecognizerCrashOnMissingOverrides and NSCrashOnStuckGestureTimeout before shipping your app” oraz „Changes to these user defaults or launch arguments take effect only after you rebuild or relaunch your app” (przed wydaniem należy zakomentować lub usunąć oba klucze, a zmiany tych kluczy lub argumentów uruchomienia działają dopiero po przebudowaniu lub ponownym uruchomieniu aplikacji).2 Żaden z tych kluczy nie pojawia się w talkach ani w czerwcowej nocie technicznej.
Ten sam artykuł wyjaśnia różnicę między dwoma stanami końcowymi, które nie oznaczają sukcesu: .failed jest dla recognizera, który nigdy nie dopasował gestu, a „If a gesture recognizer sets this after it already delivered an action, the system automatically converts it to NSGestureRecognizer.State.cancelled instead” (jeśli recognizer ustawi go po dostarczeniu akcji, system zamienia go na anulowanie).2 Wymienia też przyczynę anulowania przez system wykraczającą poza przykład z Exposé z talku: „when the same touches begin a system-level gesture like smartMagnify(with:), the system takes over those touches for its own use” (gdy te same dotknięcia rozpoczynają gest systemowy, system przejmuje je na własny użytek).2
Trzy mniejsze fakty z tej samej strony. Recognizery mogą opóźniać zdarzenia responderów: „If you set delaysPrimaryMouseButtonEvents to true, the system stores the relevant events until the gesture recognizer fails, then delivers them to the appropriate responders. If the gesture recognizer reaches the NSGestureRecognizer.State.ended state, the system never delivers the events.”2 Natomiast własne metody dotykowe respondera dotyczą tylko gładzika: „These methods don’t have an equivalent delay mechanism, and they apply only to indirect touches from a trackpad.”2 Kontroler widoku nie może trzymać recognizera: „You can’t attach a gesture recognizer to a view controller directly”, więc dodaje się go do widoku kontrolera, a kontroler ustawia jako cel i delegata.2
Widoki wyłączne: domyślnie włączone, wyłączone w szablonach AppKit w Xcode
Popołudniowy wpis wyjaśniał wyłączność na podstawie talku i czerwcowej noty technicznej. Dwa artykuły dodają wartość domyślną, mechanizm i fakt dotyczący Xcode.
Mechanizm, nowy w macOS 27: „In macOS 27 and later, the initial hit-tested view for a mouse click or touch event becomes an exclusive view for gathering gesture recognizers” (w macOS 27 i nowszych pierwszy widok trafiony przez kliknięcie lub dotyk staje się widokiem wyłącznym do zbierania recognizerów) oraz „This behavior simulates the presence of a single mouse pointer, even when a person touches a Mac with a touch display with several fingers at once”.2 Wartość domyślna: „Exclusive views are on by default” oraz „If your app’s information property list doesn’t include the NSViewGestureRecognizerIsExclusive key, the system treats it as YES”.2 Strona samego klucza się zgadza, „The default value is YES”, i wąsko ujmuje rezygnację: „Set this key to NO to allow gesture recognizers in multiple views to activate simultaneously. Do this only if your app intentionally needs concurrent gesture recognition across different views” (wartość NO pozwala recognizerom w wielu widokach aktywować się jednocześnie; należy ją ustawiać tylko wtedy, gdy aplikacja celowo potrzebuje jednoczesnego rozpoznawania gestów w różnych widokach).6 Systemowym przykładem rezygnacji dla pojedynczego widoku jest Color Panel: „AppKit uses this same technique in the standard Color Panel”, gdzie kilka suwaków porusza się jednocześnie.2
Fakt dotyczący Xcode, podany w obu artykułach: „Xcode adds the NSViewGestureRecognizerIsExclusive key with a value of NO to new macOS app projects, so those apps don’t use exclusive views unless you change the value to YES” (Xcode dodaje klucz z wartością NO do nowych projektów aplikacji na macOS, więc te aplikacje nie używają widoków wyłącznych, dopóki nie zmieni się wartości na YES).1 Przeczytałem szablony projektów na tym Macu, żeby sprawdzić, których projektów to dotyczy (analiza autora). W Xcode 27.1 (27A9275) i w becie Xcode 27.2 (27B5019j) szablon macOS App dziedziczy po jednostce „Cocoa App Base”, której opcja cyklu życia aplikacji ma dwie wartości, SwiftUI i Cocoa, przy czym domyślna jest SwiftUI; klucz INFOPLIST_KEY_NSViewGestureRecognizerIsExclusive z wartością NO znajduje się wyłącznie w jednostce Cocoa, więc Xcode zapisuje go, jako ustawienie kompilacji trafiające do generowanego Info.plist, gdy projekt używa cyklu życia z app delegate AppKit, a nie gdy używa cyklu życia SwiftUI.7 Opcja cyklu życia nie ma w szablonach nazwy wyświetlanej; tę ma opcja Interface: (SwiftUI, Storyboard lub XIB, domyślnie SwiftUI), a jednostki Storyboard i XIB wymagają cyklu życia AppKit, więc projekt Storyboard lub XIB dostaje klucz, a projekt z interfejsem SwiftUI dostaje go tylko wtedy, gdy jego cyklem życia jest app delegate AppKit.7 Szablon Document App wywodzi się z tej samej bazy, więc pod tym samym warunkiem dziedziczy to ustawienie kompilacji i dodatkowo zapisuje klucz jako wpis Info.plist z wartością false; szablon macOS Game wymusza cykl życia Cocoa, więc nowa gra na macOS startuje bez wyłączności.7 Na tym Macu nie ma Xcode starszego niż 27.1, więc nie mogę powiedzieć, kiedy szablony zyskały ten klucz. Praktyczny wniosek: projekt AppKit utworzony z tych szablonów startuje bez wyłączności; projekt SwiftUI, a także każda istniejąca aplikacja, której Info.plist nie ma klucza, używa widoków wyłącznych. Z tego wynika uwaga migracyjna drugiego artykułu: jeśli aplikacja ustawiła klucz na NO, przy zastępowaniu pętli śledzenia należy „also set the view’s exclusiveGestureBehavior to NSView.ExclusiveGestureBehavior.exclusive”, ponieważ „This prevents simultaneous gesture recognition with unrelated views, which the loop previously prevented implicitly” (zapobiega to jednoczesnemu rozpoznawaniu gestów z niepowiązanymi widokami, czemu pętla wcześniej zapobiegała niejawnie).2
Pierwszy artykuł rozplątuje trzy ustawienia dotyczące wyłączności i przewijania zdaniem „These settings don’t override each other” (te ustawienia nie nadpisują się nawzajem).1 exclusiveGestureBehavior i klucz plist decydują, które recognizery AppKit zbiera, a „The view’s setting applies to that view and its subviews, and takes precedence over the app-wide key” (ustawienie widoku obejmuje go wraz z podwidokami i ma pierwszeństwo przed kluczem całej aplikacji).1 isCancellableByScrollGesture działa na poziomie recognizera i niezależnie: „It lets a scroll gesture cancel that gesture recognizer even if it has already begun.”1 scrollGestureForRelationships służy recognizerom, które konkurują z recognizerami samego widoku przewijania.1 Scenariusz, na który artykuł każe uważać, to przycisk wewnątrz widoku przewijania: należy ustawić isCancellableByScrollGesture na true w jego recognizerze, aby przeciągnięcie zaczęte na przycisku przewijało zamiast klikać.1
Silniki tekstu dostają jeszcze jedno narzędzie. NSTextSelectionManager „creates its gesture recognizers itself, so you don’t have a reference to them” (sam tworzy swoje recognizery, więc nie ma się do nich referencji), dlatego własny recognizer, który musi na nie czekać, zwraca true z gestureRecognizer(_:shouldRequireFailureOf:), gdy gesturesForFailureRequirements menedżera zawiera ten drugi recognizer.1 Ta właściwość figuruje przy macOS 27.0 i jest zadeklarowana w NSTextSelectionManager.h w wierszu 59 SDK macOS 27.0 (analiza autora).67
Emulacja w pięciu punktach
Popołudniowy wpis cytował cztery reguły emulacji z czerwcowej noty technicznej dla frameworków UI spoza AppKit. Lista w artykule ma pięć punktów, a nowym jest przeciąganie.18
- „Tapping emulates a mouse-down followed immediately by a mouse-up.” (dotknięcie emuluje naciśnięcie przycisku myszy, po którym od razu następuje jego zwolnienie)
- „Touching and then immediately panning emulates a trackpad scroll, even if the UI doesn’t otherwise handle Multi-Touch input directly.” (dotknięcie z natychmiastowym przesunięciem emuluje przewijanie gładzikiem, nawet jeśli UI nie obsługuje bezpośrednio wejścia Multi-Touch)
- „Touching, briefly holding, and then moving emulates mouseDown(with:), followed by mouseDragged(with:) as it moves, and mouseUp(with:) on lift.” (dotknięcie, krótkie przytrzymanie i ruch emulują
mouseDown(with:), a następniemouseDragged(with:)podczas ruchu imouseUp(with:)po podniesieniu palca) - „Touching and holding without movement emulates rightMouseDown(with:), and rightMouseUp(with:) on lift.” (dotknięcie i przytrzymanie bez ruchu emuluje
rightMouseDown(with:), a po podniesieniu palcarightMouseUp(with:)) - „Pinching or rotating with two fingers emulates the trackpad magnify and rotate gestures.” (szczypanie lub obrót dwoma palcami emuluje gesty powiększania i obrotu na gładziku)
Trzeci punkt to pisemna postać „drag with a small hold delay” (przeciągania z krótkim opóźnieniem przytrzymania) z talku (11:31): dotknięcie, które od razu się przesuwa, przewija, a dotknięcie, które najpierw się zatrzymuje, przeciąga.9 Lista czerwcowa miała dotknięcie, natychmiastowe przesunięcie, długie przytrzymanie oraz szczypanie lub obrót dwoma palcami, a punktu o przeciąganiu nie miała.8 Granicą są dwa palce: „This emulation covers interactions with one or two fingers. If another finger touches down during a pinch or rotation, the emulation ignores it and keeps tracking the original two fingers” (emulacja obejmuje jeden lub dwa palce; kolejny palec podczas szczypania lub obrotu jest ignorowany, a emulacja dalej śledzi pierwotne dwa palce).1 Trzy palce lub więcej wymagają recognizerów, a NSIsTouchNative ustawione na YES wyłącza dodatkową emulację, gdy framework sam obsługuje dotyk.1
Gry: TN3216
Nota techniczna dotyczy gier: „This guidance focuses on configurations common in games, such as an app with a single view backed by a CAMetalLayer.”3 Jej ujęcie projektowe jest surowsze niż w HIG: „Touch extends the Mac experience rather than replacing other input” (dotyk rozszerza doświadczenie Maca, a nie zastępuje innych sposobów wprowadzania) oraz „Touch works best for brief interactions that complement these devices”, z poleceniem „Design touch interactions for brief moments that the player can complete with one hand, while the other hand stays on the keyboard, mouse, or controller” (projektować interakcje dotykowe na krótkie chwile, które gracz wykona jedną ręką, podczas gdy druga zostaje na klawiaturze, myszy lub kontrolerze), dla interakcji „such as inventory management, map zoom and scroll, or the settings menu”.3
Kwestia zgodności to ta sama emulacja z odmianą dla gier. „AppKit applies game-specific emulation to apps in a games category, and can apply emulation to an individual view, such as one that hosts a third-party UI framework.”3 Kategoria jest odczytywana z Info.plist: jeśli LSApplicationCategoryType to public.app-category.games lub podkategoria gier, „the system applies game-specific mouse emulation”.3 Problem, który to rozwiązuje, to taki, którego mysz nigdy nie ma: „A mouse moves continuously from one position to the next, but a player can tap or start a drag at two distant locations in succession” (mysz porusza się w sposób ciągły, a gracz może dotknąć lub zacząć przeciąganie w dwóch odległych miejscach jedno po drugim), więc gra, która sumuje przesunięcia, dryfuje, a „For games, AppKit sends a mouseMoved event to the new location before each mouseDown(with:), so your game can refresh its cached mouse position”.3
Następnie nota wymienia emulowane zdarzenia w kolejności. Dla dotknięcia: mouseMoved(with:) „from the last mouse position to the tap location”, potem mouseDown(with:), potem mouseUp(with:). Dla przeciągnięcia: mouseMoved(with:) do miejsca, w którym przeciągnięcie się zaczyna, mouseDown(with:), mouseDragged(with:) „repeatedly while the touch continues” i mouseUp(with:).3 Potem reguła w ramce „Important”: „Don’t intercept events by overriding sendEvent(_:) or pulling them with nextEvent(matching:until:inMode:dequeue:). Doing so can interfere with gesture recognizers that respond to a physical mouse” (nie przechwytywać zdarzeń przez nadpisanie sendEvent(_:) ani przez ich pobieranie, bo może to zakłócić recognizery reagujące na fizyczną mysz).3
Częścią bez odpowiednika w talkach jest GCMouse. „By default, the system presents each touch surface, whether a Mac with a touch display or a Sidecar-connected iPad, as an additional GCMouse that emulates mouse input from touches” (domyślnie system przedstawia każdą powierzchnię dotykową, Maca z ekranem dotykowym lub iPada podłączonego przez Sidecar, jako dodatkową GCMouse, która emuluje wejście myszy z dotknięć): palec przyciśnięty wciska lewy przycisk, ruch wywołuje procedurę obsługi ruchu myszy z przesunięciem, a podniesienie palca zwalnia przycisk.3 Ograniczenia: „The emulated GCMouse ignores additional fingers, and it doesn’t emulate other buttons, scrolling, or pinch input”, a ponieważ dotknięcia lądują w niepowiązanych miejscach, „Don’t use them to track an absolute pointer position” (nie używać ich do śledzenia bezwzględnego położenia wskaźnika).3
NSIsTouchNative robi dla gry więcej niż dla aplikacji AppKit. Ustawione na YES sprawia, że „AppKit’s emulation will stick to a generic emulation - ignoring the app’s category or whether it uses a third-party UI framework - and disable GCMouse emulation” (emulacja AppKit pozostaje ogólna, niezależnie od kategorii aplikacji, a emulacja GCMouse zostaje wyłączona), więc powierzchnia dotykowa nie pojawia się już jako GCMouse, a „Your game receives touch through gesture recognizers instead of emulated mouse events”.3 Pozostaje jedna pozostałość: „AppKit still emulates mouse events for any NSView that handles them, for example by overriding mouseDown:.”3 Strona samego klucza mówi, że większość aplikacji AppKit go nie potrzebuje i że jest „primarily intended for apps that embed a non-AppKit rendering or input framework and implement their own touch-handling pipeline” (przeznaczony głównie dla aplikacji z własnym frameworkiem renderowania lub wejścia i własnym potokiem obsługi dotyku); silnik gry to właśnie ten przypadek.6
Sekcja implementacyjna to wzorzec delegata w Swift i Objective-C: recognizer przesunięcia i recognizer dotknięcia z buttonMask 0, aby ignorowały fizyczną mysz, recognizer powiększenia, shouldRecognizeSimultaneouslyWith zwracające true tylko dla pary przesunięcie i szczypanie oraz shouldRequireFailureOf, które każe dotknięciu czekać na oba.3 Uwaga potwierdza wartość domyślną podaną w pierwszym artykule: „The gesture recognizers must include direct in allowedTouchTypes. On macOS 27 and later, this is the default.”3 Rada dotycząca routingu jest skrojona pod silniki: „Install the gesture recognizers once in your app and leave them enabled” (zainstalować recognizery raz i zostawić je włączone), zamieniać wywołania zwrotne na zdarzenia wejścia silnika i „Let the active input handler decide if and how to react to different gestures”, z procedurą obsługi przesunięcia, która przelicza punkty na piksele współczynnikiem skali bufora i odwraca oś y dla silnika, którego początek układu jest w lewym górnym rogu.3 Standardowe kontrolki działają po przełączeniu nadal, „as long as you don’t attach your own direct-touch gesture recognizers to them” (o ile nie dołączy się do nich własnych recognizerów bezpośredniego dotyku).3
Końcowa lista kontrolna ma dwie połowy. Dla gry korzystającej z emulacji: odczytywać położenie z każdego zdarzenia i nie nadpisywać sendEvent: ani nie pobierać zdarzeń z nextEventMatchingMask:. Dla gry obsługującej dotyk: ustawić NSIsTouchNative, używać recognizerów bezpośredniego dotyku z buttonMask 0, dodawać dotyk tam, gdzie gracze się go spodziewają, i utrzymać go w obsłudze jedną ręką, „Continue to read GCMouse for the physical mouse. With NSIsTouchNative set, touch no longer appears as an emulated GCMouse” (nadal odczytywać GCMouse dla fizycznej myszy, bo dotyk nie pojawia się już jako emulowana GCMouse), a tam, gdzie to możliwe, używać standardowych kontrolek.3
HIG z datą 28 października
Popołudniowy wpis mówił, że cztery strony HIG, które strona docelowa planowała podlinkować, nie zawierają żadnych wskazówek dotyczących dotyku na ekranie Maca. Trzy z nich teraz je zawierają, każda z wierszem dziennika zmian z datą 28 października 2026; strona o Live Activities się nie zmieniła.4
Designing for macOS („Added guidance for touch input.”) przepisuje trzy wiersze opisu platformy i dodaje jedną dobrą praktykę. Ergonomia: „On a Mac with a touch display, people reach forward to touch the screen, so they tend to use touch for brief, intentional interactions rather than sustained work” (na Macu z ekranem dotykowym sięga się do przodu, by dotknąć ekranu, więc dotyk służy krótkim, celowym interakcjom, a nie długotrwałej pracy). Sposoby wprowadzania zaczynają się teraz od klawiatur i urządzeń wskazujących, a wśród pozostałych wymieniają „Gestures on a trackpad or a Mac with a touch display”. Nowa dobra praktyka to cały talk projektowy w jednym zdaniu: „Support standard gestures on trackpads and Mac devices with a touch display. Use larger tap targets and controls that expand on touch to make touch interaction easier, while keeping control size and density the same to stay optimized for keyboard and pointing device input” (obsługiwać standardowe gesty na gładzikach i Macach z ekranem dotykowym; stosować większe obszary dotknięcia i kontrolki rozszerzające się przy dotyku, aby ułatwić interakcję dotykową, zachowując ten sam rozmiar i gęstość kontrolek, by pozostały zoptymalizowane pod klawiaturę i urządzenia wskazujące).4
Gestures („Added guidance for gestures and touch input in macOS.”) rozszerza sekcję o macOS, która teraz linkuje oba artykuły AppKit jako wskazówki dla programistów. Pierwsze zdanie ustala rangę: „On a Mac with a touch display, or on an iPad connected with Sidecar, people can also interact directly using touch gestures as a secondary input mode” (na Macu z ekranem dotykowym lub na iPadzie podłączonym przez Sidecar można też wchodzić w interakcję bezpośrednio, gestami dotykowymi, jako drugorzędnym trybem wprowadzania).4 Potem sześć reguł, każda z pogrubionym początkiem:
- „Keep the layout and size of interface elements the same when you support touch.” (zachować układ i rozmiar elementów interfejsu) Uzasadnienie: „In macOS, touch is secondary to keyboard and pointing device input”, a ludzie często przełączają się między sposobami wprowadzania. Kontrast z iPhone Duo, gdzie wewnętrzny ekran zmienia klasy rozmiaru, jest tu sednem: na Macu zmienia się sposób wprowadzania, a układ się nie zmienia.
- „Make tap targets bigger, not the controls themselves” (powiększać obszary dotknięcia, nie same kontrolki), przez „enlarging their tap targets to anticipate that interaction may start slightly outside of their visual bounds”, oraz „Where possible, use standard framework controls, which the system configures with an optimal tap target size automatically” (standardowe kontrolki dostają optymalny obszar dotknięcia automatycznie).
- „Consider providing expandable controls” (rozważyć kontrolki rozszerzalne), czyli tymczasowy wariant w rozmiarze dotykowym, który pojawia się po dotknięciu, trwa podczas przytrzymania i zostaje „for a short time after they lift their finger to keep the control accessible to touch”; „When using standard system controls, steppers, sliders, and dials gain this behavior automatically.”
- „Don’t use touch as the only way to access controls or features” (dotyk nie może być jedyną drogą do kontrolek i funkcji), a także odwrotnie: „aim to provide touch equivalents for actions otherwise performed exclusively with a pointing device, like using a keyboard modifier to shift-drag or displaying a menu when the pointer moves over an element” (zapewnić dotykowe odpowiedniki akcji wykonywanych dotąd wyłącznie urządzeniem wskazującym, takich jak przeciąganie z klawiszem Shift czy wyświetlanie menu po najechaniu wskaźnikiem na element).
- „Where possible, align with gestures people already know” (opierać się na znanych gestach), z aplikacji na iPhone’a lub iPada albo z gładzika, oraz „Test to ensure they don’t conflict with macOS system gestures.”
- „Make sure custom gestures work for both touch and trackpad” (własne gesty mają działać i z dotykiem, i z gładzikiem), z użyciem „modifier keys where needed to help the gesture work for all touch inputs”.4
Strona nie podaje wartości 36 punktów; ta liczba jest w dwóch talkach („Touch on macOS uses a touch target of 36 points”, 9:17 w talku projektowym, oraz rozszerzenie prostokąta trafienia w talku o AppKit do „a minimum of 36 points on each axis”, 5:53).9 Dodaje za to tabelę „macOS trackpad and touch display gestures”, która każdej akcji przypisuje jej postać na gładziku i dotykową: zaznaczenie to kliknięcie główne lub dotknięcie; otwarcie to dwukrotne kliknięcie lub dwukrotne dotknięcie; przewijanie to dwa palce na gładziku i jeden na ekranie; powiększanie i obrót to to samo szczypanie i obrót dwoma palcami na obu; menu kontekstowe to kliknięcie pomocnicze lub dotknięcie z przytrzymaniem; przeciąganie to kliknięcie i przeciągnięcie albo „Touch and hold, then drag”.4 W tabeli międzyplatformowej „Touch (or pinch) and hold” wymienia teraz macOS, a wiersz dwukrotnego dotknięcia dodaje „open an item on a Mac with a touch display”.4
Pointing devices („Added guidance for revealing hidden controls with touch on Mac, and updated guidance and artwork for iPadOS and macOS.”) to strona z najbardziej konkretnymi regułami, pod nowym nagłówkiem „Touch support for hidden controls”. Założenie: „Some Mac apps reveal certain controls only when the pointer moves over them. These controls need another way to appear when someone uses touch on a Mac that supports it” (niektóre kontrolki pojawiają się tylko pod wskaźnikiem i przy dotyku potrzebują innego sposobu, by się pokazać).4 Reguły:
- „Decide which hidden controls need touch support. Reserve touch support for essential controls.” (zdecydować, które ukryte kontrolki potrzebują obsługi dotyku, i zarezerwować ją dla kontrolek niezbędnych) Podpowiedzi się nie kwalifikują, podobnie jak wszystko, co dotyk już robi w inny sposób: „someone can swipe to scroll a carousel, so there’s no need to reveal the navigation buttons that exist for mice without a scroll wheel”. Oraz: „Avoid keeping hidden controls visible all the time just to accommodate touch.” (nie pokazywać ukrytych kontrolek na stałe tylko ze względu na dotyk)
- „Reveal a hidden control when someone taps its general location on a touch display.” (pokazać ukrytą kontrolkę po dotknięciu jej okolicy) Tylko te istotne, a „Hide a control again when the person finishes with it, or after a few seconds if they don’t use it” (ukryć ją ponownie po skończeniu albo po kilku sekundach bez użycia).
- „Retain the current selection when revealing a hidden control” (zachować bieżące zaznaczenie), ponieważ kontrolka może działać na zaznaczeniu.
- „Make essential hidden controls available in a context menu” (udostępnić niezbędne ukryte kontrolki w menu kontekstowym), które dotyk otwiera dotknięciem z przytrzymaniem.
- „In a sidebar, reveal hidden controls when someone taps the corresponding section header or row.” (na pasku bocznym pokazywać ukryte kontrolki po dotknięciu nagłówka sekcji lub wiersza) Przykładem jest Finder: „tapping the Favorites header in the Finder sidebar collapses the section and reveals a disclosure button”. Pokazane kontrolki zostają, dopóki dotyk nie opuści paska bocznego albo nie zostanie użyte urządzenie wskazujące, a dotknięcie obszaru ukrytej kontrolki pokazuje ją bez aktywowania: „The control acts only when they tap it again.” (kontrolka działa dopiero po ponownym dotknięciu)
- „In a tabbed window, reveal the selected tab’s close button when someone taps anywhere in the window” (w oknie z kartami pokazać przycisk zamknięcia wybranej karty po dotknięciu w dowolnym miejscu okna) i zachować go, dopóki użytkownik nie wróci do urządzenia wskazującego.4
Tych sześć reguł to pisemna postać wskazówek z obu talków dotyczących hover, z czasami i wyjątkami, których talki nie podały: kilka sekund, zanim pokazana kontrolka się ukryje, drugie dotknięcie do aktywacji oraz paski boczne i przyciski zamykania kart jako przypadki, które pozostają widoczne. Ta sama wersja przepisała materiał strony o wskaźniku w iPadOS pod kątem Liquid Glass, zmieniła w tekście domyślny kształt wskaźnika z koła na strzałkę, zastąpiła kolumnę tabeli kursorów dotyczącą tylko AppKit symbolami AppKit i SwiftUI oraz usunęła sekcję o przyciąganiu wskaźnika; nic z tego nie dotyczy ekranu Maca.4
Czego nadal brak
Sprawdzone 28 października o 21:51 czasu pacyficznego w endpoincie dokumentacji Apple, na stronie docelowej, w kanałach aktualności i wydań dla programistów oraz na liście Tech Talków.56
- Bety Xcode 27.3. Kafelek na stronie docelowej nadal ma status „Coming soon”, kanał wydań nie wymienia niczego nowszego niż wydania cząstkowe z 26 października (wśród nich macOS 27.2), a strony informacji o wydaniu Xcode 27.3 i macOS 27.3 zwracają 404. Najnowszym Xcode pozostaje 27.2 RC, którego SDK nie sprawdzałem; zastrzeżenie popołudniowego wpisu dotyczące deployment target nadal obowiązuje, a wpis o wydaniu Xcode 27 zawiera tło na temat deployment targetów.
- Trzech przełączników gestów systemowych, które zademonstrowano w talku o AppKit:
NSWindow.disablesDirectTouchSystemGestures,UIViewController.prefersDirectTouchSystemGesturesDisabledidirectTouchSystemGesturesDisabled()ze SwiftUI. Wszystkie trzy zwracają 404 i żaden z nowych artykułów o nich nie wspomina. Artykuły opisują, jak gest systemowy anuluje recognizer; nie opisują wyłączania gestu systemowego. - ActivityKit dostępnego na macOS. Strona frameworku ma znacznik z 8 października i nie wymienia platformy macOS, a
AlertConfiguration.announcement(_:)z talku o Live Activities zwraca 404. Sekcja popołudniowego wpisu o Live Activities pozostaje bez zmian, a wpis o maszynie stanów nadal jest wyłącznie implementacją na iOS. - „gesture modernization skill”, o której talk o AppKit wspomniał w 12:16. Żadna strona Apple jej nie wymienia; komentarz
TK skillna stronie docelowej nadal tam jest.59 - Linku ze strony talku do artykułu oraz jakiejkolwiek wypowiedzi Apple o ścieżce przez symulator lub Device Hub dla dotyku na Macu.
- Datowanego wpisu na stronie aktualizacji dokumentacji SwiftUI. Jej znacznik serwera to 14:58 czasu pacyficznego 28 października, ale najnowsza sekcja to nadal wrzesień 2026, a wpis o gestach to czerwcowy wiersz o
GestureInputKindsz 2026 roku; cokolwiek się zmieniło, nie była to nowa datowana sekcja.6
Korekty wpisu dla programistów
Popołudniowy wpis był zgodny ze sprawdzeniami z południa; pięć jego stwierdzeń przestało być aktualnych przy sprawdzeniu 28 października o 21:51 czasu pacyficznego. Blok aktualizacji w tamtym wpisie, opublikowany razem z tym, linkuje tutaj, a stwierdzenia są poprawione na miejscu; oto lista.
- Z trzech kafelków „Coming soon” na stronie docelowej został jeden. Zasoby dokumentacji i zasoby projektowe są dostępne; beta Xcode 27.3 nie jest.5
- „Adopting gesture recognizers in your app” nie zwraca już 404. Jest dostępny i to z niego należy czytać wskazówki dotyczące migracji.1
- Strony HIG nie milczą już o dotyku na ekranie Maca. Trzy z czterech mają wiersze dziennika zmian z 28 października; strona o Live Activities nie ma.4
- Lista emulacji ma pięć reguł, a nie cztery jak w czerwcowej nocie technicznej; dodatkiem jest reguła przytrzymania, a potem przeciągnięcia.18
- Widoki wyłączne są domyślnie włączone tylko w aplikacjach, których Info.plist nie zawiera klucza. Szablony macOS App i Document App w Xcode, w Xcode 27.1 i becie 27.2, zapisują klucz jako
NOdla projektu, którego cyklem życia aplikacji jest app delegate AppKit.127
To, co popołudniowy wpis mówił o SDK, trzech nieudokumentowanych przełącznikach i ActivityKit, pozostaje niezmienione przez wszystko, co opublikowano od tamtej pory.
Najważniejsze wnioski
Dla programistów AppKit: przeprowadzić czterowierszowy test z artykułu na Sidecar, a potem wyszukać pięć wymienionych w nim symboli; nadpisanie mouseDown(with:), mouseDragged(with:) lub mouseUp(with:) w podklasie kontrolki cofa tę kontrolkę do pętli śledzenia niezależnie od tego, czy wywołuje super, a poprawką są pary target-action na NSControl.Events z .trackingEndedOutside w zbiorze ukrywania.1
Dla pętli, którą się zachowuje: recognizer tylko dla dotyku potrzebuje buttonMask 0, inaczej przeciągnięcie myszą uruchamia recognizer, a mouseDown(with:) nigdy się nie wykonuje; allowedTouchTypes = .direct jest już wartością domyślną.1
Dla własnych recognizerów: osiągać stan końcowy, nadpisać reset() i location(in:) oraz debugować z -NSGestureRecognizerCrashOnMissingOverrides YES i -NSCrashOnStuckGestureTimeout 2, usuniętymi przed wydaniem; w środowisku produkcyjnym zablokowany recognizer jest anulowany dwie sekundy po zakończeniu interakcji.12
Dla wyłączności: aplikacja bez klucza używa widoków wyłącznych; projekt utworzony z szablonu macOS App w Xcode z cyklem życia app delegate AppKit (każdy projekt Storyboard lub XIB) ich nie używa, a projekt z domyślnym cyklem życia SwiftUI używa. Przed migracją pętli śledzenia należy przeczytać swój Info.plist.27
Dla gier: odczytywać położenie ze zdarzeń zamiast sumować przesunięcia, nie nadpisywać sendEvent(_:) i pamiętać, że każda powierzchnia dotykowa jest dodatkową emulowaną GCMouse, dopóki NSIsTouchNative nie ma wartości YES.3
Dla projektantów: dotyk to „a secondary input mode” (drugorzędny tryb wprowadzania); zachować układ i rozmiar kontrolek; powiększać obszary dotknięcia, nie kontrolki; pokazywać ukryte kontrolki po dotknięciu w ich pobliżu i ukrywać je, gdy użytkownik skończy albo po kilku sekundach bez użycia, z paskami bocznymi i przyciskiem zamknięcia karty jako wyjątkami, a te niezbędne udostępnić w menu kontekstowym.4
FAQ
Czy artykuł AppKit o migracji jest już dostępny?
Tak. „Adopting gesture recognizers in your app” zwracał 404 w południe czasu pacyficznego 28 października, a o 21:51 był dostępny, wymieniony w sekcjach Essentials i Gestures AppKit i podlinkowany z kafelka dokumentacji na stronie docelowej.15
Czy w mojej aplikacji widoki wyłączne są włączone?
Jeśli Info.plist nie ma klucza NSViewGestureRecognizerIsExclusive, to tak: „If your app’s information property list doesn’t include the NSViewGestureRecognizerIsExclusive key, the system treats it as YES” (bez klucza system przyjmuje wartość YES).2 Projekt utworzony z szablonu macOS App w Xcode 27.1 lub becie 27.2 z cyklem życia app delegate AppKit (każdy projekt Storyboard lub XIB) dostaje klucz jako ustawienie kompilacji z wartością NO; ten sam szablon z domyślnym cyklem życia SwiftUI nie zapisuje klucza (analiza autora na podstawie szablonów na tym Macu).7
Co się dzieje z gesture recognizerem, który nigdy nie kończy?
Zatrzymuje swój widok wyłączny, dopóki system go nie anuluje: recognizer „is stuck if it hasn’t reached a terminal state within 2 seconds after the person stops interacting with your app” (jest zablokowany, jeśli nie osiągnie stanu końcowego w ciągu 2 sekund od zakończenia interakcji), a system „automatically transitions it to the NSGestureRecognizer.State.cancelled state”.2 Podczas debugowania można ustawić klucz NSCrashOnStuckGestureTimeout, aby zamiast tego dostać stos wywołań.2
Dlaczego moja pętla śledzenia przestaje dostawać przeciągnięcia po dodaniu recognizera przesunięcia?
Ponieważ buttonMask recognizera ma wartość domyślną i przejmuje główny przycisk myszy: „A mouse drag then begins the gesture recognizer, and AppKit doesn’t call your mouseDown(with:) override, so the tracking loop doesn’t run” (przeciągnięcie myszą uruchamia recognizer, nadpisanie mouseDown(with:) nie jest wywoływane i pętla się nie wykonuje). Dla recognizera tylko dla dotyku należy ustawić buttonMask na 0.1
Jak gra otrzymuje dotyk na Macu?
Jako emulowane zdarzenia myszy, w udokumentowanej kolejności, z mouseMoved(with:) do nowego położenia przed każdym mouseDown(with:), oraz jako dodatkową GCMouse, której przesunięcia nie są ciągłe. Ustawienie NSIsTouchNative na YES przełącza grę na gesture recognizery i usuwa emulowaną GCMouse.3
Czy HIG podaje rozmiar obszaru dotyku dla Maca?
Nie. Strona Gestures mówi „Make tap targets bigger, not the controls themselves” (powiększać obszary dotknięcia, nie same kontrolki) i w kwestii rozmiaru odsyła do kontrolek frameworku; wartość 36 punktów jest w dwóch talkach, a nie w HIG.49
Skąd pochodzą fakty w tym wpisie?
Z dwóch artykułów AppKit Apple, z TN3216, z trzech stron HIG i ze strony docelowej, wszystkich pobranych 28 października 2026 o 21:51 czasu pacyficznego, przy czym dostępność artykułu, strony HIG i stronę docelową porównałem ze sprawdzeniami z południa i z 11:49 tego samego dnia; z JSON dokumentacji Apple dla każdego wymienionego symbolu; z czerwcowej noty technicznej dla porównania; ze ścieżek napisów Tech Talków dla cytatów z talków; oraz z nagłówków i szablonów projektów wewnątrz Xcode 27.1 i bety Xcode 27.2 na moim Macu, co jest analizą autora. Doniesienia prasowe nie są źródłem.
Źródła
-
Dokumentacja Apple dla programistów, Adopting gesture recognizers in your app, AppKit. Pobrane 28 października 2026 o 21:51 czasu pacyficznego (HTTP 200,
Last-Modified: Wed, 28 Oct 2026 21:30:45 GMT); ten sam URL i jego endpoint JSON zwracały 404 w południe czasu pacyficznego tego samego dnia. Wymieniony w sekcji Essentials strony frameworku AppKit, jako drugi po „Adopting Liquid Glass”, oraz pod „Gesture handling” na stronie Gestures. Pierwszy zapis w Wayback Machine to 2026-10-29 01:08:12 UTC (wiersz CDX20261029010812, status 200; surowy nagłówek i potwierdzenia CDX są zapisane wraz z materiałem dowodowym wpisu). Cytowane sekcje: Overview, „Prepare your app for a Mac with a touch display” (tabela, czterowierszowy test, lista wyszukiwania), „Filter gesture recognizer candidates”, „Handle touch input”, „Update your event-handling code”, „Replace common mouse-down overrides”, „Handle tracking-loop compatibility”, „Replace your custom tracking loops with gesture recognizers”, „Add touch support to your custom tracking loop”, „Write a custom gesture recognizer”, „Account for gesture recognizer behavior changes” oraz „Support non-AppKit UI frameworks”. Listing Swift w tym wpisie to czterowierszowe skrócenie listingu z sekcji „Add touch support to your custom tracking loop”. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Dokumentacja Apple dla programistów, Handling AppKit gestures, AppKit. Pobrane 28 października 2026 o 21:51 czasu pacyficznego (
Last-Modified: Wed, 28 Oct 2026 05:35:41 GMT); brak historii wersji na stronie; brak linku z TN3212; brak zapisu w indeksie Wayback Machine na tamtą chwilę. Cytowane sekcje: Overview, „Track gesture recognizer states”, „Understand gesture recognizer gathering”, „Implement a custom gesture recognizer”, „Prioritize gesture recognizers”, „Delay responder events”, „Configure exclusive views”, „Replace event-tracking loops” oraz „Diagnose unresponsive gesture recognizers”. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Dokumentacja Apple dla programistów, TN3216: Supporting touch input for games on macOS, historia wersji „2026-10-28 First published”, pobrane 28 października 2026 o 21:51 czasu pacyficznego (
Last-Modified: Wed, 28 Oct 2026 21:09:50 GMT). Cytowane sekcje: Overview, „Extend the player experience meaningfully”, „Understand automatic touch support in existing games”, „Understand GCMouse emulation”, „Opt out of mouse emulation”, „Implement touch events”, „Route gestures in your game engine”, „Use standard AppKit controls for automatic touch support” oraz „Review your touch integration”. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple Developer, Human Interface Guidelines, pobrane 28 października 2026 o 21:51 czasu pacyficznego i porównane z kopiami pobranymi w południe czasu pacyficznego tego samego dnia: Designing for macOS, dziennik zmian „October 28, 2026 | Added guidance for touch input.”; Gestures, dziennik zmian „October 28, 2026 | Added guidance for gestures and touch input in macOS.”, z sekcją platformy macOS i tabelą „macOS trackpad and touch display gestures”; Pointing devices, dziennik zmian „October 28, 2026 | Added guidance for revealing hidden controls with touch on Mac, and updated guidance and artwork for iPadOS and macOS.”, z sekcją „Touch support for hidden controls”; Live Activities, identyczna bajt w bajt z kopią z południa, dziennik zmian kończący się na 13 października 2026. Kopie z południa trzech pierwszych stron nie zawierają żadnego z cytowanych fragmentów. Nagłówek
Last-Modifiedserwera dla nowego JSON strony Gestures ma wartośćTue, 27 Oct 2026 05:11:02 GMT, zX-Cache: hit-staleprzy pierwszym żądaniu o 21:59. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple Developer, Get ready for Touch on Mac, pobrane 28 października 2026 o 11:49 czasu pacyficznego (trzy kafelki ze statusem „Coming soon”, linki w komentarzach HTML) oraz o 21:51 czasu pacyficznego: „Xcode 27.3 beta” / „Download the latest SDKs to build, run, and test your app.” / „Coming soon”; „Documentation resources” / „Learn how to build and optimize your app for touch input.” z linkami do artykułu i przykładu; „Design resources” / „Create experiences that take advantage of touch user interface patterns.” z linkami do Gestures, Designing for macOS, Pointing devices i Apple Design Resources, których lista ramek produktów obejmuje „MacBook Pro PROLED XDR Display”; komentarz
TK skillobecny w obu pobraniach. Latest News RSS, najnowsza pozycja „Say hello to Touch on Mac”, Wed, 28 Oct 2026 10:00:55 PDT; Releases RSS, najnowsze pozycje z datą Mon, 26 Oct 2026 10:00:00 PDT (iOS, iPadOS, macOS 27.2 (26B123), tvOS, visionOS i watchOS 27.2); lista Tech Talks z 111456, 111467 i 111468 i bez żadnego innego talku o dotyku ani o Live Activities; strona Bring Touch to your Mac app, bez linku do któregokolwiek z artykułów; wszystko pobrane o 21:51 czasu pacyficznego. ↩↩↩↩↩↩↩↩↩ -
JSON dokumentacji Apple (
developer.apple.com/tutorials/data/documentation/<path>.json), pobrane 28 października 2026 o 21:51 czasu pacyficznego. Znalezione:trackingEndedOutside(macOS 11.0),NSEvent.isTouchSwipeNavigationEnabled(macOS 27.0),NSTextSelectionManager.gesturesForFailureRequirements(macOS 27.0),gestureRecognizer(_:shouldRequireFailureOf:)w NSGestureRecognizerDelegate (macOS 10.10),NSViewGestureRecognizerIsExclusive(„The default value isYES.”),NSIsTouchNative, Mouse, Keyboard, and Trackpad (dwa zdania o łańcuchu responderów i dotyku), Supporting drag and drop through file promises (zdanie o bezpośrednim dotyku) oraz SwiftUI updates (Last-Modified: Wed, 28 Oct 2026 21:58:06 GMT, najnowsza sekcja „September 2026”). Nieznalezione (HTTP 404):appkit/nswindow/disablesdirecttouchsystemgestures,uikit/uiviewcontroller/prefersdirecttouchsystemgesturesdisabled,swiftui/view/directtouchsystemgesturesdisabled(),activitykit/alertconfiguration/announcement(_:),xcode-release-notes/xcode-27_3-release-notes,macos-release-notes/macos-27_3-release-notes. ActivityKit:Last-Modified: Thu, 08 Oct 2026 15:31:35 GMT, platformy iOS, iPadOS i Mac Catalyst. ↩↩↩↩↩↩↩↩↩↩↩ -
Sprawdzenie SDK i szablonów 28 października 2026 (analiza autora), w
MacOSX27.0.sdkwewnątrz Xcode 27.1 (27A9275):NSControl.hw wierszu 180 zawieraNSControlEventTrackingEndedOutside = 1 << 7, z tym samym wierszem wMacOSX27.2.sdkwewnątrz bety Xcode 27.2 (27B5019j);NSEvent.hw wierszu 496 zawiera@property (class, readonly, getter=isTouchSwipeNavigationEnabled) BOOL touchSwipeNavigationEnabled API_AVAILABLE(macos(27));;NSTextSelectionManager.hw wierszu 59 deklarujegesturesForFailureRequirements;NSGestureRecognizer.hw wierszach 116 i 149 deklarujeshouldRequireFailureOfGestureRecognizer:. Szablony projektów, odczytane przez plistlib, w Xcode 27.1 (27A9275) i becie Xcode 27.2 (27B5019j), wPlatforms/MacOSX.platform/Developer/Library/Xcode/Templates/Project Templates/macOS/Application/:Cocoa App Base.xctemplate/TemplateInfo.plistzawiera kluczINFOPLIST_KEY_NSViewGestureRecognizerIsExclusivez wartością tekstowąNOwOptions[0] (appLifecycle) / Units / Cocoa / Targets[0] / SharedSettingsi nigdzie indziej;macOS App Lifecycle.xctemplate, wProject Templates/macOS/Base/, definiuje opcjęappLifecyclebezName, zTypepopup,DefaultSwiftUIiValuesSwiftUI,Cocoa;macOS Application UI.xctemplatedefiniujeuserInterfacezNameInterface:,DefaultSwiftUIiValuesSwiftUI,Storyboard,XIB, a jego jednostkiStoryboardiXIB, podobnie jak jedna z jednostekSwiftUI, mająRequiredOptionszappLifecycleCocoa;App.xctemplatewywodzi się zCocoa App BaseprzezCore Data App,macOS SwiftUI App,macOS Application UIiCocoa App Native Base;Cocoa Document App.xctemplatezapisuje węzełInfo.plist:NSViewGestureRecognizerIsExclusive, którego definicja to<key>NSViewGestureRecognizerIsExclusive</key><false/>, wewnątrz swojej jednostki cyklu życiaCocoa;Game.xctemplateustawiaappLifecyclenaOverrideCocoa;Document App.xctemplatewywodzi się zarówno zCocoa Document App, jak i zCocoa App Base(przezCore Data Document App,macOS SwiftUI Document-Based App,macOS Application UIiCocoa App Native Base). Tylko te dwa pliki szablonów zawierają klucz w którymkolwiek z obu Xcode. Ustawienia kompilacjiINFOPLIST_KEY_w Xcode są zapisywane do generowanego Info.plist. ↩↩↩↩↩↩↩↩↩↩ -
Dokumentacja Apple dla programistów, TN3212: Adopting gesture recognizers for Sidecar touch support, historia wersji „2026-06-08 First published”, sekcja „Support third-party UI frameworks”, cztery punkty: „A tap emulates a mouse-down followed immediately by a mouse-up”, „A touch and immediate pan emulates trackpad scroll events, even if the UI doesn’t respond to them”, „A long press without movement emulates a rightMouseDown(with:) (optionally followed by rightMouseDragged(with:)) with a rightMouseUp(with:) on touch lift” oraz „A two-finger pinch or rotation simultaneously emulates both the trackpad magnify and rotate gestures”. Pobrane 28 października 2026. ↩↩↩
-
Apple Developer, Tech Talki Bring Touch to your Mac app (13:19, Raleigh Ledet) i Design for touch on Mac (16:52, Cas Lemmens), 28 października 2026. Cytaty pochodzą z angielskich ścieżek napisów; czas w nawiasie to pierwszy znacznik cytowanego fragmentu: „drag with a small hold delay” (111468, 11:31), „there’s a gesture modernization skill that has an understanding of the features and changes I’ve talked about” (111468, 12:16), „Touch on macOS uses a touch target of 36 points” (111456, 9:17), „a minimum of 36 points on each axis” (111468, 5:53). Listing kodu ze strony talku „Use NSControl events to detect tracking” (2:24) rejestruje
addTarget(_:action:for:)dla[.trackingBegan, .trackingEntered],[.trackingExited, .trackingEndedInside]oraz.trackingCancelled. ↩↩↩↩↩