Touch on Mac, la doc : migration, technote jeux, HIG

Qu’a publié Apple pour Touch on Mac après les Tech Talks ? Le soir du 28 octobre, l’article AppKit « Adopting gesture recognizers in your app », qui renvoyait une erreur 404 à midi, était en ligne avec un tableau de quatre lignes qui donne une étape suivante à chaque type de code de gestion de la souris ; un second article, « Handling AppKit gestures », documente les vues exclusives, un délai de deux secondes pour les reconnaisseurs bloqués et deux réglages de débogage ; une nouvelle technote, TN3216, traite des jeux et de l’ordre des événements souris émulés ; et trois pages des Human Interface Guidelines portent des entrées de journal des modifications datées du 28 octobre sur le tactile sur le Mac.1234 La vignette « Documentation resources » de la page d’accueil renvoie désormais vers l’article et l’exemple de file promises, sa vignette « Design resources » vers les trois pages HIG, et la vignette de la bêta de Xcode 27.3 indique toujours « Coming soon ».5

TL;DR : L’article pour les développeurs, publié le 28 octobre à 13 h 47 PT, s’appuyait sur trois Tech Talks et une technote de juin ; l’article AppKit dont il avait besoin renvoyait une erreur 404 à midi. À 21 h 51, cet article était en ligne, accompagné de deux autres documents. L’article de migration classe votre code en quatre lignes : les contrôles standard et les reconnaisseurs n’ont besoin de rien ; les surcharges de mouseDown(with:) et les boucles de suivi bénéficient d’une émulation à un doigt et devraient devenir des reconnaisseurs ; une sous-classe de contrôle qui surcharge une méthode de la souris retombe sur une boucle de suivi ; un reconnaisseur personnalisé qui ne termine jamais garde sa vue exclusive.1 La règle derrière la troisième ligne est qu’AppKit décide selon que la méthode est surchargée ou non, « not on what the override does » (et non selon ce que fait la surcharge), si bien qu’appeler super n’y change rien.1 Un NSPanGestureRecognizer réservé au toucher, avec buttonMask à 0, à côté d’une boucle que vous conservez est désormais documenté, avec la conséquence précise de laisser le masque tel quel : un glisser à la souris déclenche le reconnaisseur et votre mouseDown(with:) ne s’exécute jamais.1 Les vues exclusives sont activées par défaut quand la clé Info.plist est absente, et le modèle macOS App de Xcode n’écrit la clé avec la valeur NO que pour un projet dont le cycle de vie est l’app delegate AppKit, pas pour le cycle de vie SwiftUI par défaut ; je l’ai lu dans les modèles inclus dans Xcode 27.1 et dans la bêta 27.2 sur ce Mac.27 Un reconnaisseur bloqué est annulé deux secondes après la fin de l’interaction et, pendant le débogage, un réglage utilisateur provoque un plantage en cas de surcharge manquante, un autre remplace l’annulation silencieuse par un plantage.2 La technote pour les jeux énumère les événements souris exacts que produisent un toucher et un glisser, demande aux moteurs de ne pas additionner les deltas et explique que chaque surface tactile apparaît comme un GCMouse émulé supplémentaire tant que NSIsTouchNative n’est pas défini.3 Les HIG qualifient le tactile de « a secondary input mode » (un mode d’entrée secondaire), disent de rendre les « tap targets bigger, not the controls themselves » (les cibles tactiles plus grandes, pas les contrôles eux-mêmes) et ajoutent six règles pour les contrôles qui n’apparaissaient qu’au survol.4 Toujours absents : la bêta de Xcode 27.3 et les trois commutateurs de gestes système, qui renvoient une erreur 404 à 21 h 51 PT.56

Ce qui est arrivé, et quand

Chaque élément ci-dessous porte l’heure à laquelle je l’ai vérifié, car les horodatages du serveur ne concordent pas avec ce que les pages affichaient pendant la journée ; l’article en direct garde la trace de la journée, avec une ligne tardive pour cette publication.

  • L’article AppKit. « Adopting gesture recognizers in your app » renvoyait une erreur 404 depuis le point d’accès de la documentation d’Apple à midi PT ; à 21 h 51, il était en ligne, servi avec un horodatage de modification de 14 h 30 PT, répertorié dans la section Essentials d’AppKit, après « Adopting Liquid Glass », et sous « Gesture handling » sur la page Gestures du framework, et la première capture de la Wayback Machine date de 18 h 08 PT (justificatifs dans les sources).1 La page du Tech Talk « Bring Touch to your Mac app » ne renvoie toujours pas vers lui.5
  • Un second article. « Handling AppKit gestures » se trouve à côté sur la page Gestures. Il n’a pas d’historique des révisions, la technote de juin ne renvoie pas vers lui, la Wayback Machine n’en a aucune capture, et son horodatage serveur indique 22 h 35 PT le 27 octobre ; il ne faisait pas partie des pages que j’ai vérifiées à midi, je ne peux donc pas dire quand il a été mis en ligne.2
  • Une nouvelle technote. « TN3216: Supporting touch input for games on macOS » a un historique des révisions d’une ligne, « 2026-10-28 First published », et un horodatage serveur de 14 h 09 PT.3
  • Trois pages des HIG. « Designing for macOS », « Gestures » et « Pointing devices » se terminent désormais par des lignes de journal des modifications datées du 28 octobre 2026. Les copies que j’ai récupérées à midi ne contenaient aucun des nouveaux textes ; celles récupérées à 21 h 51 les contiennent. L’en-tête de modification du serveur sur la nouvelle page « Gestures » indique 5 h 11 UTC le 27 octobre, soit avant la copie de midi qui ne contenait pas le texte ; je rapporte les deux faits et aucune minute de publication.4
  • La page d’accueil. À 11 h 49, les trois vignettes sous « Tools and documentation » indiquaient toutes « Coming soon », leurs liens conservés dans des commentaires HTML. À 21 h 51, la vignette « Documentation resources » (« Learn how to build and optimize your app for touch input. ») renvoie vers l’article et l’exemple de file promises, et la vignette « Design resources » (« Create experiences that take advantage of touch user interface patterns. ») renvoie vers les trois pages HIG et vers les Apple Design Resources, dont la liste de contours d’appareils inclut « MacBook Pro PROLED XDR Display ». La vignette « Xcode 27.3 beta » indique toujours « Coming soon », et le commentaire HTML TK skill figure toujours dans la page.5
  • L’exemple. La page « Supporting drag and drop through file promises » dit désormais de ses reconnaisseurs que « The recognizers work the same way with a pointer or with direct touches on a Mac with a touch display » (les reconnaisseurs fonctionnent de la même façon avec un pointeur ou avec des touchers directs sur un Mac à écran tactile).6
  • Rien d’autre n’a bougé. L’élément le plus récent du flux d’actualités développeurs est toujours « Say hello to Touch on Mac », à 10 h 00 min 55 s PDT ; les éléments les plus récents du flux des sorties sont les versions intermédiaires du 26 octobre, sans bêta de Xcode 27.3 ni de macOS 27.3 ; la liste des Tech Talks contient les trois mêmes Tech Talks ; la page ActivityKit porte un horodatage du 8 octobre et ne répertorie toujours aucune plateforme macOS.56

L’article de l’après-midi disait que les règles écrites se limitaient aux Tech Talks et à la technote de juin. Cet article porte sur ce qu’ajoutent les règles écrites, et il corrige l’article de l’après-midi là où la vérification du soir a trouvé des informations différentes ; la liste figure à la fin.

Le tableau qui trie votre code

La section « Prepare your app for a Mac with a touch display » de l’article est un tableau à trois colonnes, gestion des entrées, comportement au toucher et étape suivante, et quatre lignes. La colonne de comportement d’Apple est citée ; les deux autres colonnes sont condensées, sauf l’étape suivante de la troisième ligne, qui reprend la formulation d’Apple :1

Votre code « Behavior with touch » selon Apple Étape suivante
Contrôles AppKit standard et reconnaisseurs de gestes « Works with touch, with no changes needed. » Tester.
Surcharges de mouseDown(with:) et boucles de suivi « AppKit emulates a single-finger tap or drag, but supports multi-finger gestures and individual touches only through gesture recognizers. » Les remplacer par des reconnaisseurs.
Sous-classes de NSButton, NSSlider et d’autres vues AppKit qui surchargent des méthodes de gestion de la souris « AppKit falls back to a tracking loop. » « Replace the overrides with target-action pairs for tracking events. »
Sous-classes personnalisées de NSGestureRecognizer « A recognizer that never reaches a terminal state keeps its exclusive view, which can make your app appear unresponsive. » Gérer l’annulation et atteindre un état terminal.

