Scegli SwiftUI Canvas quando il trascinamento richiede regole di posizionamento esplicite che puoi implementare autonomamente. Scegli SpriteKit quando le forme devono spingersi, rimbalzare o continuare a muoversi grazie alla fisica. Inizia decidendo cosa accade quando una forma trascinata incontra un’altra forma.
Questa guida affronta un solo compito: scegliere l’architettura di interazione per un’app Mac che contiene cerchi o rettangoli sintetici, conservati in memoria, che gli utenti trascinano all’interno di un’area di lavoro gestita dall’app. La decisione dipende dal comportamento in caso di collisione, dalla gestione degli input e dal componente che controlla la posizione di ogni forma.
Cosa dovrebbe accadere quando due forme si incontrano?
«Risposta alle collisioni» può indicare diversi requisiti. Evidenziare una sovrapposizione, rifiutare una posizione non valida e far rimbalzare due oggetti richiedono meccanismi diversi. Apple distingue esplicitamente i contatti di SpriteKit, che segnalano il tocco tra corpi, dalle collisioni, che calcolano una risposta fisica e applicano impulsi. Informazioni su collisioni e contatti
| Comportamento desiderato | Punto di partenza pratico | Responsabilità che mantieni |
|---|---|---|
| Evidenziare una sovrapposizione mentre il puntatore posiziona liberamente una forma | Canvas con test geometrici personalizzati | Selezione, rilevamento della sovrapposizione e feedback visivo |
| Fermare una forma al primo ostacolo | Canvas con vincoli di movimento personalizzati | Verifica del percorso e risoluzione della posizione accettata |
| Spingere le forme vicine e consentire che si assestino o rimbalzino | SpriteKit | Configurazione della fisica e controller di trascinamento |
| Fornire a ogni elemento i normali controlli e gesti di SwiftUI | Singole viste SwiftUI | Regole specifiche dell’app per sovrapposizioni o movimenti |
Sono raccomandazioni architetturali basate sulle funzionalità documentate, non soglie prestazionali stabilite da Apple. Un numero ridotto di forme non favorisce automaticamente Canvas, e un numero elevato non richiede automaticamente SpriteKit. Scegli prima il comportamento, poi misura l’implementazione con un numero rappresentativo di elementi.
C’è anche un vincolo utile da considerare nell’interazione: il tracciamento esatto del puntatore può entrare in conflitto con il divieto assoluto di sovrapposizioni. Se il puntatore attraversa una posizione occupata, l’app deve consentire la sovrapposizione, fermare l’elemento dietro il puntatore oppure spostare gli altri elementi. Definisci questa scelta prima di selezionare il renderer.
Cosa offre Canvas e cosa devi costruire?
Canvas offre il disegno in modalità immediata tramite un contesto grafico. I cerchi, i tracciati e i simboli non diventano elementi interattivi indipendenti. Apple afferma inoltre che Canvas non fornisce accessibilità per i singoli elementi disegnati, compresi i simboli forniti come viste. Un gesto collegato a Canvas richiede quindi una logica dell’app per determinare quale elemento l’utente ha selezionato. Canvas
Per un editor con regole di posizionamento esplicite, conserva le forme in un modello e tratta il disegno come una rappresentazione di quel modello. Ogni elemento può contenere un identificatore, il centro, le dimensioni e l’ordine di disegno. Le linee guida di Swift consigliano le strutture per i dati ordinari e le classi quando sono necessarie un’identità condivisa o l’ereditarietà da un framework. Un modello di elementi basato su tipi valore e una sottoclasse della scena SpriteKit hanno scopi diversi. Scelta tra strutture e classi
Un trascinamento con Canvas può seguire questa sequenza:
- All’inizio del gesto, verifica la posizione iniziale del puntatore rispetto agli elementi in ordine inverso a quello di disegno e seleziona il primo elemento corrispondente.
- Conserva l’identificatore dell’elemento selezionato e il suo centro iniziale per tutta la durata del gesto.
- Calcola ogni centro proposto sommando la traslazione totale del gesto al centro iniziale.
- Applica i limiti dell’area di lavoro e le regole sulle collisioni per ottenere un centro accettato.
- Aggiorna il modello e lascia che Canvas ridisegni il contenuto a partire da esso.
- Cancella la selezione al termine del gesto e definisci come gestire le interazioni interrotte.
La distinzione tra movimento totale e incrementale è importante. SwiftUI definisce translation come lo spostamento dall’inizio del gesto. Aggiungere ripetutamente questo valore totale a un centro già aggiornato provoca uno spostamento eccessivo. Usa ogni volta il centro iniziale conservato. SwiftUI espone startLocation, location e translation tramite il valore del gesto e consente di specificare lo spazio di coordinate del gesto. DragGesture.Value, DragGesture
Mantieni stabile la selezione durante il trascinamento. Ripetere a ogni aggiornamento del puntatore l’operazione iniziale di selezione può cambiare l’elemento selezionato quando il puntatore attraversa un’altra forma. Allo stesso modo, conservare lo scostamento iniziale tra puntatore e centro evita che la forma salti, portando improvvisamente il proprio centro sotto il puntatore. Sono regole di progettazione dell’app, non comportamenti forniti automaticamente da Canvas.
Esempio pratico: rilevamento delle sovrapposizioni o blocco del movimento
Consideriamo due cerchi esemplificativi, ciascuno con raggio di 20 punti. Il cerchio A parte da (100, 100) e il cerchio B, fermo, si trova in (200, 100). L’utente richiede che il centro di A si sposti in (190, 100). La distanza tra i centri sarebbe di 10 punti, mentre la somma dei raggi è 40. La posizione proposta comporta una sovrapposizione.
Per indicare una sovrapposizione, accetta la posizione proposta e modifica il disegno. Per bloccare un trascinamento orizzontale, la prima posizione di contatto è (160, 100): in quel punto i centri distano 40 punti. Questo calcolo presuppone che B rimanga fermo, che le dimensioni dei cerchi non cambino e che A segua il percorso orizzontale.
Ora supponiamo che un aggiornamento del puntatore richieda (260, 100). Verificare soltanto quel punto finale non rileva alcuna sovrapposizione, anche se il percorso attraversa B. Un’implementazione che blocca il trascinamento deve verificare le intersezioni lungo il percorso o usare un altro metodo esplicito per controllare il tratto intermedio. Rifiutare soltanto i punti finali che si sovrappongono non implementa il comportamento «fermati al primo ostacolo».
Questo esempio è il motivo principale per definire con precisione la risposta alle collisioni. Per realizzare una funzione che evidenzia le sovrapposizioni possono bastare alcuni test geometrici sui punti finali. Supportare lo scorrimento attorno agli ostacoli, i contatti simultanei o lo spostamento degli elementi vicini amplia notevolmente il risolutore personalizzato. Canvas resta un’opzione per il disegno, ma questi comportamenti aggiuntivi sono responsabilità dell’applicazione.
Quando SpriteKit diventa la scelta più adatta?
SpriteKit è interessante quando la risposta alle collisioni dovrebbe emergere da una simulazione. Collegando un SKPhysicsBody a un nodo, si attivano calcoli che coinvolgono gravità, attrito e collisioni; la simulazione aggiorna quindi le posizioni e gli orientamenti dei nodi. Supporta corpi volumetrici per gli oggetti e corpi di bordo per i limiti. SKPhysicsBody
Per un primo prototipo, usa due cerchi separati e i limiti rettangolari di un’area di lavoro. Assegna ai cerchi corpi circolari corrispondenti, disattiva la gravità per un’area di lavoro simile a un piano d’appoggio e decidi se la rotazione contribuisce all’interazione. Configura con attenzione attrito, restituzione e smorzamento, invece di affidarti ai valori predefiniti. Queste proprietà determinano aspetti diversi del comportamento fisico. SKPhysicsBody
SwiftUI può ospitare la scena direttamente usando SpriteView, che Apple documenta come una vista SwiftUI che visualizza una scena SpriteKit. La disponibilità documentata su Mac inizia con macOS 11; Canvas richiede macOS 12 o successivo. Gli obiettivi di distribuzione fanno quindi parte della decisione, anche se il criterio principale resta il comportamento dell’interazione. SpriteView, Canvas
Configura la risposta alle collisioni separatamente dalle notifiche
Assegna categorie alle forme e ai limiti, quindi configura esplicitamente le due maschere di interazione:
categoryBitMaskidentifica le categorie a cui appartiene un corpo.collisionBitMaskseleziona le categorie che possono influenzare fisicamente quel corpo.contactTestBitMaskrichiede notifiche di contatto per le categorie selezionate.
Le maschere di collisione vengono valutate separatamente per ogni corpo. Per ottenere una normale risposta reciproca tra due forme dinamiche, configura entrambi i corpi affinché rispondano alla categoria dell’altro. La segnalazione dei contatti segue una regola diversa: il confronto tra categoria e maschera dei contatti in una delle due direzioni può generare una notifica. Attivare soltanto le notifiche non configura il blocco fisico. collisionBitMask, contactTestBitMask
Se i corpi reagiscono fisicamente ma non arriva alcuna callback, controlla la maschera dei contatti e il delegato dei contatti del mondo fisico. Se arrivano callback mentre le forme si attraversano, esamina le maschere di collisione e il controller di movimento. Queste verifiche distinguono la configurazione delle notifiche dal problema effettivo del movimento.
Chi controlla la posizione durante il trascinamento?
Un trascinamento in SpriteKit richiede un controller esplicito. Il motore fisico, da solo, non segue il puntatore. Scegli tra posizionamento diretto e movimento guidato dalla simulazione in base al comportamento definito in precedenza.
Con il posizionamento diretto, il codice dell’app assegna la posizione del nodo trascinato. È adatto quando l’elemento deve seguire esattamente il puntatore, ma da solo non determina il percorso risolto fisicamente tra posizioni successive. Disattivando isDynamic, il corpo ignora forze e impulsi; questo non crea un risolutore del trascinamento. Se modifichi temporaneamente questa proprietà, definisci quando ripristinarla e se al rilascio il movimento debba essere mantenuto o azzerato. isDynamic
Con il trascinamento guidato dalla simulazione, conserva la posizione del puntatore come obiettivo e usa forze o un controller a molla per guidare verso di essa un corpo dinamico. La simulazione può così risolvere i contatti durante il movimento del corpo. Il compromesso consiste nell’accettare intenzionalmente un ritardo o uno scostamento dal puntatore quando un altro oggetto ostacola il movimento. SpriteKit supporta l’applicazione di forze e SKPhysicsJointSpring collega due corpi fisici con un comportamento elastico. Sono elementi di base da calibrare in base all’applicazione. SKPhysicsBody, SKPhysicsJointSpring
Evita di assegnare continuamente le posizioni del puntatore aspettandoti che la fisica controlli in modo indipendente la stessa posizione. La sequenza dei fotogrammi documentata di SpriteKit esegue in fasi separate gli aggiornamenti dell’app, le azioni, la fisica e le callback successive. Una regola utile per la gestione è far aggiornare all’input l’obiettivo del trascinamento, lasciare che la logica della scena applichi il controller e lasciare che la fisica produca la posizione risultante del corpo. Gestione degli eventi del ciclo dei fotogrammi
Anche le callback dei contatti hanno un limite documentato. Apple afferma che vengono eseguite durante la fase di simulazione fisica, quando non è supportato modificare il mondo fisico e le modifiche ai corpi simulati hanno un comportamento indefinito. Registra le operazioni da eseguire nella callback e applica le modifiche alla fisica durante un aggiornamento successivo, seguendo lo schema di Apple basato su flag e aggiornamento. SKPhysicsContactDelegate
Come dovrebbe passare l’input di macOS tra i framework?
Scegli un solo gestore dell’input per l’area di lavoro. Un’implementazione Canvas può collegare un DragGesture di SwiftUI alla superficie di interazione. Una superficie AppKit personalizzata può gestire gli eventi del responder; mouseDragged(with:) riceve il movimento quando il pulsante sinistro del mouse è premuto. Evita che un gesto SwiftUI sovrapposto e un gestore AppKit spostino indipendentemente lo stesso elemento selezionato. DragGesture, mouseDragged(with:)
Per un collegamento tra AppKit e SpriteKit, converti le coordinate a ogni confine. NSEvent.locationInWindow è normalmente espresso nel sistema di coordinate di base della finestra associata. Converti quel punto nelle coordinate della vista che ospita la scena, quindi converti dalle coordinate della vista a quelle della scena. Apple fornisce convertPoint(fromView:) per il secondo passaggio e richiede che la scena sia stata presentata prima di chiamarlo. La posizione di un nodo è espressa nel sistema di coordinate del suo genitore, quindi per i nodi annidati può servire un’ulteriore conversione. locationInWindow, convertPoint(fromView:), position
Usa SpriteView per un’integrazione SwiftUI semplice. Se ti serve una vista AppKit personalizzata, NSViewRepresentable fornisce creazione, aggiornamenti, coordinamento e smantellamento. Lascia che sia SwiftUI a gestire il layout della vista rappresentata: Apple avverte esplicitamente di non assegnarle direttamente il frame o i bounds gestiti da SwiftUI. NSViewRepresentable
Conserva la scena in un contenitore stabile, invece di crearne una nuova ogni volta che SwiftUI ricalcola il contenuto della vista. Per Canvas, conserva allo stesso modo il modello degli elementi. La documentazione di SwiftUI sui modelli spiega come lo stato gestito mantenga un’istanza attraverso la ricreazione della vista; l’approccio Observation richiede macOS 14 o successivo. Tratta i comandi della scena e le istantanee del modello come trasferimenti espliciti, così due copie modificabili non competono per determinare le posizioni. Gestione dei dati del modello nell’app
Cosa dovresti testare prima di scegliere uno dei due approcci?
Usa una piccola area di lavoro sintetica per provare le interazioni più difficili prima di aggiungere rifiniture visive:
- Afferra una forma vicino al bordo e verifica che lo scostamento iniziale venga conservato.
- Trascinala lentamente contro un altro elemento, poi ripeti con un ampio spostamento del puntatore che lo attraversi.
- Trascinala verso un angolo con due forme vicine.
- Rilasciala mentre tocca un altro elemento e verifica il comportamento di assestamento previsto.
- Ridimensiona l’area di lavoro e verifica la selezione, i limiti e la conversione delle coordinate.
- Ricrea il contenuto SwiftUI circostante e verifica che le posizioni degli elementi persistano.
- Interrompi un trascinamento e verifica che la selezione e le eventuali impostazioni fisiche temporanee vengano ripulite.
In SpriteKit, attiva SKView.showsPhysics per verificare che la geometria dei corpi corrisponda all’area di collisione prevista. Per i corpi piccoli e veloci, valuta usesPreciseCollisionDetection; Apple ne documenta il costo computazionale maggiore e l’utilità nel rilevare collisioni che i normali controlli basati sui fotogrammi possono non rilevare. Non dovrebbe sostituire un controller di trascinamento ben progettato. SKView, usesPreciseCollisionDetection
Scegli Canvas quando le regole di posizionamento restano chiare, circoscritte e più semplici da esprimere direttamente. Scegli SpriteKit quando l’interazione fisica tra le forme fa parte del compito. In entrambe le implementazioni, un trascinamento affidabile su desktop parte da un solo elemento selezionato, conversioni esplicite delle coordinate e un unico componente responsabile di ogni aggiornamento della posizione.
Fonti
- Canvas — Apple
- DragGesture — Apple
- DragGesture.Value — Apple
- Scelta tra strutture e classi — Apple
- Informazioni su collisioni e contatti — Apple
- SKPhysicsBody — Apple
- SpriteView — Apple
- collisionBitMask — Apple
- contactTestBitMask — Apple
- isDynamic — Apple
- SKPhysicsJointSpring — Apple
- Gestione degli eventi del ciclo dei fotogrammi — Apple
- SKPhysicsContactDelegate — Apple
- mouseDragged(with:) — Apple
- locationInWindow — Apple
- convertPoint(fromView:) — Apple
- position — Apple
- NSViewRepresentable — Apple
- Gestione dei dati del modello nell’app — Apple
- SKView — Apple
- usesPreciseCollisionDetection — Apple