Wähle SwiftUI Canvas, wenn beim Ziehen explizite Platzierungsregeln gelten sollen, die du selbst implementieren kannst. Wähle SpriteKit, wenn Formen sich gegenseitig verschieben, abprallen oder durch Physik in Bewegung bleiben sollen. Überlege zuerst, was passieren soll, wenn eine gezogene Form auf eine andere trifft.
Dieser Leitfaden behandelt eine konkrete Aufgabe: die Wahl der Interaktionsarchitektur für eine Mac-App mit synthetischen, im Arbeitsspeicher gehaltenen Kreisen oder Rechtecken, die Nutzer innerhalb eines von der App verwalteten Arbeitsbereichs ziehen. Die Entscheidung hängt vom Kollisionsverhalten, der Eingabeverarbeitung und davon ab, welche Komponente die Position jeder Form steuert.
Was soll passieren, wenn zwei Formen aufeinandertreffen?
„Kollisionsreaktion“ kann verschiedene Anforderungen beschreiben. Eine Überlappung hervorzuheben, eine ungültige Position abzulehnen und zwei Objekte abprallen zu lassen, erfordert jeweils unterschiedliche Mechanismen. Apple unterscheidet ausdrücklich zwischen SpriteKit-Kontakten, die das Berühren von Körpern melden, und Kollisionen, die eine physikalische Reaktion berechnen und Impulse anwenden. About Collisions and Contacts
| Gewünschtes Verhalten | Praktischer Ausgangspunkt | Deine verbleibende Verantwortung |
|---|---|---|
| Eine Überlappung hervorheben, während der Zeiger eine Form frei platziert | Canvas mit benutzerdefinierten Geometrietests | Auswahl, Überlappungserkennung und visuelle Rückmeldung |
| Eine Form beim ersten Hindernis stoppen | Canvas mit benutzerdefinierten Bewegungsbeschränkungen | Den Bewegungspfad prüfen und die akzeptierte Position bestimmen |
| Benachbarte Formen verschieben und ein Auspendeln oder Abprallen ermöglichen | SpriteKit | Physikkonfiguration und eine Steuerung für das Ziehen |
| Jedem Element gewöhnliche SwiftUI-Steuerelemente und Gesten geben | Einzelne SwiftUI-Ansichten | Anwendungsspezifische Überlappungs- oder Bewegungsregeln |
Dies sind Architekturvorschläge, die sich aus den dokumentierten Funktionen ableiten, keine von Apple festgelegten Leistungsschwellen. Eine geringe Anzahl von Formen spricht nicht automatisch für Canvas, und eine große Anzahl erfordert nicht automatisch SpriteKit. Wähle zuerst das gewünschte Verhalten und miss anschließend die Implementierung mit einer repräsentativen Anzahl von Elementen.
Es gibt außerdem eine wichtige Einschränkung für die Interaktion: Exaktes Nachführen des Zeigers und strikte Überlappungsfreiheit können miteinander kollidieren. Bewegt sich der Zeiger über eine belegte Position, muss die App das Überlappen zulassen, das Element hinter dem Zeiger anhalten oder andere Elemente verschieben. Lege diese Entscheidung fest, bevor du den Renderer auswählst.
Was bietet Canvas, und was musst du selbst entwickeln?
Canvas bietet unmittelbares Zeichnen über einen Grafik-Kontext. Seine Kreise, Pfade und Symbole werden nicht zu eigenständig interaktiven Elementen. Apple weist außerdem darauf hin, dass Canvas keine Barrierefreiheit für einzelne gezeichnete Elemente bietet, auch nicht für Symbole, die als Ansichten bereitgestellt werden. Eine an Canvas angehängte Geste benötigt daher Anwendungslogik, um zu bestimmen, welches Element der Nutzer ausgewählt hat. Canvas
Für einen Editor mit expliziten Platzierungsregeln legst du die Formen in einem Modell ab und behandelst das Zeichnen als Darstellung dieses Modells. Jedes Element kann eine Kennung, einen Mittelpunkt, Abmessungen und eine Zeichenreihenfolge enthalten. Swift empfiehlt Strukturen standardmäßig für gewöhnliche Daten und Klassen, wenn gemeinsame Identität oder Framework-Vererbung erforderlich ist. Ein Werttyp für das Elementmodell und eine SpriteKit-Szenenunterklasse erfüllen unterschiedliche Aufgaben. Choosing Between Structures and Classes
Ein Canvas-Ziehvorgang kann so ablaufen:
- Prüfe zu Beginn der Geste die anfängliche Zeigerposition anhand der Elemente in umgekehrter Zeichenreihenfolge und wähle das erste passende Element aus.
- Behalte während der gesamten Geste die Kennung des ausgewählten Elements und dessen ursprünglichen Mittelpunkt bei.
- Berechne jeden vorgeschlagenen Mittelpunkt aus dem ursprünglichen Mittelpunkt plus der gesamten Verschiebung der Geste.
- Wende die Grenzen des Arbeitsbereichs und die Kollisionsregeln an, um einen akzeptierten Mittelpunkt zu ermitteln.
- Aktualisiere das Modell und lasse Canvas es neu zeichnen.
- Hebe die Auswahl auf, wenn die Geste endet, und lege fest, wie unterbrochene Interaktionen bereinigt werden.
Der Unterschied zwischen der gesamten und der schrittweisen Bewegung ist wichtig. SwiftUI definiert translation als Bewegung seit Beginn der Geste. Wird diese Gesamtbewegung wiederholt zum bereits aktualisierten Mittelpunkt addiert, summiert sich die Bewegung zu stark auf. Verwende jedes Mal den gespeicherten Ausgangsmittelpunkt. SwiftUI stellt über den Gestenwert startLocation, location und translation bereit und lässt dich den Koordinatenraum der Geste festlegen. DragGesture.Value, DragGesture
Behalte die Auswahl während des Ziehens bei. Wird die anfängliche Auswahlprüfung bei jeder Zeigerbewegung wiederholt, kann das ausgewählte Element wechseln, sobald der Zeiger eine andere Form überquert. Wenn du außerdem den ursprünglichen Versatz zwischen Zeiger und Mittelpunkt beibehältst, springt die Form nicht plötzlich so, dass ihr Mittelpunkt unter dem Zeiger liegt. Das sind Regeln für das Anwendungsdesign und keine Funktionen, die Canvas automatisch bereitstellt.
Beispiel: Überlappungserkennung oder blockierte Bewegung
Betrachten wir zwei beispielhafte Kreise mit jeweils 20 Punkten Radius. Kreis A beginnt bei (100, 100), und der stationäre Kreis B befindet sich bei (200, 100). Der Nutzer fordert für A den Mittelpunkt (190, 100) an. Der Abstand zwischen ihren Mittelpunkten wäre dann 10 Punkte, die Summe ihrer Radien beträgt 40. Dieser Vorschlag führt zu einer Überlappung.
Für eine Überlappungsanzeige wird der Vorschlag akzeptiert und die Darstellung geändert. Bei einer blockierten horizontalen Bewegung liegt die erste Berührungsposition bei (160, 100): Die Mittelpunkte sind dann 40 Punkte voneinander entfernt. Diese Berechnung setzt voraus, dass B fest bleibt, beide Kreise ihre Größe behalten und A sich auf einer horizontalen Bahn bewegt.
Angenommen, eine Zeigeraktualisierung fordert nun (260, 100) an. Eine Prüfung nur dieses Endpunkts erkennt keine Überlappung, obwohl der Bewegungspfad durch B verläuft. Eine Implementierung, die das Ziehen am ersten Hindernis stoppt, benötigt eine Schnittprüfung entlang der gesamten Bewegung oder eine andere ausdrückliche Methode, um den dazwischenliegenden Pfad zu prüfen. Nur überlappende Endpunkte abzulehnen, setzt die Regel „am ersten Hindernis stoppen“ nicht um.
Dieses Beispiel zeigt, warum die Kollisionsreaktion genau festgelegt werden muss. Für eine Funktion, die Überlappungen hervorhebt, reichen möglicherweise einige einfache Geometrietests an den Endpunkten. Wenn Formen an Hindernissen entlanggleiten, mehrere Kontakte gleichzeitig verarbeiten oder benachbarte Elemente bewegen sollen, wird der eigene Lösungsmechanismus deutlich umfangreicher. Canvas bleibt eine Möglichkeit zum Zeichnen, aber deine Anwendung ist für dieses zusätzliche Verhalten zuständig.
Wann eignet sich SpriteKit besser?
SpriteKit ist attraktiv, wenn die Kollisionsreaktion aus einer Simulation entstehen soll. Wird einem Knoten ein SKPhysicsBody zugewiesen, kann die Engine Schwerkraft, Reibung und Kollisionen berechnen; die Simulation aktualisiert anschließend die Positionen und Ausrichtungen der Knoten. Sie unterstützt Volumenkörper für Objekte und Kantenelemente für Begrenzungen. SKPhysicsBody
Verwende für einen ersten Prototyp zwei getrennte Kreise und eine rechteckige Begrenzung des Arbeitsbereichs. Weise den Kreisen passende kreisförmige Körper zu, deaktiviere die Schwerkraft für einen Arbeitsbereich wie auf einer Tischplatte und überlege, ob Drehungen für die Interaktion relevant sind. Konfiguriere Reibung, Rückprall und Dämpfung bewusst, statt dich auf die Standardwerte zu verlassen. Diese Eigenschaften bestimmen unterschiedliche Aspekte des physikalischen Verhaltens. SKPhysicsBody
Mit SpriteView kann SwiftUI die Szene direkt einbetten. Apple dokumentiert es als SwiftUI-Ansicht, die eine SpriteKit-Szene darstellt. Laut Dokumentation ist es auf dem Mac ab macOS 11 verfügbar, Canvas ab macOS 12. Das Mindestziel für die Bereitstellung gehört daher zur Entscheidung, auch wenn das Interaktionsverhalten das wichtigste Kriterium bleibt. SpriteView, Canvas
Kollisionsreaktionen und Benachrichtigungen getrennt konfigurieren
Weise deinen Formen und der Begrenzung Kategorien zu und konfiguriere dann die beiden Interaktionsmasken ausdrücklich:
categoryBitMaskgibt an, zu welchen Kategorien ein Körper gehört.collisionBitMasklegt fest, welche Kategorien diesen Körper physisch beeinflussen können.contactTestBitMaskfordert Kontaktbenachrichtigungen für ausgewählte Kategorien an.
Kollisionsmasken werden für jeden Körper unabhängig ausgewertet. Damit zwei dynamische Formen gewöhnlich gegenseitig aufeinander reagieren, muss jeder Körper so konfiguriert sein, dass er auf die Kategorie des jeweils anderen reagiert. Für die Kontaktmeldung gilt eine andere Regel: Ein passender Vergleich von Kategorie und Kontaktmaske in einer der beiden Richtungen kann eine Benachrichtigung auslösen. Benachrichtigungen allein zu aktivieren, richtet keine physische Blockierung ein. collisionBitMask, contactTestBitMask
Wenn Körper physisch aufeinander reagieren, aber kein Rückruf erfolgt, überprüfe die Kontaktmaske und den Kontaktdelegaten der Physikwelt. Werden Rückrufe ausgelöst, während Formen aneinander vorbeigleiten, überprüfe die Kollisionsmasken und die Bewegungssteuerung. Mit diesen Prüfungen lassen sich die Konfiguration der Benachrichtigungen und das eigentliche Bewegungsproblem getrennt untersuchen.
Wer steuert die Position während des Ziehens?
Für das Ziehen in SpriteKit ist eine ausdrückliche Steuerung erforderlich. Die Physik-Engine folgt nicht von selbst dem Zeiger. Wähle je nach zuvor festgelegtem Verhalten zwischen direkter Platzierung und simulationsgesteuerter Bewegung.
Bei direkter Platzierung weist der Anwendungscode dem gezogenen Knoten seine Position zu. Das eignet sich, wenn das Element exakt der Zeigerposition folgen muss, legt aber nicht automatisch einen physikalisch aufgelösten Pfad zwischen aufeinanderfolgenden Positionen fest. Wird isDynamic deaktiviert, ignoriert der Körper Kräfte und Impulse; dadurch entsteht kein Ziehmechanismus. Wenn du diese Eigenschaft vorübergehend änderst, lege fest, wann sie wiederhergestellt wird und ob beim Loslassen die Bewegung beibehalten oder gelöscht wird. isDynamic
Bei simulationsgesteuertem Ziehen wird die Zeigerposition als Ziel gespeichert, und Kräfte oder eine federbasierte Steuerung lenken einen dynamischen Körper dorthin. Die Simulation kann dann Kontakte während der Bewegung auflösen. Dabei kann der Körper absichtlich verzögert oder vom Zeiger weg verschoben werden, wenn ein anderes Objekt die Bewegung behindert. SpriteKit unterstützt das Anwenden von Kräften, und SKPhysicsJointSpring verbindet zwei Physikkörper mit einer Federwirkung. Diese Bausteine müssen für die jeweilige Anwendung abgestimmt werden. SKPhysicsBody, SKPhysicsJointSpring
Weise nicht fortlaufend Zeigerpositionen zu, während du erwartest, dass die Physik gleichzeitig dieselbe Position unabhängig steuert. SpriteKit führt in seiner dokumentierten Frame-Sequenz Anwendungsaktualisierungen, Aktionen, Physik und nachfolgende Rückrufe in getrennten Phasen aus. Eine sinnvolle Zuständigkeitsregel lautet: Die Eingabe aktualisiert das Ziehziel, die Szenenlogik wendet die Steuerung an und die Physik berechnet die resultierende Position des Körpers. Responding to Frame-Cycle Events
Für Kontakt-Rückrufe gibt es außerdem eine dokumentierte Einschränkung. Laut Apple werden sie während des Physiksimulationsschritts ausgeführt. In dieser Phase darf die Physikwelt nicht verändert werden, und Änderungen an simulierten Körpern führen zu undefiniertem Verhalten. Vermerke im Rückruf anstehende Aufgaben und wende physikalische Änderungen bei einer späteren Aktualisierung an, wie im von Apple empfohlenen Muster mit Markierung und anschließender Aktualisierung. SKPhysicsContactDelegate
Wie sollte die macOS-Eingabe die Framework-Grenzen überschreiten?
Lege einen einzigen Eingabeverantwortlichen für den Arbeitsbereich fest. Eine Canvas-Implementierung kann eine SwiftUI-DragGesture an ihre Interaktionsfläche anhängen. Eine benutzerdefinierte AppKit-Fläche kann Ereignisse über die Responder-Methoden verarbeiten; mouseDragged(with:) empfängt Bewegungen bei gedrückter linker Maustaste. Vermeide es, dass eine darüberliegende SwiftUI-Geste und ein AppKit-Handler dasselbe ausgewählte Element unabhängig voneinander bewegen. DragGesture, mouseDragged(with:)
Bei einer AppKit-zu-SpriteKit-Brücke werden die Koordinaten an jeder Grenze umgerechnet. NSEvent.locationInWindow wird gewöhnlich im Basiskoordinatensystem des zugehörigen Fensters angegeben. Wandle diesen Punkt in die Koordinaten der Host-Ansicht und dann von den Ansichtskoordinaten in die Szenenkoordinaten um. Apple stellt für Letzteres convertPoint(fromView:) bereit und verlangt, dass die Szene präsentiert wird, bevor der Aufruf erfolgt. Die Position eines Knotens wird im Koordinatensystem seines übergeordneten Knotens angegeben, sodass für verschachtelte Knoten eine weitere Umrechnung erforderlich sein kann. locationInWindow, convertPoint(fromView:), position
Verwende SpriteView für eine einfache Einbettung in SwiftUI. Wenn du eine benutzerdefinierte AppKit-Ansicht benötigst, bietet NSViewRepresentable Erzeugung, Aktualisierung, Koordination und Abbau. Überlasse SwiftUI das Layout der dargestellten Ansicht: Apple warnt ausdrücklich davor, deren verwalteten Frame oder deren Bounds selbst festzulegen. NSViewRepresentable
Bewahre die Szene in einem stabilen Besitzer auf, statt bei jeder Neuberechnung des SwiftUI-Ansichtsinhalts eine neue Szene zu erzeugen. Für Canvas solltest du das Elementmodell ebenfalls beibehalten. In der Dokumentation zu Modelldaten in SwiftUI wird erläutert, wie verwalteter Zustand eine Instanz über die Neuerstellung einer Ansicht hinweg erhält; Apples Observation-Ansatz setzt macOS 14 oder neuer voraus. Behandle Szenenbefehle und Modell-Snapshots als gezielte Übergaben, damit nicht zwei veränderbare Kopien gleichzeitig um die Kontrolle über die Positionen konkurrieren. Managing model data in your app
Was solltest du testen, bevor du dich für einen Ansatz entscheidest?
Teste die schwierigen Interaktionen in einem kleinen synthetischen Arbeitsbereich, bevor du den visuellen Feinschliff hinzufügst:
- Greife nahe am Rand einer Form und prüfe, ob der anfängliche Versatz erhalten bleibt.
- Ziehe langsam in ein anderes Element hinein und wiederhole den Vorgang dann mit einer großen Zeigerbewegung darüber hinweg.
- Ziehe in Richtung einer Ecke mit zwei benachbarten Formen.
- Lass die Maustaste los, während das Element eine andere Form berührt, und prüfe das gewünschte Auspendeln.
- Ändere die Größe des Arbeitsbereichs und überprüfe die Auswahl, Begrenzungen und Koordinatenumrechnung.
- Erzeuge den umgebenden SwiftUI-Inhalt neu und bestätige, dass die Elementpositionen erhalten bleiben.
- Unterbrich einen Ziehvorgang und überprüfe, ob die Auswahl und vorübergehende Physikeinstellungen bereinigt werden.
Aktiviere in SpriteKit SKView.showsPhysics, um zu prüfen, ob die Körpergeometrie dem vorgesehenen Kollisionsbereich entspricht. Werte bei kleinen, schnellen Körpern usesPreciseCollisionDetection aus. Apple dokumentiert die höheren Rechenkosten und die Verwendung zur Erkennung von Kollisionen, die bei gewöhnlichen Prüfungen pro Bild verloren gehen können. Eine stimmige Steuerung für das Ziehen wird dadurch nicht ersetzt. SKView, usesPreciseCollisionDetection
Wähle Canvas, solange deine Platzierungsregeln klar, begrenzt und direkt auszudrücken sind. Wähle SpriteKit, wenn die physikalische Interaktion zwischen den Formen Teil der Aufgabe ist. Bei beiden Ansätzen beginnt zuverlässiges Ziehen auf dem Desktop mit einem ausgewählten Element, ausdrücklichen Koordinatenumrechnungen und einer einzigen Komponente, die für jede Positionsaktualisierung zuständig ist.
Quellen
- Canvas — Apple
- DragGesture — Apple
- DragGesture.Value — Apple
- Choosing Between Structures and Classes — Apple
- About Collisions and Contacts — Apple
- SKPhysicsBody — Apple
- SpriteView — Apple
- collisionBitMask — Apple
- contactTestBitMask — Apple
- isDynamic — Apple
- SKPhysicsJointSpring — Apple
- Responding to Frame-Cycle Events — Apple
- SKPhysicsContactDelegate — Apple
- mouseDragged(with:) — Apple
- locationInWindow — Apple
- convertPoint(fromView:) — Apple
- position — Apple
- NSViewRepresentable — Apple
- Managing model data in your app — Apple
- SKView — Apple
- usesPreciseCollisionDetection — Apple