Le test qui suit le tableau tient en quatre lignes, à exécuter tel quel : « 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 » et « Small targets, such as resize grips and grab handles, are easy to hit with a finger » (les contrôles répondent au toucher et les boutons s’allument et s’activent comme prévu ; faire glisser une vue, sélectionner du texte et faire défiler fonctionnent à un doigt ; le pincement et la rotation fonctionnent là où l’app les prend en charge ; les petites cibles, comme les poignées de redimensionnement et de saisie, se touchent facilement du doigt).1 La liste de recherche étend l’audit des surcharges de mouseDown(with:) du Tech Talk à cinq symboles : « 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 La dernière vérification est celle dont traite la quatrième ligne : « confirm that your app stays responsive after a touch interaction ends, and that every gesture recognizer action method handles the NSGestureRecognizer.State.cancelled state » (vérifier que l’app reste réactive après la fin d’une interaction tactile et que chaque méthode d’action de reconnaisseur de gestes gère l’état annulé).1

Deux phrases de l’Overview tranchent une question que le Tech Talk laissait à l’interprétation. L’émulation se fait à un seul doigt : « AppKit emulates mouse events from a single finger tapping or dragging », et « Relying on this emulation, however, means you miss out on multi-finger gestures, like pinch and rotate, that gesture recognizers handle » (s’en remettre à cette émulation prive toutefois des gestes à plusieurs doigts, comme le pincement et la rotation, que gèrent les reconnaisseurs).1 Et le banc d’essai est celui que nommait le Tech Talk, désormais dans un conseil : « Use Sidecar to test touch input without a Mac that has a touch display », car « AppKit then routes touch on its screen the same way it does on a Mac with a touch display ».1

La règle des surcharges

La troisième ligne du tableau repose sur une règle que ni le Tech Talk ni la technote de juin n’énonçaient aussi clairement. Si vous sous-classez NSButton et surchargez mouseDown(with:) pour ajouter une animation de surbrillance, « AppKit treats your override as a replacement for its own click handling, rather than as a side effect » (AppKit traite votre surcharge comme un remplacement de sa propre gestion du clic, et non comme un effet secondaire), et le bouton repasse à une boucle de suivi.1 Puis : « AppKit decides based on whether your subclass overrides the method, not on what the override does », si bien que le repli s’applique même quand la surcharge appelle super.mouseDown(with:), et « Overriding mouseDragged(with:) or mouseUp(with:) has the same effect ».1

Le remplacement est le modèle des événements de contrôle présenté dans le Tech Talk, et le listing de l’article diffère de celui du Tech Talk sur un détail qui mérite d’être copié. Le listing du Tech Talk à 2:24 enregistrait une action pour [.trackingExited, .trackingEndedInside] et une autre pour .trackingCancelled ; l’ensemble de masquage de l’article est [.trackingExited, .trackingEndedInside, .trackingEndedOutside, .trackingCancelled], qui ajoute un appui se terminant hors du bouton.19 trackingEndedOutside n’est pas nouveau : la documentation d’Apple le répertorie en macOS 11.0, et le SDK macOS 27.0 le déclare sous la forme NSControlEventTrackingEndedOutside = 1 << 7 dans NSControl.h (analyse de l’auteur).67 L’article précise aussi que les méthodes d’action doivent être exposées à Objective-C avec @objc, et que si vous n’avez besoin que de l’activation, primaryActionTriggered se déclenche « regardless of whether someone used a mouse, trackpad, or touch to produce it » (que l’utilisateur ait utilisé une souris, un trackpad ou le toucher).1

Les vues conteneurs présentent le même piège : « 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 », et l’issue passe par les méthodes de glisser de la source de données et du délégué, tableView(_:pasteboardWriterForRow:) pour un tableau.1

Pour les deux surcharges de mouseDown(with:) les plus courantes, l’article donne des paires avant et après. « 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 » (la plupart de ces surcharges marquent un élément comme sélectionné au clic ou affichent un menu au Contrôle-clic).1 Pour la sélection, observez plutôt isSelected sur l’élément de la collection view ou sur la vue de ligne du tableau, avec cette réserve que l’observateur « also runs when your code changes the selection programmatically » (s’exécute aussi quand votre code modifie la sélection par programmation) ; pour le menu, surchargez menu(for:) et laissez AppKit le présenter, ou définissez menu ou defaultMenu quand le menu ne dépend pas du clic.1

Un reconnaisseur réservé au toucher à côté d’une boucle que vous conservez

Le Tech Talk recommandait un reconnaisseur de panoramique réservé au toucher, à côté d’une boucle de suivi existante, comme voie pour une version mineure. L’article documente la même recette sous « Add touch support to your custom tracking loop », avec les fonctions partagées de début, de poursuite, de fin et d’annulation, puis indique ce que fait chaque propriété avec une précision que le Tech Talk n’avait pas le temps d’atteindre :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 » (avec buttonMask à 0, le reconnaisseur ne reconnaît que les touchers, pas la souris ni le trackpad, et votre boucle de suivi continue de gérer la souris et le trackpad exactement comme avant).1 Laissez le masque à sa valeur par défaut et le reconnaisseur « 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 » (répond aussi au bouton principal de la souris : un glisser à la souris déclenche alors le reconnaisseur, AppKit n’appelle pas votre surcharge et la boucle ne s’exécute pas, tandis qu’un clic sans mouvement atteint toujours la boucle).1 Le masque n’est donc pas un raffinement : sans lui, la boucle cesse sans bruit de recevoir les glissers. La ligne allowedTouchTypes relève de la double précaution : « Direct touches are already the default for allowedTouchTypes, and setting it to .direct states that intent explicitly. »1 L’appréciation d’Apple elle-même sur cette approche : « 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 » (c’est souvent le moyen le plus rapide et le moins risqué d’ajouter le tactile à du code dont vous préférez ne pas réécrire la boucle d’un coup).1

Si vous remplacez plutôt la boucle, l’exemple de panoramique de l’article ramène la légende à son origine de départ sur .cancelled, et une boucle qui lançait une session de glisser passe le reconnaisseur à beginDraggingSession(items:gesture:source:) au lieu de l’événement.1

Filtrer avec les deux méthodes de délégué

La section la plus nouvelle par rapport aux Tech Talks est « Filter gesture recognizer candidates ». Les reconnaisseurs sont rassemblés par hit-testing, pas par la chaîne de répondeurs : « Gesture recognizers don’t use the responder chain. »1 Quand AppKit les rassemble, « not every recognizer it finds is actually applicable to the interaction » (tous ceux qu’il trouve ne s’appliquent pas forcément à l’interaction), et l’app écarte ceux qui ne s’appliquent pas grâce à deux méthodes de délégué : gestureRecognizer(_:shouldAttemptToRecognizeWith:) « to inspect the initiating NSEvent for pointer-based input » (pour examiner le NSEvent initial d’une entrée par pointeur), et gestureRecognizer(_:shouldReceive:) « to inspect each new NSTouch for touch input » (pour examiner chaque nouveau NSTouch d’une entrée tactile).1 L’intérêt des deux : « 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 » (chacune vous donne l’entrée initiale, que le reconnaisseur n’expose pas autrement, et vous permet de l’empêcher de traiter ce flux d’événements ou ce toucher).1 Et la séparation est stricte : « Direct touches don’t call this method. To filter them, implement gestureRecognizer(_:shouldReceive:) instead, as shown above. »1 Filtrer uniquement par la méthode des événements laisse les touchers directs non filtrés par ce délégué.

Les deux exemples sont des modèles que je m’attends à voir recopiés. Une poignée de redimensionnement : attachez le reconnaisseur de redimensionnement à toute la vue et, dans les deux méthodes de délégué, renvoyez si l’emplacement se trouve dans le cadre de la poignée, en renvoyant true « only to give the gesture recognizer a chance to attempt recognition, not to guarantee it succeeds » (seulement pour donner au reconnaisseur une chance de tenter la reconnaissance, pas pour garantir sa réussite).1 Une timeline de clips : un seul reconnaisseur sur la timeline et, dans la méthode des événements, un hit-test du clip, conservé pour la méthode d’action, avec false renvoyé quand il n’y en a pas.1 L’avertissement qui l’accompagne pour les petites cibles : « If a custom control expects a touch to land inside its visible area, update it to handle touches that land outside that area » (si un contrôle personnalisé s’attend à ce qu’un toucher tombe dans sa zone visible, adaptez-le aux touchers qui tombent en dehors).1

