Damit eine synthetische Karte während eines nativen macOS-Ziehvorgangs intakt erscheint, versehen Sie ihren äußeren Container mit einer einzigen Drag-Quelle und stellen Sie die vollständige Karte als Vorschau dar. Verwenden Sie AppKit, wenn Sie den anfänglichen Rahmen der Vorschau und den Zeiger-Offset explizit steuern müssen.
Diese Anleitung behandelt das Ziehen einer einzelnen Karte aus dem Arbeitsspeicher zwischen zwei Bereichen in Ihrer eigenen Mac-App. Das praktische Ziel ist, Formen, Text, Abstände und Identität der Karte beizubehalten und zugleich die Zuständigkeit für Gestenbehandlung und Modellaktualisierungen klar zu regeln.
Welcher Teil des Ziehvorgangs muss geändert werden?
Apple trennt die übertragenen Daten von der visuellen Darstellung, die während des Ziehens angezeigt wird. SwiftUI’s draggable(_:preview:) erstellt beim Beginn des Ziehvorgangs eine Darstellung Ihrer Vorschauansicht. Die dokumentierte Vorschau wird über der Quellansicht zentriert. Die Quellansicht selbst wird nicht übertragen. Siehe Apples draggable(_:preview:)-Dokumentation.
Verwenden Sie diese Entscheidungshilfe, um die tatsächlich benötigte Steuerungsmöglichkeit zu bestimmen:
| Gewünschtes Verhalten | Unterstützter Ausgangspunkt |
|---|---|
| Beschriftung, Formen und Hintergrund der Karte zusammenhalten | Eine Drag-Quelle am äußeren Container mit einer vollständigen benutzerdefinierten Vorschau |
| Kontur während des Anhebens anpassen | contentShape(.dragPreview, shape) |
| Ändern, welcher Teil der Karte das Ziehen auslösen kann | contentShape(.interaction, shape) |
| Eine Karte in einen anderen Bereich übernehmen | Payload am Ziel validieren und das Modell aktualisieren |
| Das anfängliche Rechteck des Drag-Bilds und die Zeigerposition explizit festlegen | Eine von AppKit verwaltete Sitzung mit NSDraggingItem |
| Ein Element auf einer Canvas kontinuierlich bewegen, ohne native Drop-Ziele | Eine lokale DragGesture-Interaktion |
Die Unterscheidung zwischen den beiden Inhaltsformen ist wichtig: Apple dokumentiert, dass .dragPreview die Form der Vorschau beeinflusst, während .interaction die Trefferprüfung steuert. Eine Änderung allein an der Vorschaukontur vergrößert nicht den Bereich, der das Ziehen auslöst. Siehe ContentShapeKinds.dragPreview.
Ein lokales DragGesture meldet eine Ziehbewegung über eine Gestenfolge. Wählen Sie diesen Ansatz, wenn die Aufgabe darin besteht, die Position auf einer Canvas zu aktualisieren. Für die native Drop-Aufgabe zwischen Bereichen in diesem Artikel lassen Sie draggable die Drag-Gesten einrichten. Siehe Apples DragGesture-Referenz und Anleitung zu Drag-Quellen.
Eine Vorschau für die gesamte synthetische Karte erstellen
Platzieren Sie den Drag-Modifikator auf dem kleinsten Container, der alles umfasst, was gemeinsam bewegt werden soll. Bei einer Karte aus einem Kreis, zwei Textzeilen und einem abgerundeten Hintergrund ist das der vollständige Kartencontainer. Separate Drag-Quellen an den untergeordneten Elementen würden separate Interaktionen definieren.
Verwenden Sie für Quelle und Vorschau dieselbe Darstellungsansicht. Geben Sie dieser Ansicht explizite Abmessungen und einen eigenen Hintergrund, statt sich auf Formatierungen zu verlassen, die von einem umgebenden Bereich übernommen werden. Dies ist eine aus dem Verhalten gerenderter Vorschauen abgeleitete Gestaltungsempfehlung: Eine in sich geschlossene Darstellung erleichtert es, beide Erscheinungsbilder aufeinander abzustimmen.
Vollständiges SwiftUI-Beispiel
Erstellen Sie eine macOS-SwiftUI-App mit einem Deployment Target von macOS 13 oder neuer. Ersetzen Sie den generierten App-Einstiegspunkt und die Inhaltsansicht durch diese einzelne Datei und lassen Sie im Target genau eine @main-Deklaration stehen. Sowohl der Modifikator für benutzerdefinierte Vorschauen als auch die hier verwendete Destination-Überladung sind laut Dokumentation ab macOS 13 verfügbar. Siehe draggable(_:preview:) und dropDestination(for:action:isTargeted:).
import Foundation
import SwiftUI
@main
struct IntactDragDemoApp: App {
var body: some Scene {
WindowGroup {
BoardView()
}
}
}
private struct CardFace: View {
var body: some View {
HStack(spacing: 14) {
Circle()
.fill(Color.orange)
.frame(width: 36, height: 36)
VStack(alignment: .leading, spacing: 6) {
Text("Tile A")
.font(.headline)
Text("Circle and label")
.font(.caption)
}
.foregroundStyle(Color.white)
}
.padding(16)
.frame(width: 220, height: 100, alignment: .leading)
.background(Color.indigo)
.clipShape(RoundedRectangle(cornerRadius: 16))
}
}
private struct BoardView: View {
@State private var token = UUID().uuidString
@State private var isOnRight = false
var body: some View {
VStack(spacing: 20) {
Text("Drag the card between panels")
.font(.headline)
HStack(spacing: 24) {
panel(right: false)
panel(right: true)
}
}
.padding(24)
}
private func panel(right: Bool) -> some View {
VStack(spacing: 16) {
Text(right ? "Right" : "Left")
.font(.headline)
ZStack {
if isOnRight == right {
CardFace()
.contentShape(
.interaction,
RoundedRectangle(cornerRadius: 16)
)
.contentShape(
.dragPreview,
RoundedRectangle(cornerRadius: 16)
)
.draggable(token) {
CardFace()
}
} else {
Text("Drop here")
.foregroundStyle(.secondary)
}
}
.frame(width: 240, height: 120)
}
.frame(width: 260, height: 180)
.background(
RoundedRectangle(cornerRadius: 20)
.fill(Color.gray.opacity(0.12))
)
.contentShape(Rectangle())
.dropDestination(for: String.self) { items, _ in
guard items == [token] else {
return false
}
isOnRight = right
return true
}
}
}
Dieses Beispiel verwendet ausschließlich synthetische Grafiken und eine generierte Kennung im Arbeitsspeicher. String entspricht bereits dem Protokoll Transferable und benötigt daher keine benutzerdefinierte Übertragungsrepräsentation. Apple dokumentiert diese integrierte Konformität unter Eine Ansicht als Drag-Quelle verwenden.
Die erwartete Interaktion ist einfach: Ziehen Sie Tile A in den anderen Bereich, lassen Sie die Maustaste los und ziehen Sie die Kachel anschließend zurück. Das vollständige CardFace liefert die bewegte Vorschau. Das Ziel akzeptiert genau ein passendes Token und ändert den einzelnen Speicherortwert. Die Rückgabe von true meldet einen erfolgreichen Drop; die Rückgabe von false weist ihn gemäß dem dokumentierten Vertrag der Zielaktion zurück.
Dies ist ein vollständiges Beispiel für ein macOS-App-Target. Es wurde hier nicht kompiliert oder ausgeführt, da der Forschungsumgebung eine Swift-Toolchain und eine macOS-Laufzeitumgebung fehlen.
Visuelle Kontinuität und Modellzuständigkeit getrennt halten
Im Beispiel bleibt die ursprüngliche Karte sichtbar, bis ein akzeptierter Drop isOnRight ändert. Wird außerhalb der Bereiche losgelassen, ändert sich das Modell nicht. Wird die Karte zurück in den bereits belegten Bereich gezogen, wird derselbe Speicherort erneut zugewiesen. Ein nicht passendes Token oder mehrere Elemente werden vor einer Zustandsänderung zurückgewiesen.
Dieses Verhalten verhindert, dass die Karte zu Beginn des Ziehvorgangs entfernt und nach einem Abbruch wiederhergestellt werden muss. Die Vorschau sorgt für visuelle Kontinuität; das Ziel ist für die Änderung des Speicherorts zuständig. Wenden Sie bei einem größeren Board dasselbe Muster auf eine stabile Elementkennung und eine maßgebliche Sammlung an.
Die Wertsemantik von Swift erklärt, weshalb die Übergabe einer Payload das ursprüngliche Modell nicht automatisch verschiebt. Strukturen und Strings haben Wertsemantik; durch Zuweisen oder Übergeben wird ein Element in der Sammlung Ihrer App nicht implizit an einen anderen Ort verschoben. Ihre Modellaktualisierung muss diese Verschiebung ausdrücken. Siehe Strukturen und Klassen.
Unterscheiden Sie außerdem eine Verschiebung auf Anwendungsebene vom nativen Ziehvorgang. Das Beispiel ändert nach einem akzeptierten Drop einen gemeinsamen Board-Zustand; es konfiguriert keine native Verschiebungsvereinbarung und garantiert keinen Verschiebungscursor.
Für macOS 26 und neuer dokumentiert Apple DragConfiguration zur Deklaration unterstützter Aktionen und onDragSessionUpdated(_:) zur Beobachtung der ausgeführten Aktion. Ein Ziel wählt die Aktion aus, die es ausführt. Wenn Sie nach einer nativen Verschiebung die Quelle entfernen, koordinieren Sie dies mit der Zielverarbeitung, damit dasselbe gemeinsame Element nicht zweimal entfernt wird. Siehe DragConfiguration und onDragSessionUpdated(_:).
Wann sollte AppKit die Drag-Sitzung verwalten?
Verwenden Sie AppKit, wenn das Abnahmekriterium ein explizit positioniertes Drag-Bild umfasst, etwa wenn der Zeiger beim Aufnehmen über demselben Punkt innerhalb der Karte bleiben soll. SwiftUIs dokumentierter Modifikator für benutzerdefinierte Vorschauen erhält eine Payload und eine Vorschauansicht und beschreibt eine zentrierte Vorschau. AppKit stellt den Rahmen des Drag-Elements direkt bereit.
Gehen Sie bei einer AppKit-Implementierung wie folgt vor:
- Lassen Sie die Quell-
NSViewdas tatsächliche Maus-Down-Ereignis empfangen und verfolgen, ob daraus ein Ziehvorgang wird. - Rendern Sie die vollständige synthetische Karte in ein einzelnes
NSImageund erstellen Sie mit dessen Pasteboard-Writer ein einzelnesNSDraggingItem. - Legen Sie mit
setDraggingFrame(_:contents:)das Bild und sein anfängliches Rechteck fest. - Geben Sie dieses Rechteck im Koordinatensystem der auslösenden Ansicht an und rufen Sie anschließend
beginDraggingSession(with:event:source:)mit dem auslösenden Maus-Down-Ereignis und IhremNSDraggingSourceauf. - Lassen Sie das Ziel den Drop validieren und übernehmen. Verwenden Sie den Abschluss-Callback der Quelle, um die Sitzung aufzuräumen.
Apple legt fest, dass die Mausposition des auslösenden Ereignisses den Versatz des gezogenen Symbols bestimmt und dass das anfängliche Rechteck jedes Elements in den Koordinaten der Quellansicht angegeben werden muss. Halten Sie das Kartenrechteck und die Ereignisposition in einem einheitlichen Koordinatensystem. Siehe beginDraggingSession(with:event:source:).
setDraggingFrame(_:contents:) ist die dokumentierte Komfortmethode für ein Drag-Element, das durch ein einzelnes Bild dargestellt wird. Eine synthetische Karte kann mehrere gezeichnete Elemente enthalten und dennoch durch dieses eine Bild dargestellt werden. Siehe setDraggingFrame(_:contents:).
Ereignis- und Sitzungsgrenzen bewahren
Wählen Sie für den interaktiven Bereich der Karte genau einen Auslöser für den Ziehvorgang. Als Implementierungsregel sollten Sie vermeiden, einen AppKit-Sitzungsstart über einer aktiven SwiftUI-draggable-Quelle für denselben Bereich einzurichten. Beide würden versuchen, die Interaktion zu verwalten. Erzeugen Sie keine Mausereignisse, um eine unklare Zuständigkeit für Gesten auszugleichen.
Sie können die AppKit-Quelle mit NSViewRepresentable einbetten. Belassen Sie die native Ereignisbehandlung in der zugehörigen NSView und übermitteln Sie akzeptierte Modelländerungen über Bindings oder einen Coordinator. SwiftUI steuert das Layout der dargestellten Ansicht; Apple weist ausdrücklich darauf hin, dass Sie frame oder bounds dieser verwalteten Ansicht nicht direkt setzen dürfen. Das Bildrechteck eines Drag-Elements ist eine davon getrennte Angelegenheit. Siehe NSViewRepresentable.
Behalten Sie ein NSDraggingItem nicht und ändern Sie es später. Apple dokumentiert dessen begrenzte Lebensdauer: Ändern Sie Drag-Elemente nach dem Erstellen der Sitzung über die Sitzungsaufzählung und verwenden Sie jedes aufgezählte Element ausschließlich innerhalb seines Callbacks. Änderungen am Ziel können zudem Änderungen am Bild durch die Quelle überschreiben, solange sich der Ziehvorgang über diesem Ziel befindet. Siehe NSDraggingItem und enumerateDraggingItems(options:for:classes:searchOptions:using:).
Wie lässt sich eine weiterhin fehlerhafte Vorschau diagnostizieren?
Prüfen Sie den Fehler in der Phase, in der er auftritt:
| Symptom | Erste Prüfung |
|---|---|
| Nur der Kreis oder der Text wird bewegt | Platzieren Sie eine einzelne Drag-Quelle auf dem vollständigen Kartencontainer. |
| Die Abmessungen der Vorschau unterscheiden sich von der Karte | Verwenden Sie dieselbe Darstellungsansicht und explizite Abmessungen wieder. |
| Die Kontur beim Aufnehmen unterscheidet sich vom bewegten Bild | Prüfen Sie .dragPreview getrennt von der Closure für die benutzerdefinierte Vorschau. |
| Leerer Raum löst unerwartet den Ziehvorgang aus | Prüfen Sie .interaction und die Grenzen des Quellcontainers. |
| Das AppKit-Bild springt beim Aufnehmen | Prüfen Sie das auslösende Maus-Down-Ereignis und die Koordinaten der Quellansicht. |
| Spätere Änderungen am AppKit-Bild zeigen keine Wirkung | Nehmen Sie sie innerhalb der Sitzungsaufzählung vor. |
| Die Karte verschwindet nach einem Abbruch | Verlagern Sie die Modelländerung in die Verarbeitung akzeptierter Drops und prüfen Sie die Bereinigung. |
Prüfen Sie Abbruch, einen akzeptierten Drop, einen Drop zurück auf den ursprünglichen Bereich und eine abgewiesene Payload jeweils separat. Die nützliche Invariante ist, dass während des gesamten Vorgangs ein einziger Modellstandort maßgeblich bleibt. Beginnen Sie mit der vollständigen SwiftUI-Vorschau und verwenden Sie erst dann eine von AppKit verwaltete Quelle, wenn die erforderliche Bildpositionierung deren explizite Rahmen- und Ereignissteuerung voraussetzt.
Quellen
draggable(_:preview:) — Apple Developer Documentation.
Eine Ansicht als Drag-Quelle verwenden — Apple Developer Documentation.
dragPreview — Apple Developer Documentation.
DragGesture — Apple Developer Documentation.
dropDestination(for:action:isTargeted:) — Apple Developer Documentation.
Strukturen und Klassen — Swift.org.
DragConfiguration — Apple Developer Documentation.
onDragSessionUpdated(_:) — Apple Developer Documentation.
beginDraggingSession(with:event:source:) — Apple Developer Documentation.
setDraggingFrame(_:contents:) — Apple Developer Documentation.
NSViewRepresentable — Apple Developer Documentation.
NSDraggingItem — Apple Developer Documentation.
enumerateDraggingItems(options:for:classes:searchOptions:using:) — Apple Developer Documentation.