Choisissez SwiftUI Canvas lorsque le déplacement nécessite des règles de placement explicites que vous pouvez implémenter vous-même. Choisissez SpriteKit lorsque les formes doivent se pousser, rebondir ou continuer à bouger sous l’effet de la physique. Commencez par décider de ce qui se passe lorsqu’une forme déplacée en rencontre une autre.

Ce guide porte sur une tâche précise : choisir l’architecture d’interaction pour une app Mac contenant des cercles ou des rectangles synthétiques stockés en mémoire, que les utilisateurs déplacent dans un espace de travail appartenant à l’app. La décision dépend du comportement en cas de collision, de la gestion des entrées et du composant qui contrôle la position de chaque forme.

Que doit-il se passer lorsque deux formes se rencontrent ?

« Réponse aux collisions » peut désigner plusieurs exigences différentes. Mettre en évidence un chevauchement, refuser une position invalide et faire rebondir deux objets nécessitent des mécanismes différents. Apple distingue explicitement les contacts SpriteKit, qui signalent le contact entre des corps, des collisions, qui calculent une réponse physique et appliquent des impulsions. À propos des collisions et des contacts

Comportement souhaité Point de départ pratique Responsabilités qui vous incombent
Mettre en évidence un chevauchement tandis que le pointeur place librement une forme Canvas avec des tests géométriques personnalisés Sélection, détection des chevauchements et retour visuel
Arrêter une forme au premier obstacle Canvas avec des contraintes de déplacement personnalisées Tester le trajet et déterminer la position acceptée
Pousser les formes voisines et permettre qu’elles se stabilisent ou rebondissent SpriteKit Configuration physique et contrôleur de déplacement
Fournir à chaque élément les contrôles et gestes SwiftUI habituels Vues SwiftUI individuelles Règles propres à l’application pour le chevauchement ou le déplacement

Il s’agit de recommandations d’architecture fondées sur les capacités documentées, et non de seuils de performance définis par Apple. Un petit nombre de formes ne favorise pas automatiquement Canvas, et un grand nombre n’impose pas automatiquement SpriteKit. Choisissez d’abord le comportement, puis mesurez l’implémentation avec des éléments représentatifs.

Il existe aussi une contrainte d’interaction utile à prendre en compte : le suivi exact du pointeur peut entrer en conflit avec l’absence stricte de chevauchement. Si le pointeur traverse une position occupée, l’app doit autoriser le chevauchement de l’élément, l’arrêter derrière le pointeur ou déplacer d’autres éléments. Précisez ce choix avant de sélectionner le moteur de rendu.

Que fournit Canvas, et que devez-vous créer ?

Canvas fournit un dessin en mode immédiat au moyen d’un contexte graphique. Ses cercles, chemins et symboles ne deviennent pas des éléments interactifs indépendants. Apple précise également que Canvas n’offre pas d’accessibilité pour les éléments dessinés individuellement, y compris les symboles fournis sous forme de vues. Un geste attaché au canevas nécessite donc une logique applicative pour déterminer quel élément l’utilisateur a sélectionné. Canvas

Pour un éditeur avec des règles de placement explicites, conservez les formes dans un modèle et considérez le dessin comme une projection de ce modèle. Chaque élément peut contenir un identifiant, un centre, des dimensions et un ordre de dessin. Les recommandations de Swift privilégient les structures pour les données ordinaires et les classes lorsque l’identité partagée ou l’héritage d’un framework est nécessaire. Un modèle d’élément à type valeur et une sous-classe de scène SpriteKit répondent à des besoins différents. Choisir entre les structures et les classes