La priorité entre les reconnaisseurs restants passe par une exigence d’échec, et l’article reprend l’avertissement du second article, « Use this sparingly, because the waiting recognizer responds more slowly » (à utiliser avec parcimonie, car le reconnaisseur qui attend répond plus lentement), avec l’exemple selon lequel « making a single click wait for a double click to fail makes clicks feel sluggish » (faire attendre un clic simple l’échec d’un double clic rend les clics poussifs).1

currentEvent, survol et balayages

« Handle touch input » reformule sous forme de documentation la phrase qui organise le Tech Talk : AppKit « doesn’t deliver direct touches (NSTouch) through the responder chain or through event monitors », et « 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 » (les reconnaisseurs de gestes sont le seul moyen de répondre aux gestes à plusieurs doigts ou de recevoir des touchers individuels d’un Mac à écran tactile ou d’un iPad connecté par Sidecar).1 La page de présentation des entrées d’AppKit dit la même chose du côté des répondeurs : « 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 » (la chaîne de répondeurs ne reçoit des événements tactiles que des touchers indirects sur un trackpad, si une vue les demande, et jamais des touchers directs sur un Mac à écran tactile).6

Le conseil sur les vérifications de capacité est de les éviter : « 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 » (dans la plupart des cas, au lieu de vérifier si un appareil prend en charge le tactile, écrivez un code de gestion des événements qui fonctionne de la même façon quelle que soit la source d’entrée, et choisissez une autre présentation selon le type d’interaction).1 Le seul exemple donné pour touchCapabilities est « capturing a signature on a touch display » (la capture d’une signature sur un écran tactile).1

currentEvent a sa propre section. Comme les interactions avec les contrôles passent par des reconnaisseurs, « currentEvent no longer reliably returns the event that initiated an action » (ne renvoie plus de façon fiable l’événement à l’origine d’une action), et la paire avant et après remplace les indicateurs de modification de l’événement par NSEvent.modifierFlags au niveau de la classe, avec cette réserve : « 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 » (ces valeurs reflètent l’état au moment de la requête, pas au moment de l’événement d’origine, et un toucher n’apparaît pas dans pressedMouseButtons).1 Ce qui ne change pas : « 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 » (les événements du clavier et de la molette se comportent de la même façon quelle que soit la source, AppKit les livrant par la chaîne de répondeurs ; le suivi du survol, qui repose sur mouseMoved(with:) et NSTrackingArea, ne concerne que le pointeur, car un écran tactile n’a pas de survol).1

Les balayages ne sont pas traduits : « AppKit doesn’t translate touch swipes for you. »1 Si vous utilisez trackSwipeEvent(options:dampenAmountThresholdMin:max:usingHandler:), implémentez les vôtres et, pour la navigation avant et arrière, vérifiez d’abord NSEvent.isTouchSwipeNavigationEnabled, « because the person can turn off swipe navigation in System Settings » (car l’utilisateur peut désactiver la navigation par balayage dans les Réglages Système).1 Cette propriété est nouvelle : la documentation d’Apple la répertorie en macOS 27.0, et NSEvent.h dans le SDK macOS 27.0 la déclare à la ligne 496 en API_AVAILABLE(macos(27)) (analyse de l’auteur).67

Reconnaisseurs personnalisés : le contrat, le délai et deux réglages

Les consignes de l’article pour une sous-classe personnalisée de NSGestureRecognizer forment un contrat en sept points : gérer l’annulation dans mouseCancelled(with:) et touchesCancelled(with:) ; dans les méthodes d’événement, se contenter de mettre à jour un état de suivi privé et de définir state ; réagir dans la méthode d’action, pas dans le rappel d’événement ; mettre en cache le NSEvent ou le NSTouch d’origine pour la méthode d’action ; « Override reset() to clear your cached state, so it doesn’t leak from one gesture into the next » (surcharger reset() pour vider l’état en cache, afin qu’il ne déborde pas d’un geste sur le suivant) ; « 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 » (toujours terminer un reconnaisseur actif par un état terminé, échoué ou annulé, faute de quoi les autres ne peuvent pas s’activer) ; et surcharger location(in:), car « If you skip this override and build your app with the macOS 27 SDK and later, AppKit returns NSZeroPoint and logs a console error » (sans cette surcharge, avec le SDK macOS 27 ou ultérieur, AppKit renvoie NSZeroPoint et consigne une erreur dans la console).1 L’exemple DragTrackingRecognizer qui suit est un squelette : le corps de ses surcharges mouse-down, dragged et up se réduit à des commentaires qui vous disent d’appeler une méthode privée track(_:event:state:), celui de la surcharge d’annulation à définir .cancelled, et la méthode d’action relit l’emplacement et l’événement mis en cache.1

C’est dans le second article que les conséquences d’une rupture du contrat sont chiffrées. « 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 » (un reconnaisseur est bloqué s’il n’a pas atteint un état terminal dans les 2 secondes qui suivent la fin de l’interaction, par exemple quand l’utilisateur relâche le bouton de la souris ou lève les doigts).2 Avec les vues exclusives, « A stuck gesture recognizer keeps holding its exclusive view », ce qui bloque le rassemblement en dehors de sa hiérarchie, et le remède du système est automatique : « 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 » (pour qu’un seul reconnaisseur bloqué ne bloque pas l’app indéfiniment, le système le fait passer automatiquement à l’état annulé, et le rassemblement des gestes reprend normalement).2 C’est le mécanisme derrière la quatrième ligne du tableau : dans une app livrée, un reconnaisseur défaillant bloque le rassemblement des gestes en dehors de sa hiérarchie exclusive, le reste de l’app semblant ne plus répondre, jusqu’à l’annulation automatique deux secondes après la fin de l’interaction, la cible de l’action restant chargée de gérer l’annulation.

Deux réglages utilisateur font planter ces problèmes avec une trace de pile pendant le développement, l’un pour une surcharge manquante, l’autre à la place de l’annulation silencieuse. « Set the NSGestureRecognizerCrashOnMissingOverrides key in UserDefaults to YES », ou passez -NSGestureRecognizerCrashOnMissingOverrides YES comme argument de lancement, pour provoquer un plantage quand une méthode d’événement que vous auriez dû surcharger manque ; et réglez NSCrashOnStuckGestureTimeout « to a small number of seconds, such as 2 » (sur quelques secondes, par exemple 2), ou passez -NSCrashOnStuckGestureTimeout 2, pour provoquer un plantage au lieu d’annuler un reconnaisseur bloqué.2 Le premier ne détecte pas tout : il « 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 » (ne détecte pas un reconnaisseur qui passe correctement à l’état annulé et envoie son action, si la cible de l’action ne gère pas cet état).2 L’avertissement qui suit a sa place dans une liste de contrôle : « Comment out or remove NSGestureRecognizerCrashOnMissingOverrides and NSCrashOnStuckGestureTimeout before shipping your app » (à commenter ou supprimer avant de livrer l’app), et « Changes to these user defaults or launch arguments take effect only after you rebuild or relaunch your app » (les modifications ne prennent effet qu’après une recompilation ou un relancement).2 Aucun de ces réglages n’apparaît dans les Tech Talks ni dans la technote de juin.

Le même article explique la différence entre les deux états terminaux qui ne sont pas une réussite : .failed concerne un reconnaisseur qui n’a jamais reconnu son geste, et « If a gesture recognizer sets this after it already delivered an action, the system automatically converts it to NSGestureRecognizer.State.cancelled instead » (si un reconnaisseur le définit après avoir déjà envoyé une action, le système le convertit automatiquement en état annulé).2 Il nomme aussi une cause d’annulation par le système au-delà de l’exemple d’Exposé donné dans le Tech Talk : « when the same touches begin a system-level gesture like smartMagnify(with:), the system takes over those touches for its own use » (quand les mêmes touchers déclenchent un geste système comme smartMagnify(with:), le système s’en empare).2

Trois faits plus modestes tirés de la même page. Les reconnaisseurs peuvent retarder les événements des répondeurs : « 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 » (avec delaysPrimaryMouseButtonEvents à true, le système conserve les événements jusqu’à l’échec du reconnaisseur puis les livre aux répondeurs, et ne les livre jamais si le reconnaisseur aboutit).2 Et les méthodes tactiles du répondeur lui-même sont réservées au trackpad : « These methods don’t have an equivalent delay mechanism, and they apply only to indirect touches from a trackpad. »2 Un view controller ne peut pas porter de reconnaisseur : « You can’t attach a gesture recognizer to a view controller directly », ajoutez-le donc à la vue du contrôleur, avec le contrôleur comme cible et comme délégué.2

Vues exclusives : activées par défaut, désactivées dans les modèles AppKit de Xcode

L’article de l’après-midi expliquait l’exclusivité à partir du Tech Talk et de la technote de juin. Les deux articles ajoutent la valeur par défaut, le mécanisme et un fait concernant Xcode.

Le mécanisme, nouveau dans 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 » (à partir de macOS 27, la première vue touchée par le hit-test d’un clic ou d’un toucher devient une vue exclusive pour le rassemblement des reconnaisseurs), et « 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 » (ce comportement simule la présence d’un seul pointeur, même quand l’utilisateur touche l’écran de plusieurs doigts à la fois).2 La valeur par défaut : « Exclusive views are on by default », et « If your app’s information property list doesn’t include the NSViewGestureRecognizerIsExclusive key, the system treats it as YES ».2 La page de la clé elle-même concorde, « The default value is YES », et présente la désactivation de façon restrictive : « 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 » (réglez la clé sur NO pour que des reconnaisseurs de plusieurs vues puissent s’activer simultanément, et ne le faites que si l’app a délibérément besoin d’une reconnaissance simultanée dans plusieurs vues).6 L’exemple que donne le système d’une désactivation vue par vue est le Color Panel : « AppKit uses this same technique in the standard Color Panel », où plusieurs curseurs bougent en même temps.2

Le fait concernant Xcode, énoncé dans les deux articles : « 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 ajoute la clé avec la valeur NO aux nouveaux projets d’app macOS, qui n’utilisent donc pas les vues exclusives à moins de passer la valeur à YES).1 J’ai lu les modèles de projet sur ce Mac pour voir quels projets cela concerne (analyse de l’auteur). Dans Xcode 27.1 (27A9275) et dans la bêta de Xcode 27.2 (27B5019j), le modèle macOS App hérite d’une unité « Cocoa App Base » dont l’option de cycle de vie de l’app a deux valeurs, SwiftUI et Cocoa, SwiftUI étant la valeur par défaut ; la clé INFOPLIST_KEY_NSViewGestureRecognizerIsExclusive avec la valeur NO se trouve uniquement dans l’unité Cocoa, si bien que Xcode l’écrit, sous la forme d’un réglage de compilation qui aboutit dans l’Info.plist généré, quand le projet utilise le cycle de vie de l’app delegate AppKit, et non quand il utilise le cycle de vie SwiftUI.7 L’option de cycle de vie n’a pas de nom affiché dans les modèles ; celle qui en a un est Interface: (SwiftUI, Storyboard ou XIB, SwiftUI par défaut), et les unités Storyboard et XIB exigent le cycle de vie AppKit : un projet Storyboard ou XIB reçoit donc la clé, et un projet à interface SwiftUI ne la reçoit que si son cycle de vie est l’app delegate AppKit.7 Le modèle Document App descend de la même base : dans les mêmes conditions, il hérite de ce réglage de compilation et écrit aussi la clé comme entrée Info.plist avec la valeur false ; le modèle macOS Game impose le cycle de vie Cocoa, si bien qu’un nouveau jeu macOS démarre non exclusif.7 Aucune version de Xcode antérieure à 27.1 n’est installée sur ce Mac, je ne peux donc pas dire quand les modèles ont reçu la clé. La lecture pratique : un projet AppKit créé à partir de ces modèles démarre non exclusif ; un projet SwiftUI, ainsi que toute app existante dont l’Info.plist ne contient pas la clé, est exclusif. La note de migration du second article en découle : si votre app a réglé la clé sur NO, « also set the view’s exclusiveGestureBehavior to NSView.ExclusiveGestureBehavior.exclusive » (définissez aussi exclusiveGestureBehavior de la vue sur .exclusive) quand vous remplacez une boucle de suivi, car « This prevents simultaneous gesture recognition with unrelated views, which the loop previously prevented implicitly » (cela empêche la reconnaissance simultanée avec des vues sans rapport, ce que la boucle empêchait implicitement).2

Le premier article démêle les trois réglages qui touchent à l’exclusivité et au défilement, avec la phrase « These settings don’t override each other » (ces réglages ne s’annulent pas les uns les autres).1 exclusiveGestureBehavior et la clé plist décident des reconnaisseurs qu’AppKit rassemble, et « The view’s setting applies to that view and its subviews, and takes precedence over the app-wide key » (le réglage de la vue s’applique à elle et à ses sous-vues, et prime sur la clé de l’app).1 isCancellableByScrollGesture se règle par reconnaisseur et indépendamment : « It lets a scroll gesture cancel that gesture recognizer even if it has already begun » (il permet à un geste de défilement d’annuler ce reconnaisseur même s’il a déjà commencé).1 scrollGestureForRelationships concerne les reconnaisseurs qui entrent en concurrence avec ceux de la scroll view.1 Le scénario que l’article demande de surveiller est un bouton dans une scroll view : réglez isCancellableByScrollGesture sur true pour son reconnaisseur, afin qu’un glisser qui commence sur le bouton fasse défiler au lieu de cliquer.1

Les moteurs de texte reçoivent un outil de plus. NSTextSelectionManager « creates its gesture recognizers itself, so you don’t have a reference to them » (crée lui-même ses reconnaisseurs, vous n’en avez donc pas de référence), si bien qu’un reconnaisseur personnalisé qui doit les attendre renvoie true depuis gestureRecognizer(_:shouldRequireFailureOf:) quand le gesturesForFailureRequirements du gestionnaire contient l’autre reconnaisseur.1 Cette propriété est répertoriée en macOS 27.0 et déclarée dans NSTextSelectionManager.h à la ligne 59 du SDK macOS 27.0 (analyse de l’auteur).67

L’émulation, en cinq points

L’article de l’après-midi citait les quatre règles d’émulation de la technote de juin pour les frameworks d’interface non AppKit. La liste de l’article en compte cinq, et la nouvelle est le glisser.18

  • « Tapping emulates a mouse-down followed immediately by a mouse-up. » (Un toucher émule un mouse-down suivi immédiatement d’un mouse-up.)
  • « Touching and then immediately panning emulates a trackpad scroll, even if the UI doesn’t otherwise handle Multi-Touch input directly. » (Toucher puis déplacer aussitôt émule un défilement au trackpad, même si l’interface ne gère pas directement les entrées Multi-Touch.)
  • « Touching, briefly holding, and then moving emulates mouseDown(with:), followed by mouseDragged(with:) as it moves, and mouseUp(with:) on lift. » (Toucher, maintenir brièvement puis déplacer émule mouseDown(with:), suivi de mouseDragged(with:) pendant le déplacement, et de mouseUp(with:) au lever du doigt.)
  • « Touching and holding without movement emulates rightMouseDown(with:), and rightMouseUp(with:) on lift. » (Toucher et maintenir sans bouger émule rightMouseDown(with:), et rightMouseUp(with:) au lever du doigt.)
  • « Pinching or rotating with two fingers emulates the trackpad magnify and rotate gestures. » (Pincer ou faire pivoter à deux doigts émule les gestes d’agrandissement et de rotation du trackpad.)

Le troisième point est la forme écrite du « drag with a small hold delay » (glisser avec un court délai de maintien) du Tech Talk (11:31) : un toucher qui se déplace aussitôt fait défiler, un toucher qui marque d’abord un temps d’arrêt fait glisser.9 La liste de juin comportait un toucher, un panoramique immédiat, un appui long et un pincement ou une rotation à deux doigts, sans point sur le glisser.8 La limite est de deux doigts : « 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 » (cette émulation couvre les interactions à un ou deux doigts ; un doigt supplémentaire posé pendant un pincement ou une rotation est ignoré, et l’émulation continue de suivre les deux doigts d’origine).1 Trois doigts ou plus exigent des reconnaisseurs, et NSIsTouchNative réglé sur YES désactive l’émulation supplémentaire dès que le framework gère lui-même le toucher.1

Les jeux : TN3216

La technote s’adresse aux jeux : « This guidance focuses on configurations common in games, such as an app with a single view backed by a CAMetalLayer » (ces recommandations visent les configurations courantes dans les jeux, comme une app à vue unique adossée à un CAMetalLayer).3 Son cadrage de design est plus strict que celui des HIG : « Touch extends the Mac experience rather than replacing other input » (le tactile prolonge l’expérience Mac au lieu de remplacer les autres entrées), et « Touch works best for brief interactions that complement these devices », avec la consigne « 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 » (concevez des interactions brèves, réalisables d’une main pendant que l’autre reste sur le clavier, la souris ou la manette), pour des interactions « such as inventory management, map zoom and scroll, or the settings menu » (gestion de l’inventaire, zoom et défilement de la carte, menu des réglages).3