Un déplacement dans Canvas peut suivre cette séquence :

  1. Au début du geste, testez la position initiale du pointeur par rapport aux éléments dans l’ordre inverse du dessin et sélectionnez le premier élément correspondant.
  2. Conservez l’identifiant de l’élément sélectionné et son centre d’origine pendant tout le geste.
  3. Calculez chaque centre proposé à partir du centre d’origine, auquel vous ajoutez la translation totale du geste.
  4. Appliquez les limites de l’espace de travail et les règles de collision pour obtenir un centre accepté.
  5. Mettez à jour le modèle et laissez Canvas le redessiner.
  6. Effacez la sélection à la fin du geste et définissez la procédure de nettoyage des interactions interrompues.

La distinction entre mouvement total et mouvement incrémentiel est importante. SwiftUI définit translation comme le déplacement depuis le début du geste. Ajouter plusieurs fois ce déplacement total à un centre déjà mis à jour accumule un mouvement excessif. Utilisez le centre de départ conservé à chaque calcul. SwiftUI expose startLocation, location et translation via la valeur du geste, et permet de définir l’espace de coordonnées du geste. DragGesture.Value, DragGesture

Gardez la sélection stable pendant le déplacement. Répéter la sélection initiale à chaque mise à jour du pointeur peut changer l’élément sélectionné lorsque le pointeur traverse une autre forme. De même, conserver le décalage initial entre le pointeur et le centre évite que la forme saute brusquement pour placer son centre sous le pointeur. Ce sont des règles de conception de l’application, et non des comportements fournis automatiquement par Canvas.

Exemple détaillé : détection d’un chevauchement ou blocage du déplacement

Prenons deux cercles d’exemple, chacun de rayon 20 points. Le cercle A se trouve initialement en (100, 100) et le cercle B, immobile, en (200, 100). L’utilisateur demande à placer le centre de A en (190, 100). La distance entre leurs centres serait alors de 10 points, tandis que la somme de leurs rayons est de 40. Cette position candidate entraîne un chevauchement.

Pour signaler le chevauchement, acceptez la position candidate et modifiez le dessin. Pour bloquer un déplacement horizontal, le premier point de contact est (160, 100) : les centres sont alors distants de 40 points. Ce calcul suppose que B reste fixe, que les deux cercles gardent leur taille et que A suit une trajectoire horizontale.

Supposons maintenant qu’une mise à jour du pointeur demande (260, 100). Tester uniquement cette position finale ne détecte aucun chevauchement, même si le déplacement passe par B. Une implémentation qui bloque le déplacement doit vérifier les intersections sur la trajectoire ou utiliser une autre méthode explicite pour contrôler le chemin intermédiaire. Rejeter uniquement les positions finales qui se chevauchent ne permet pas d’« arrêter au premier obstacle ».

Cet exemple montre pourquoi il est essentiel de définir précisément la réponse aux collisions. Quelques tests géométriques sur les positions finales peuvent suffire pour mettre en évidence les chevauchements. Gérer le glissement autour des obstacles, les contacts simultanés ou le déplacement des éléments voisins complexifie considérablement le solveur personnalisé. Canvas reste une option pour le dessin, mais votre application prend alors en charge ce comportement supplémentaire.

Quand SpriteKit devient-il la meilleure option ?

SpriteKit devient intéressant lorsque la réponse aux collisions doit émerger d’une simulation. Associer un SKPhysicsBody à un nœud permet de calculer la gravité, la friction et les collisions ; la simulation met ensuite à jour les positions et les orientations des nœuds. Elle prend en charge les corps volumiques pour les objets et les corps de bord pour les limites. SKPhysicsBody

Pour un premier prototype, utilisez deux cercles séparés et les limites rectangulaires d’un espace de travail. Attribuez aux cercles des corps circulaires correspondants, désactivez la gravité pour obtenir un espace de travail de type table et décidez si la rotation contribue à l’interaction. Configurez délibérément la friction, la restitution et l’amortissement au lieu de vous fier aux valeurs par défaut. Ces propriétés déterminent différents aspects du comportement physique. SKPhysicsBody