Côté compatibilité, c’est la même émulation, avec une variante propre aux jeux. « 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 » (AppKit applique une émulation propre aux jeux aux apps de la catégorie jeux, et peut l’appliquer à une vue isolée, par exemple celle qui héberge un framework d’interface tiers).3 La catégorie est lue dans l’Info.plist : si LSApplicationCategoryType vaut public.app-category.games ou une sous-catégorie de jeu, « the system applies game-specific mouse emulation ».3 Le problème résolu est celui qu’une souris n’a jamais : « 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 » (une souris se déplace en continu, alors qu’un joueur peut toucher ou commencer un glisser à deux endroits éloignés l’un après l’autre), si bien qu’un jeu qui additionne les deltas dérive, et « For games, AppKit sends a mouseMoved event to the new location before each mouseDown(with:), so your game can refresh its cached mouse position » (pour les jeux, AppKit envoie un événement mouseMoved vers le nouvel emplacement avant chaque mouseDown(with:), afin que le jeu actualise la position en cache).3

La technote énumère ensuite les événements émulés dans l’ordre. Pour un toucher : mouseMoved(with:) « from the last mouse position to the tap location » (de la dernière position de la souris à l’emplacement du toucher), puis mouseDown(with:), puis mouseUp(with:). Pour un glisser : mouseMoved(with:) jusqu’au point de départ du glisser, mouseDown(with:), mouseDragged(with:) « repeatedly while the touch continues » (de façon répétée tant que le toucher dure), et mouseUp(with:).3 Puis une règle dans un encadré « 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 » (n’interceptez pas les événements en surchargeant sendEvent(_:) ni en les tirant avec nextEvent(matching:until:inMode:dequeue:), cela peut perturber les reconnaisseurs qui répondent à une souris physique).3

La partie sans équivalent dans les Tech Talks est 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 » (par défaut, le système présente chaque surface tactile, Mac à écran tactile ou iPad connecté par Sidecar, comme un GCMouse supplémentaire qui émule la souris à partir des touchers) : un doigt posé appuie sur le bouton gauche, un mouvement appelle le gestionnaire de déplacement de la souris avec un delta, et lever le doigt relâche le bouton.3 Ses limites : « The emulated GCMouse ignores additional fingers, and it doesn’t emulate other buttons, scrolling, or pinch input » (le GCMouse émulé ignore les doigts supplémentaires et n’émule ni les autres boutons, ni le défilement, ni le pincement), et comme les touchers tombent à des endroits sans lien entre eux, « Don’t use them to track an absolute pointer position » (ne vous en servez pas pour suivre une position absolue du pointeur).3

NSIsTouchNative fait davantage pour un jeu que pour une app AppKit. Réglée sur YES, « 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 » (l’émulation d’AppKit s’en tient à une émulation générique, sans tenir compte de la catégorie de l’app ni d’un framework tiers, et désactive l’émulation GCMouse), si bien que la surface tactile n’apparaît plus comme un GCMouse et que « Your game receives touch through gesture recognizers instead of emulated mouse events » (votre jeu reçoit le toucher par des reconnaisseurs de gestes au lieu d’événements souris émulés).3 Il reste un résidu : « AppKit still emulates mouse events for any NSView that handles them, for example by overriding mouseDown: » (AppKit émule toujours les événements souris pour toute NSView qui les gère, par exemple en surchargeant mouseDown:).3 La page de la clé elle-même indique que la plupart des apps AppKit n’en ont pas besoin et qu’elle est « primarily intended for apps that embed a non-AppKit rendering or input framework and implement their own touch-handling pipeline » (destinée avant tout aux apps qui embarquent un framework de rendu ou d’entrée non AppKit et ont leur propre chaîne de traitement du toucher) ; un moteur de jeu correspond à ce cas.6

La section d’implémentation présente un modèle de délégué en Swift et en Objective-C : un reconnaisseur de panoramique et un reconnaisseur de toucher avec buttonMask à 0 pour ignorer une souris physique, un reconnaisseur d’agrandissement, shouldRecognizeSimultaneouslyWith qui renvoie true uniquement pour la paire panoramique et pincement, et shouldRequireFailureOf qui fait attendre les deux au toucher.3 Une note confirme la valeur par défaut qu’énonce le premier article : « The gesture recognizers must include direct in allowedTouchTypes. On macOS 27 and later, this is the default. »3 Les conseils d’aiguillage sont pensés pour un moteur : « Install the gesture recognizers once in your app and leave them enabled » (installez les reconnaisseurs une seule fois et laissez-les activés), convertissez les rappels en événements d’entrée du moteur, et « Let the active input handler decide if and how to react to different gestures » (laissez le gestionnaire d’entrée actif décider s’il réagit aux différents gestes, et comment), avec un gestionnaire de panoramique qui convertit les points en pixels grâce au facteur d’échelle du backing et inverse l’axe y pour un moteur dont l’origine est en haut à gauche.3 Les contrôles standard continuent de fonctionner après le changement, « as long as you don’t attach your own direct-touch gesture recognizers to them » (tant que vous ne leur attachez pas vos propres reconnaisseurs de toucher direct).3

La liste de contrôle finale comporte deux volets. Pour un jeu qui s’en remet à l’émulation : lire l’emplacement dans chaque événement, et ne pas surcharger sendEvent: ni tirer les événements de nextEventMatchingMask:. Pour un jeu qui gère le toucher : définir NSIsTouchNative, utiliser des reconnaisseurs de toucher direct avec buttonMask à 0, ajouter le tactile là où les joueurs l’attendent en le gardant utilisable d’une main, « Continue to read GCMouse for the physical mouse. With NSIsTouchNative set, touch no longer appears as an emulated GCMouse » (continuer de lire GCMouse pour la souris physique, le toucher n’apparaissant plus comme un GCMouse émulé), et utiliser des contrôles standard autant que possible.3

Les HIG, datées du 28 octobre

L’article de l’après-midi disait que les quatre pages des HIG vers lesquelles la page d’accueil prévoyait de renvoyer ne contenaient aucune recommandation sur le tactile sur l’écran du Mac. Trois d’entre elles en contiennent désormais, chacune avec une ligne de journal des modifications datée du 28 octobre 2026 ; la page Live Activities n’a pas changé.4

Designing for macOS (« Added guidance for touch input. ») réécrit trois lignes de sa description de la plateforme et ajoute une bonne pratique. Ergonomie : « 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 » (sur un Mac à écran tactile, on tend le bras pour toucher l’écran, si bien que le tactile sert à des interactions brèves et délibérées plutôt qu’à un travail prolongé). Les entrées citent désormais d’abord le clavier et les dispositifs de pointage, et rangent « Gestures on a trackpad or a Mac with a touch display » parmi les autres. La nouvelle bonne pratique résume tout le Tech Talk de design en une phrase : « 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 » (prenez en charge les gestes standard sur les trackpads et les Mac à écran tactile, et utilisez des cibles tactiles plus grandes et des contrôles qui s’agrandissent au toucher pour faciliter l’interaction tactile, tout en gardant la même taille et la même densité de contrôles pour le clavier et le pointage).4

Gestures (« Added guidance for gestures and touch input in macOS. ») développe sa section macOS, qui renvoie désormais vers les deux articles AppKit pour les recommandations destinées aux développeurs. Sa première phrase fixe le 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 » (sur un Mac à écran tactile, ou sur un iPad connecté par Sidecar, les gens peuvent aussi interagir directement par des gestes tactiles, comme mode d’entrée secondaire).4 Suivent six règles, chacune introduite en gras :

  • « Keep the layout and size of interface elements the same when you support touch » (garder la même disposition et la même taille des éléments d’interface quand vous prenez en charge le tactile). La raison : « In macOS, touch is secondary to keyboard and pointing device input », et les gens passent souvent d’une entrée à l’autre. Le contraste avec l’iPhone Duo, dont l’écran interne change de classes de taille, est précisément le propos : sur le Mac, l’entrée change et la disposition non.
  • « Make tap targets bigger, not the controls themselves » (agrandir les cibles tactiles, pas les contrôles), en « enlarging their tap targets to anticipate that interaction may start slightly outside of their visual bounds » (agrandissant leurs cibles pour prévoir une interaction qui commence un peu hors de leurs limites visibles), et « Where possible, use standard framework controls, which the system configures with an optimal tap target size automatically » (dans la mesure du possible, utiliser les contrôles standard du framework, auxquels le système donne automatiquement une taille de cible optimale).
  • « Consider providing expandable controls » (envisager des contrôles extensibles), une variante temporaire à la taille du toucher qui apparaît au toucher, reste tant que le doigt est maintenu et demeure « for a short time after they lift their finger to keep the control accessible to touch » (un court instant après que le doigt se lève, pour que le contrôle reste accessible au toucher) ; « When using standard system controls, steppers, sliders, and dials gain this behavior automatically » (les steppers, curseurs et molettes standard l’obtiennent automatiquement).
  • « Don’t use touch as the only way to access controls or features » (ne pas faire du tactile le seul accès à un contrôle ou une fonctionnalité), et la réciproque : « 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 » (prévoir des équivalents tactiles aux actions réservées au pointage, comme le glisser avec Maj ou un menu qui s’affiche au survol).
  • « Where possible, align with gestures people already know » (dans la mesure du possible, s’aligner sur les gestes que les gens connaissent déjà), ceux de votre app iPhone ou iPad ou ceux du trackpad, et « Test to ensure they don’t conflict with macOS system gestures » (vérifier qu’ils n’entrent pas en conflit avec les gestes système de macOS).
  • « Make sure custom gestures work for both touch and trackpad » (s’assurer que les gestes personnalisés fonctionnent au toucher comme au trackpad), en utilisant des « modifier keys where needed to help the gesture work for all touch inputs » (des touches de modification si nécessaire, pour que le geste fonctionne avec toutes les entrées tactiles).4

La page ne donne pas le chiffre de 36 points ; ce nombre figure dans les deux Tech Talks (« Touch on macOS uses a touch target of 36 points », à 9:17 dans le Tech Talk de design, et l’agrandissement de la zone de toucher du Tech Talk AppKit à « a minimum of 36 points on each axis », 5:53).9 Elle ajoute un tableau, « macOS trackpad and touch display gestures », qui associe chaque action à sa forme au trackpad et au toucher : sélectionner, c’est un clic principal ou un toucher ; ouvrir, un double clic ou un double toucher ; faire défiler, deux doigts sur le trackpad et un seul sur l’écran ; zoomer et faire pivoter, le même pincement et la même rotation à deux doigts sur les deux ; un menu contextuel, un clic secondaire ou un toucher maintenu ; un glisser, un clic-glisser ou « Touch and hold, then drag » (toucher, maintenir, puis glisser).4 Dans le tableau multiplateforme, « Touch (or pinch) and hold » mentionne désormais macOS, et la ligne du double toucher ajoute « open an item on a Mac with a touch display » (ouvrir un élément sur un Mac à écran tactile).4

Pointing devices (« Added guidance for revealing hidden controls with touch on Mac, and updated guidance and artwork for iPadOS and macOS. ») est la page aux règles les plus concrètes, sous un nouvel intitulé, « Touch support for hidden controls ». Le principe : « 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 » (certaines apps Mac ne révèlent des contrôles qu’au passage du pointeur, et ces contrôles ont besoin d’une autre façon d’apparaître quand on utilise le tactile sur un Mac qui le prend en charge).4 Les règles :

  • « Decide which hidden controls need touch support. Reserve touch support for essential controls » (décider quels contrôles masqués ont besoin du tactile, et le réserver aux contrôles essentiels). Les infobulles n’en font pas partie, pas plus que ce que le toucher permet déjà autrement : « 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 » (on peut balayer pour faire défiler un carrousel, inutile donc de révéler les boutons de navigation prévus pour les souris sans molette). Et : « Avoid keeping hidden controls visible all the time just to accommodate touch » (éviter de laisser les contrôles masqués visibles en permanence pour le seul tactile).
  • « Reveal a hidden control when someone taps its general location on a touch display » (révéler un contrôle masqué quand on touche sa zone approximative sur un écran tactile). Seulement ceux qui sont pertinents, et « Hide a control again when the person finishes with it, or after a few seconds if they don’t use it » (le masquer de nouveau quand on a fini, ou après quelques secondes sans usage).
  • « Retain the current selection when revealing a hidden control » (conserver la sélection en cours), car le contrôle peut agir sur la sélection.
  • « Make essential hidden controls available in a context menu » (proposer les contrôles masqués essentiels dans un menu contextuel), que le toucher ouvre par un toucher maintenu.
  • « In a sidebar, reveal hidden controls when someone taps the corresponding section header or row » (dans une barre latérale, révéler les contrôles masqués au toucher de l’en-tête de section ou de la ligne correspondante). L’exemple est le Finder : « tapping the Favorites header in the Finder sidebar collapses the section and reveals a disclosure button » (toucher l’en-tête Favoris replie la section et révèle un bouton d’affichage). Les contrôles révélés restent jusqu’à ce que le toucher quitte la barre latérale ou qu’un dispositif de pointage soit utilisé, et un toucher sur la zone d’un contrôle masqué le révèle sans l’activer : « The control acts only when they tap it again » (le contrôle n’agit qu’au toucher suivant).
  • « In a tabbed window, reveal the selected tab’s close button when someone taps anywhere in the window » (dans une fenêtre à onglets, révéler le bouton de fermeture de l’onglet sélectionné au toucher n’importe où dans la fenêtre), et le garder jusqu’à ce que l’utilisateur revienne à un dispositif de pointage.4

Ces six règles sont la forme écrite des recommandations des deux Tech Talks sur le survol, avec des délais et des exceptions que les Tech Talks ne donnaient pas : quelques secondes avant qu’un contrôle révélé ne se masque, un second toucher pour l’activer, et les barres latérales et les boutons de fermeture d’onglet comme cas qui restent visibles. La même révision a réécrit les éléments de la page sur le pointeur d’iPadOS pour Liquid Glass, changé la forme par défaut du pointeur dans son texte d’un cercle en flèche, remplacé la colonne AppKit seule du tableau des curseurs par des symboles AppKit et SwiftUI, et supprimé la section sur le magnétisme du pointeur ; rien de cela ne concerne l’écran du Mac.4

Ce qui manque encore

Vérifié le 28 octobre à 21 h 51 PT sur le point d’accès de la documentation d’Apple, la page d’accueil, les flux d’actualités et de sorties pour les développeurs et la liste des Tech Talks.56

  • La bêta de Xcode 27.3. La vignette de la page d’accueil indique toujours « Coming soon », le flux des sorties ne mentionne rien de plus récent que les versions intermédiaires du 26 octobre (macOS 27.2 parmi elles), et les pages des notes de version de Xcode 27.3 et de macOS 27.3 renvoient une erreur 404. Le Xcode le plus récent reste la RC 27.2, dont je n’ai pas examiné le SDK ; la réserve de l’article de l’après-midi sur la cible de déploiement s’applique toujours, et l’article sur la sortie de Xcode 27 donne le contexte sur les cibles de déploiement.
  • Les trois commutateurs de gestes système démontrés dans le Tech Talk AppKit, NSWindow.disablesDirectTouchSystemGestures, UIViewController.prefersDirectTouchSystemGesturesDisabled et directTouchSystemGesturesDisabled() de SwiftUI. Tous trois renvoient une erreur 404, et aucun des deux nouveaux articles ne les mentionne. Les articles décrivent comment un geste système annule votre reconnaisseur ; ils ne décrivent pas comment désactiver le geste système.
  • Un ActivityKit disponible sur macOS. La page du framework porte un horodatage du 8 octobre et ne répertorie aucune plateforme macOS, et AlertConfiguration.announcement(_:), tiré du Tech Talk sur les Live Activities, renvoie une erreur 404. La section de l’article de l’après-midi sur les Live Activities reste valable telle quelle, et l’article sur la machine à états reste une implémentation iOS uniquement.
  • Le « gesture modernization skill » que le Tech Talk AppKit mentionnait à 12:16. Aucune page d’Apple ne le nomme ; le commentaire TK skill de la page d’accueil est toujours là.59
  • Un lien de la page du Tech Talk vers l’article, et toute déclaration d’Apple sur un chemin par le simulateur ou le Device Hub pour le tactile sur Mac.
  • Une entrée datée sur la page des mises à jour de la documentation SwiftUI. Son horodatage serveur indique 14 h 58 PT le 28 octobre, mais sa section la plus récente reste celle de septembre 2026 et son entrée sur les gestes est la ligne GestureInputKinds de juin 2026 ; quel que soit ce qui a changé, il ne s’agissait pas d’une nouvelle section datée.6

Corrections apportées à l’article pour les développeurs