SwiftUI peut héberger directement la scène avec SpriteView, qu’Apple décrit comme une vue SwiftUI affichant une scène SpriteKit. La documentation indique que cette vue est disponible sur Mac à partir de macOS 11, tandis que Canvas nécessite macOS 12. Les versions minimales prises en charge doivent donc entrer dans la décision, même si le comportement d’interaction reste le critère principal. SpriteView, Canvas

Configurez séparément la réponse aux collisions et les notifications

Attribuez des catégories à vos formes et à la limite, puis configurez explicitement les deux masques d’interaction :

  • categoryBitMask indique les catégories auxquelles appartient un corps.
  • collisionBitMask sélectionne les catégories susceptibles d’affecter physiquement ce corps.
  • contactTestBitMask demande des notifications de contact pour les catégories sélectionnées.

Les masques de collision sont évalués indépendamment pour chaque corps. Pour obtenir une réponse mutuelle ordinaire entre deux formes dynamiques, configurez les deux corps afin qu’ils réagissent à la catégorie de l’autre. Les notifications de contact obéissent à une autre règle : une comparaison correspondante entre une catégorie et un masque de contact dans l’un ou l’autre sens peut générer une notification. Activer les notifications ne configure pas à lui seul le blocage physique. collisionBitMask, contactTestBitMask

Si les corps réagissent physiquement mais qu’aucun rappel n’est déclenché, vérifiez le masque de contact et le délégué de contact du monde physique. Si les rappels sont déclenchés alors que les formes se traversent, examinez les masques de collision et le contrôleur de déplacement. Ces vérifications permettent de distinguer la configuration des notifications du problème de déplacement lui-même.

Qui contrôle la position pendant le déplacement de l’utilisateur ?

Un déplacement dans SpriteKit nécessite un contrôleur explicite. Le moteur physique ne suit pas le pointeur de lui-même. Choisissez entre le placement direct et le déplacement piloté par simulation selon le comportement défini précédemment.

Avec le placement direct, le code de l’application attribue la position du nœud déplacé. Cette solution convient lorsque l’élément doit suivre précisément le pointeur, mais elle ne définit pas à elle seule un trajet résolu physiquement entre deux positions successives. Désactiver isDynamic fait ignorer au corps les forces et les impulsions ; cela ne crée pas un solveur de déplacement. Si vous modifiez temporairement cette propriété, définissez quand elle sera rétablie et si le mouvement doit être conservé ou effacé au relâchement. isDynamic

Avec un déplacement piloté par la simulation, conservez la position du pointeur comme cible et utilisez des forces ou un contrôleur à ressort pour guider un corps dynamique vers cette cible. La simulation peut alors gérer les contacts pendant le déplacement du corps. En contrepartie, le mouvement accuse délibérément un retard ou s’écarte du pointeur lorsqu’un autre objet bloque le passage. SpriteKit permet d’appliquer des forces, et SKPhysicsJointSpring relie deux corps physiques avec un comportement de ressort. Ces composants nécessitent un réglage propre à l’application. SKPhysicsBody, SKPhysicsJointSpring

Évitez d’attribuer en continu les positions du pointeur tout en attendant de la physique qu’elle contrôle indépendamment la même position. La séquence de rendu documentée de SpriteKit exécute les mises à jour de l’application, les actions, la physique et les rappels ultérieurs lors d’étapes distinctes. Une règle de contrôle utile consiste à laisser les entrées mettre à jour la cible du déplacement, la logique de la scène appliquer le contrôleur et la physique produire la position résultante du corps. Répondre aux événements du cycle de rendu

Les rappels de contact ont également une limite documentée. Apple indique qu’ils sont exécutés pendant l’étape de simulation physique, au cours de laquelle la modification du monde physique n’est pas prise en charge et la modification des corps simulés entraîne un comportement indéfini. Enregistrez le travail à effectuer dans le rappel, puis appliquez les changements physiques lors d’une mise à jour ultérieure, selon le modèle d’Apple qui consiste à utiliser un indicateur puis une mise à jour. SKPhysicsContactDelegate