L’article de l’après-midi était exact au regard des vérifications de midi ; cinq de ses affirmations ne tenaient plus lors de la vérification du 28 octobre à 21 h 51 PT. Un bloc de mise à jour dans cet article, publié en même temps que celui-ci, renvoie ici, et les affirmations y sont corrigées sur place ; en voici la liste.

  1. Les trois vignettes « Coming soon » de la page d’accueil ne sont plus qu’une. Les ressources de documentation et les ressources de design sont en ligne ; la bêta de Xcode 27.3 ne l’est pas.5
  2. « Adopting gesture recognizers in your app » ne renvoie plus d’erreur 404. L’article est en ligne, et c’est dans ce document qu’il faut lire les recommandations de migration.1
  3. Les pages des HIG ne sont plus muettes sur le tactile pour l’écran du Mac. Trois des quatre portent des lignes de journal des modifications datées du 28 octobre ; la page Live Activities n’en porte pas.4
  4. La liste d’émulation compte cinq règles, et non les quatre de la technote de juin ; la règle du maintien puis du glisser est l’ajout.18
  5. Les vues exclusives ne sont activées par défaut que pour les apps dont l’Info.plist ne contient pas la clé. Les modèles macOS App et Document App de Xcode, dans Xcode 27.1 et dans la bêta 27.2, écrivent la clé avec la valeur NO pour un projet dont le cycle de vie est l’app delegate AppKit.127

Ce que l’article de l’après-midi disait des SDK, des trois commutateurs non documentés et d’ActivityKit n’est remis en cause par rien de ce qui a été publié depuis.

Points clés à retenir

Pour les développeurs AppKit : exécutez le test en quatre lignes de l’article avec Sidecar, puis recherchez les cinq symboles qu’il nomme ; une surcharge de mouseDown(with:), mouseDragged(with:) ou mouseUp(with:) sur une sous-classe de contrôle fait retomber ce contrôle sur une boucle de suivi, qu’elle appelle super ou non, et la correction passe par des paires cible-action sur NSControl.Events avec .trackingEndedOutside dans l’ensemble de masquage.1

Pour une boucle que vous conservez : le reconnaisseur réservé au toucher a besoin de buttonMask à 0, faute de quoi un glisser à la souris déclenche le reconnaisseur et votre mouseDown(with:) ne s’exécute jamais ; allowedTouchTypes = .direct est déjà la valeur par défaut.1

Pour les reconnaisseurs personnalisés : atteignez un état terminal, surchargez reset() et location(in:), et déboguez avec -NSGestureRecognizerCrashOnMissingOverrides YES et -NSCrashOnStuckGestureTimeout 2, à retirer avant la livraison ; en production, un reconnaisseur bloqué est annulé deux secondes après la fin de l’interaction.12

Pour l’exclusivité : une app sans la clé est exclusive ; un projet créé à partir du modèle macOS App de Xcode avec le cycle de vie de l’app delegate AppKit (tout projet Storyboard ou XIB) ne l’est pas, et un projet avec le cycle de vie SwiftUI par défaut l’est. Lisez votre Info.plist avant de migrer une boucle de suivi.27

Pour les jeux : lisez les emplacements dans les événements au lieu d’additionner les deltas, ne surchargez pas sendEvent(_:), et sachez que chaque surface tactile est un GCMouse émulé supplémentaire tant que NSIsTouchNative ne vaut pas YES.3

Pour les designers : le tactile est « a secondary input mode » (un mode d’entrée secondaire) ; gardez la disposition et la taille des contrôles ; agrandissez les cibles tactiles, pas les contrôles ; révélez les contrôles masqués au toucher de leur zone, masquez-les quand l’utilisateur a fini ou après quelques secondes sans usage, avec les barres latérales et le bouton de fermeture d’un onglet comme exceptions, et proposez les contrôles essentiels dans un menu contextuel.4

Questions fréquentes

L’article de migration AppKit est-il en ligne ?

Oui. « Adopting gesture recognizers in your app » renvoyait une erreur 404 le 28 octobre à midi PT et était en ligne à 21 h 51, répertorié dans les sections Essentials et Gestures d’AppKit et lié depuis la vignette de documentation de la page d’accueil.15

Les vues exclusives sont-elles activées dans mon app ?

Si votre Info.plist ne contient pas la clé NSViewGestureRecognizerIsExclusive, oui : « If your app’s information property list doesn’t include the NSViewGestureRecognizerIsExclusive key, the system treats it as YES » (si l’Info.plist de l’app ne contient pas la clé, le système la considère comme valant YES).2 Un projet créé à partir du modèle macOS App dans Xcode 27.1 ou dans la bêta 27.2 avec le cycle de vie de l’app delegate AppKit (tout projet Storyboard ou XIB) reçoit la clé sous la forme d’un réglage de compilation de valeur NO ; le même modèle avec le cycle de vie SwiftUI par défaut n’écrit aucune clé (analyse de l’auteur à partir des modèles présents sur ce Mac).7

Que devient un reconnaisseur de gestes qui ne termine jamais ?

Il garde sa vue exclusive jusqu’à ce que le système l’annule : un reconnaisseur « is stuck if it hasn’t reached a terminal state within 2 seconds after the person stops interacting with your app » (est bloqué s’il n’a pas atteint un état terminal dans les 2 secondes qui suivent la fin de l’interaction), et le système « automatically transitions it to the NSGestureRecognizer.State.cancelled state » (le fait passer automatiquement à l’état annulé).2 Définissez le réglage NSCrashOnStuckGestureTimeout pendant le débogage pour obtenir plutôt une trace de pile.2

Pourquoi ma boucle de suivi ne reçoit-elle plus les glissers après l’ajout d’un reconnaisseur de panoramique ?

Parce que le buttonMask du reconnaisseur est à sa valeur par défaut et s’approprie le bouton principal de la souris : « A mouse drag then begins the gesture recognizer, and AppKit doesn’t call your mouseDown(with:) override, so the tracking loop doesn’t run » (un glisser à la souris déclenche alors le reconnaisseur, AppKit n’appelle pas votre surcharge et la boucle ne s’exécute pas). Réglez buttonMask sur 0 pour un reconnaisseur réservé au toucher.1

Comment un jeu reçoit-il le toucher sur le Mac ?

Sous forme d’événements souris émulés, dans un ordre documenté, avec un mouseMoved(with:) vers le nouvel emplacement avant chaque mouseDown(with:), et sous forme d’un GCMouse supplémentaire dont les deltas ne sont pas continus. Régler NSIsTouchNative sur YES fait passer le jeu aux reconnaisseurs de gestes et supprime le GCMouse émulé.3

Les HIG donnent-elles une taille de cible tactile pour le Mac ?

Non. La page Gestures dit « Make tap targets bigger, not the controls themselves » (agrandir les cibles tactiles, pas les contrôles) et s’en remet aux contrôles du framework pour la taille ; le chiffre de 36 points figure dans les deux Tech Talks, pas dans les HIG.49

D’où viennent les faits de cet article ?

Des deux articles AppKit d’Apple, de TN3216, des trois pages des HIG et de la page d’accueil, tous récupérés le 28 octobre 2026 à 21 h 51 PT, la disponibilité de l’article, les pages des HIG et la page d’accueil étant comparées aux vérifications de midi et de 11 h 49 le même jour ; du JSON de documentation d’Apple pour chaque symbole cité ; de la technote de juin pour la comparaison ; des pistes de sous-titres des Tech Talks pour les citations des Tech Talks ; et des en-têtes et des modèles de projet inclus dans Xcode 27.1 et dans la bêta de Xcode 27.2 sur mon Mac, ce qui relève de l’analyse de l’auteur. La couverture de presse n’est pas une source.

Sources


  1. Documentation développeur Apple, Adopting gesture recognizers in your app, AppKit. Consultée le 28 octobre 2026 à 21 h 51 PT (HTTP 200, Last-Modified: Wed, 28 Oct 2026 21:30:45 GMT) ; la même URL et son point d’accès JSON renvoyaient une erreur 404 à midi PT le même jour. Répertoriée dans la section Essentials de la page du framework AppKit, en deuxième position après « Adopting Liquid Glass », et sous « Gesture handling » sur Gestures. La première capture de la Wayback Machine date du 2026-10-29 01:08:12 UTC (ligne CDX 20261029010812, statut 200 ; l’en-tête brut et les justificatifs CDX sont conservés avec les éléments de preuve de l’article). Sections citées : Overview, « Prepare your app for a Mac with a touch display » (le tableau, le test en quatre lignes, la liste de recherche), « 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 » et « Support non-AppKit UI frameworks ». Le listing Swift de cet article est une réduction en quatre lignes du listing de « Add touch support to your custom tracking loop ». ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  2. Documentation développeur Apple, Handling AppKit gestures, AppKit. Consultée le 28 octobre 2026 à 21 h 51 PT (Last-Modified: Wed, 28 Oct 2026 05:35:41 GMT) ; aucun historique des révisions sur la page ; aucun lien depuis TN3212 ; aucune capture dans l’index de la Wayback Machine à cette heure-là. Sections citées : 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 » et « Diagnose unresponsive gesture recognizers ». ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  3. Documentation développeur Apple, TN3216: Supporting touch input for games on macOS, historique des révisions « 2026-10-28 First published », consultée le 28 octobre 2026 à 21 h 51 PT (Last-Modified: Wed, 28 Oct 2026 21:09:50 GMT). Sections citées : 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 » et « Review your touch integration ». ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  4. Apple Developer, Human Interface Guidelines, consultées le 28 octobre 2026 à 21 h 51 PT et comparées aux copies récupérées à midi PT le même jour : Designing for macOS, journal des modifications « October 28, 2026 | Added guidance for touch input. » ; Gestures, journal des modifications « October 28, 2026 | Added guidance for gestures and touch input in macOS. », avec la section de plateforme macOS et le tableau « macOS trackpad and touch display gestures » ; Pointing devices, journal des modifications « October 28, 2026 | Added guidance for revealing hidden controls with touch on Mac, and updated guidance and artwork for iPadOS and macOS. », avec la section « Touch support for hidden controls » ; Live Activities, identique octet pour octet à la copie de midi, journal des modifications s’arrêtant au 13 octobre 2026. Les copies de midi des trois premières ne contiennent aucun des textes cités. L’en-tête Last-Modified du serveur sur le nouveau JSON de Gestures indique Tue, 27 Oct 2026 05:11:02 GMT, avec X-Cache: hit-stale à la première requête, à 21 h 59. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  5. Apple Developer, Get ready for Touch on Mac, consultée le 28 octobre 2026 à 11 h 49 PT (trois vignettes indiquant « Coming soon », liens dans des commentaires HTML) et à 21 h 51 PT : « 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. », avec des liens vers l’article et l’exemple ; « Design resources » / « Create experiences that take advantage of touch user interface patterns. », avec des liens vers Gestures, Designing for macOS, Pointing devices et Apple Design Resources, dont la liste de contours d’appareils inclut « MacBook Pro PROLED XDR Display » ; le commentaire TK skill présent lors des deux consultations. Latest News RSS, élément le plus récent « Say hello to Touch on Mac », Wed, 28 Oct 2026 10:00:55 PDT ; Releases RSS, éléments les plus récents datés Mon, 26 Oct 2026 10:00:00 PDT (iOS, iPadOS, macOS 27.2 (26B123), tvOS, visionOS et watchOS 27.2) ; liste des Tech Talks comprenant 111456, 111467 et 111468 et aucun autre Tech Talk sur le tactile ou les Live Activities ; la page de Bring Touch to your Mac app, sans lien vers l’un ou l’autre article ; tous consultés à 21 h 51 PT. ↩↩↩↩↩↩↩↩↩

  6. JSON de documentation d’Apple (developer.apple.com/tutorials/data/documentation/<path>.json), récupéré le 28 octobre 2026 à 21 h 51 PT. Trouvés : trackingEndedOutside (macOS 11.0), NSEvent.isTouchSwipeNavigationEnabled (macOS 27.0), NSTextSelectionManager.gesturesForFailureRequirements (macOS 27.0), gestureRecognizer(_:shouldRequireFailureOf:) sur NSGestureRecognizerDelegate (macOS 10.10), NSViewGestureRecognizerIsExclusive (« The default value is YES. »), NSIsTouchNative, Mouse, Keyboard, and Trackpad (les deux phrases sur la chaîne de répondeurs et le toucher), Supporting drag and drop through file promises (la phrase sur les touchers directs) et SwiftUI updates (Last-Modified: Wed, 28 Oct 2026 21:58:06 GMT, section la plus récente « September 2026 »). Introuvables (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, plateformes iOS, iPadOS et Mac Catalyst. ↩↩↩↩↩↩↩↩↩↩↩

  7. Vérification des SDK et des modèles le 28 octobre 2026 (analyse de l’auteur), dans MacOSX27.0.sdk inclus dans Xcode 27.1 (27A9275) : la ligne 180 de NSControl.h indique NSControlEventTrackingEndedOutside = 1 << 7, avec la même ligne dans MacOSX27.2.sdk inclus dans la bêta de Xcode 27.2 (27B5019j) ; la ligne 496 de NSEvent.h indique @property (class, readonly, getter=isTouchSwipeNavigationEnabled) BOOL touchSwipeNavigationEnabled API_AVAILABLE(macos(27)); ; la ligne 59 de NSTextSelectionManager.h déclare gesturesForFailureRequirements ; les lignes 116 et 149 de NSGestureRecognizer.h déclarent shouldRequireFailureOfGestureRecognizer:. Modèles de projet, lus avec plistlib, dans Xcode 27.1 (27A9275) et dans la bêta de Xcode 27.2 (27B5019j), sous Platforms/MacOSX.platform/Developer/Library/Xcode/Templates/Project Templates/macOS/Application/ : Cocoa App Base.xctemplate/TemplateInfo.plist contient la clé INFOPLIST_KEY_NSViewGestureRecognizerIsExclusive avec la valeur de chaîne NO à Options[0] (appLifecycle) / Units / Cocoa / Targets[0] / SharedSettings, et nulle part ailleurs ; macOS App Lifecycle.xctemplate, sous Project Templates/macOS/Base/, définit l’option appLifecycle sans Name, avec Type popup, Default SwiftUI et Values SwiftUI, Cocoa ; macOS Application UI.xctemplate définit userInterface avec Name Interface:, Default SwiftUI et Values SwiftUI, Storyboard, XIB, et ses unités Storyboard et XIB, comme l’une de ses unités SwiftUI, portent RequiredOptions avec appLifecycle Cocoa ; App.xctemplate descend de Cocoa App Base par Core Data App, macOS SwiftUI App, macOS Application UI et Cocoa App Native Base ; Cocoa Document App.xctemplate écrit un nœud Info.plist:NSViewGestureRecognizerIsExclusive dont la définition est <key>NSViewGestureRecognizerIsExclusive</key><false/>, dans son unité de cycle de vie Cocoa ; Game.xctemplate règle appLifecycle sur Override Cocoa ; Document App.xctemplate descend à la fois de Cocoa Document App et de Cocoa App Base (par Core Data Document App, macOS SwiftUI Document-Based App, macOS Application UI et Cocoa App Native Base). Seuls ces deux fichiers de modèle contiennent la clé, dans l’une comme dans l’autre version de Xcode. Les réglages de compilation INFOPLIST_KEY_ de Xcode sont écrits dans l’Info.plist généré. ↩↩↩↩↩↩↩↩↩↩

  8. Documentation développeur Apple, TN3212: Adopting gesture recognizers for Sidecar touch support, historique des révisions « 2026-06-08 First published », section « Support third-party UI frameworks », quatre points : « 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 » et « A two-finger pinch or rotation simultaneously emulates both the trackpad magnify and rotate gestures ». Consultée le 28 octobre 2026. ↩↩↩

  9. Apple Developer, Tech Talks Bring Touch to your Mac app (13:19, Raleigh Ledet) et Design for touch on Mac (16:52, Cas Lemmens), 28 octobre 2026. Les citations proviennent des pistes de sous-titres anglais ; le temps entre parenthèses est le premier repère du passage cité : « 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). Le listing de code de la page du Tech Talk « Use NSControl events to detect tracking » (2:24) enregistre addTarget(_:action:for:) pour [.trackingBegan, .trackingEntered], [.trackingExited, .trackingEndedInside] et .trackingCancelled. ↩↩↩↩↩

Articles connexes

Touch on Mac côté développeurs : 36 points, gestes, Sidecar

Le tactile sur Mac pour les développeurs : touchers convertis en clics, reconnaisseurs de gestes seuls accès au toucher …

51 min de lecture

Les images des éléments de menu disparaissent dans macOS 27 et iPadOS 27

macOS 27 et iPadOS 27 masquent par défaut les images des éléments de menu, et le résultat dépend du SDK de liaison. Troi…

17 min de lecture

Le Mac App Store interdit l'existence des gestionnaires de fenêtres. J'en ai publié un quand même.

App Sandbox interdit l'API dont tout outil de mosaïque de fenêtres a besoin, et Apple m'a répondu de publier hors de la …

14 min de lecture