Comment les entrées macOS doivent-elles passer d’un framework à l’autre ?

Choisissez un seul composant pour gérer les entrées de l’espace de travail. Une implémentation Canvas peut associer un DragGesture SwiftUI à sa surface d’interaction. Une surface AppKit personnalisée peut gérer les événements du répondeur ; mouseDragged(with:) reçoit les mouvements effectués avec le bouton gauche de la souris enfoncé. Évitez qu’un geste SwiftUI superposé et un gestionnaire AppKit déplacent indépendamment le même élément sélectionné. DragGesture, mouseDragged(with:)

Pour faire communiquer AppKit et SpriteKit, convertissez les coordonnées à chaque frontière. NSEvent.locationInWindow est généralement exprimé dans le système de coordonnées de base de la fenêtre associée. Convertissez ce point dans les coordonnées de la vue hôte, puis des coordonnées de la vue vers celles de la scène. Apple fournit convertPoint(fromView:) pour cette dernière étape et exige que la scène soit présentée avant son appel. La position d’un nœud est exprimée dans le système de coordonnées de son parent ; des nœuds imbriqués peuvent donc nécessiter une autre conversion. locationInWindow, convertPoint(fromView:), position

Utilisez SpriteView pour un hébergement SwiftUI simple. Si vous avez besoin d’une vue AppKit personnalisée, NSViewRepresentable fournit la création, les mises à jour, la coordination et le nettoyage. Laissez SwiftUI gérer la disposition de la vue représentée : Apple déconseille explicitement de définir vous-même son cadre ou ses limites. NSViewRepresentable

Conservez la scène auprès d’un propriétaire stable au lieu de créer une nouvelle scène chaque fois que SwiftUI recalcule le contenu d’une vue. Pour Canvas, conservez le modèle des éléments de la même manière. La documentation sur les modèles de SwiftUI explique comment l’état géré préserve une instance lors de la recréation des vues ; son approche Observation nécessite macOS 14 ou une version ultérieure. Traitez les commandes de scène et les instantanés du modèle comme des transferts explicites afin que deux copies modifiables ne se disputent pas le contrôle des positions. Gérer les données du modèle dans votre app

Que devez-vous tester avant de vous décider ?

Utilisez un petit espace de travail synthétique pour tester les interactions difficiles avant d’ajouter des finitions visuelles :

  1. Saisissez une forme près de son bord et vérifiez que le décalage initial est conservé.
  2. Déplacez-la lentement contre un autre élément, puis recommencez avec un seul grand mouvement du pointeur qui traverse cet élément.
  3. Déplacez-la vers un coin où se trouvent deux formes voisines.
  4. Relâchez-la au contact d’un autre élément et vérifiez le comportement de stabilisation attendu.
  5. Redimensionnez l’espace de travail et vérifiez la sélection, les limites et la conversion des coordonnées.
  6. Recréez le contenu SwiftUI qui l’entoure et vérifiez que les positions des éléments sont conservées.
  7. Interrompez un déplacement et vérifiez que la sélection et les paramètres physiques temporaires sont réinitialisés.

Dans SpriteKit, activez SKView.showsPhysics pour vérifier que la géométrie des corps correspond à la zone de collision voulue. Pour les petits corps qui se déplacent rapidement, évaluez usesPreciseCollisionDetection ; Apple indique que son coût de calcul est supérieur et qu’il permet de détecter des collisions que les vérifications ordinaires à chaque image peuvent manquer. Il ne remplace pas un contrôleur de déplacement cohérent. SKView, usesPreciseCollisionDetection

Choisissez Canvas lorsque vos règles de placement restent claires, délimitées et plus simples à exprimer directement. Choisissez SpriteKit lorsque l’interaction physique entre les formes fait partie de la tâche. Dans les deux cas, un déplacement fiable sur ordinateur commence par un seul élément sélectionné, des conversions explicites des coordonnées et un seul composant chargé de chaque mise à jour de position.

Sources