Um synthetische Formen zwischen zwei Bereichen einer Mac-App zu kopieren, beginne mit typisiertem Drag-and-Drop in SwiftUI. Verwende Item Provider, wenn eine vorhandene Provider-Schnittstelle oder ein älteres macOS-Ziel dies erfordert. Definiere die Nutzdaten, Annahmeregeln und die Zuständigkeit für das Modell, bevor du dich für eine der APIs entscheidest.
Festlegen, was ein erfolgreicher Drop bedeutet
Die Aufgabe für Nutzer ist hier klar umrissen: Jemand soll einen synthetischen Kreis oder ein Quadrat aus einer Palette in eine Sammlung innerhalb derselben Mac-App ziehen können. Das Ziel erstellt ein neues Element; die Palette bleibt unverändert. Alles befindet sich im Arbeitsspeicher, und beim Schließen des Fensters wird die Sammlung verworfen.
Apple empfiehlt übertragbare Elemente für Swift-Drag-and-Drop. Die Dokumentation unterstützt auch Provider-basierte Modifikatoren. Die Wahl hängt also von den Bereitstellungsanforderungen und der zu integrierenden Schnittstelle ab. Drag and drop — Apple.
Formuliere den Übertragungsvertrag, bevor du die Ansichten implementierst:
| Vertragsfrage | Entscheidung für dieses Beispiel |
|---|---|
| Was wird über die Grenze übertragen? | Ein versionierter Wert, der eine Formart beschreibt |
| Wer kann die Darstellung empfangen? | Nur die Quell-App |
| Was bewirkt ein erfolgreicher Drop? | Ein neu identifiziertes Sammlungselement wird hinzugefügt |
| Wer ist für die Änderung zuständig? | Der Sammlungszustand der Zielansicht |
| Was geschieht bei Abbruch oder Ablehnung? | Keine Modelländerung |
| Welche Bedeutung hat die Position des Drops? | Hier keine; angenommene Elemente werden der Reihe nach angefügt |
Dies sind Entscheidungen zum Anwendungsdesign. Eine Drag-Vorschau stellt die Nutzdaten dar, während das Ziel entscheidet, wie diese Nutzdaten das Modell verändern. Durch die Trennung dieser Zuständigkeiten lässt sich der Übertragungsweg ändern, ohne die Regeln der Sammlung neu schreiben zu müssen.
Die kleinste API wählen, die den Vertrag ausdrückt
| Situation | Ausgangspunkt | Begründung |
|---|---|---|
| SwiftUI-Quelle und -Ziel, macOS 13 oder neuer | Transferable, .draggable und typisiertes .dropDestination |
Den akzeptierten Modelltyp direkt ausdrücken |
Vorhandene NSItemProvider-Integration |
.onDrag und .onDrop |
Explizite Registrierung und das Laden der Darstellung beibehalten |
| Zielversion älter als macOS 13 | Verfügbarkeit der Provider-Modifikatoren für diese Zielversion prüfen | Die typisierte Basis erfordert macOS 13; die weiter unten besprochene Provider-Überladung erfordert macOS 11 |
| Vorhandene AppKit-Ansicht verwaltet die Mausinteraktion | AppKit-Drag-and-Drop-APIs | Ereignisbehandlung und Lebenszyklus der Drag-Sitzung der Ansicht beibehalten |
Apple dokumentiert Transferable und das ursprüngliche typisierte Drop-Ziel ab macOS 13. Die Provider-basierte Überladung von onDrop(of:isTargeted:perform:), die ein Array von UTType-Werten annimmt, ist ab macOS 11 verfügbar. Prüfe die konkrete Überladung, statt anzunehmen, dass für alle Drag-and-Drop-APIs dieselbe Mindestversion gilt. Transferable, dropDestination(for:action:isTargeted:), onDrop(of:isTargeted:perform:).
Die Beschränkung auf die eigene App ist mit beiden Ansätzen möglich. Bei einer typisierten Darstellung setze .visibility(.ownProcess); Apple definiert diese Sichtbarkeit als Beschränkung auf die Quell-App. Die Wahl eines Item Providers ist also nicht allein deshalb nötig, um diese Übertragung auf die App zu beschränken. visibility(_:), ownProcess.
Die typisierte Kopierinteraktion erstellen
Dieses vollständige Beispiel besteht aus zwei Swift-Dateien in einem neuen macOS-SwiftUI-App-Ziel. Setze das Bereitstellungsziel auf macOS 13 oder neuer und verwende für diese Basis den Swift-Sprachmodus 5. Ersetze den generierten App-Einstiegspunkt durch die zweite Datei; es darf nur eine @main-Deklaration geben. Der Code wurde geprüft, hier jedoch nicht kompiliert, da in der verfügbaren Shell keine Swift-Toolchain vorhanden ist.
Nutzdaten und Inhaltstyp deklarieren
Füge der Informations-Property-Liste des Ziels eine exportierte Typdeklaration hinzu. Erstelle im Property-List-Editor von Xcode UTExportedTypeDeclarations als Array mit einem Dictionary. Setze darin UTTypeIdentifier auf com.example.shape-transfer, UTTypeDescription auf Synthetic Shape Transfer und UTTypeConformsTo auf ein Array mit public.data. Dieser Bezeichner dient nur als Beispiel; verwende für deine App einen eindeutigen Bezeichner.
Die Swift-Deklaration und der Bezeichner in der Property-Liste müssen übereinstimmen. Für benutzerdefinierte Uniform Types verlangt Apple entsprechende Einträge in der Property-Liste und erläutert exportierte Deklarationen für Formate, die eine App definiert. Dieses Beispiel überträgt Daten statt Dateien und benötigt daher keine Dateinamenerweiterung. Defining file and data types for your app.
Erstelle ShapePayload.swift:
import Foundation
import CoreTransferable
import UniformTypeIdentifiers
extension UTType {
static let shapeTransfer = UTType(
exportedAs: "com.example.shape-transfer",
conformingTo: .data
)
}
enum ShapeKind: String, Codable, Sendable {
case circle = "Circle"
case square = "Square"
}
struct ShapePayload: Codable, Sendable, Transferable {
let version: Int
let kind: ShapeKind
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .shapeTransfer)
.visibility(.ownProcess)
}
}
struct PlacedShape: Identifiable {
let id = UUID()
let kind: ShapeKind
}
CodableRepresentation übernimmt die Umwandlung zwischen den Nutzdaten und übertragbaren Daten. Die Nutzdaten enthalten einen Wert, keine Ansicht oder veränderbare Sammlungsreferenz. Die explizite Sendable-Konformität entspricht der Swift-Regel, dass gespeicherte Member einer sendbaren Struktur selbst sendbar sein müssen. Transferable, Sendable.
Es gibt bewusst nur eine Darstellung. Ein Text-Proxy würde eine weitere Möglichkeit hinzufügen, das Element zu verwenden, was diesem Vertrag keinen Nutzen bringt. Apple dokumentiert die Reihenfolge von Darstellungen und Fallback-Formate; füge sie nur hinzu, wenn ein Empfänger sie tatsächlich benötigt. Making a view into a drag source.
Nutzdaten annehmen und genau eine Sammlungsänderung ausführen
Erstelle ShapeTransferApp.swift:
import SwiftUI
@main
struct ShapeTransferApp: App {
var body: some Scene {
WindowGroup {
ShapeTransferView()
}
}
}
@MainActor
struct ShapeTransferView: View {
@State private var placed: [PlacedShape] = []
@State private var targeted = false
@State private var message = "Drag a shape into Collection."
var body: some View {
HStack(alignment: .top, spacing: 24) {
VStack(spacing: 16) {
Text("Palette").font(.headline)
paletteItem(.circle)
paletteItem(.square)
}
.frame(width: 160)
VStack(alignment: .leading, spacing: 12) {
Text("Collection").font(.headline)
Text(message)
ForEach(placed) { item in
Label(item.kind.rawValue,
systemImage: symbol(for: item.kind))
}
Spacer(minLength: 20)
}
.padding()
.frame(width: 300, height: 320, alignment: .topLeading)
.background(targeted ? Color.blue.opacity(0.15)
: Color.gray.opacity(0.08))
.contentShape(Rectangle())
.dropDestination(
for: ShapePayload.self,
action: { payloads, _ in
guard !payloads.isEmpty,
payloads.allSatisfy({ $0.version == 1 })
else {
message = "Drop rejected: unsupported payload."
return false
}
let additions = payloads.map {
PlacedShape(kind: $0.kind)
}
placed.append(contentsOf: additions)
message = "Added \(additions.count) shape(s)."
return true
},
isTargeted: { targeted = $0 }
)
}
.padding(24)
}
private func paletteItem(_ kind: ShapeKind) -> some View {
Label(kind.rawValue, systemImage: symbol(for: kind))
.padding()
.background(Color.gray.opacity(0.12))
.draggable(ShapePayload(version: 1, kind: kind))
}
private func symbol(for kind: ShapeKind) -> String {
switch kind {
case .circle: return "circle.fill"
case .square: return "square.fill"
}
}
}
Die expliziten Labels action: und isTargeted: wählen die ursprüngliche Überladung des typisierten Drop-Ziels aus. Apple definiert den booleschen Rückgabewert als Erfolg oder Fehlschlag und das Positionsargument als Koordinate im Koordinatensystem der Zielansicht. Hier wird die Position absichtlich nicht verwendet, da die Sammlung Formen anhängt, statt sie zu positionieren. dropDestination(for:action:isTargeted:).
Die Annahmeregel prüft jede gelieferte Nutzlast, bevor irgendetwas angehängt wird. Eine nicht unterstützte Version lehnt das gesamte gelieferte Paket ab; für jede gültige Nutzlast wird eine neue Zielidentität vergeben. Bei wiederholtem Ziehen derselben Palette entsteht also jeweils eine weitere Kopie. Diese Regel gilt für Werte, die der Closure übergeben werden; Decodierungsfehler treten auf, bevor solche Werte zu ShapePayload-Werten werden.
Der Zielstatus ändert nur die Darstellung. Er fügt keine Elemente ein und entfernt nichts aus der Quelle. Dadurch ist auch das Ergebnis eines Abbruchs eindeutig: Ohne angenommenen Drop bleibt die Sammlung unverändert.
Item Provider verwenden, wenn die Integration sie erfordert
Eine Provider-Implementierung kann dieselbe Version und Formart übertragen. Behalte den Vertrag bei und ersetze den Übertragungsweg mit diesen Schritten:
- Erstelle in der
.onDrag-Closure einenNSItemProviderund registriere eine Datendarstellung unter demselben benutzerdefinierten Typbezeichner mit der Sichtbarkeit.ownProcess. - Erfasse in dessen Lade-Handler einen unveränderlichen Nutzlastwert oder vorbereitete Bytes. Kodiere diesen Wert und rufe den Abschluss-Handler der Darstellung entweder mit Daten oder einem Fehler auf.
- Konfiguriere
.onDropso, dass der benutzerdefinierte Typ angenommen wird. - Beginne innerhalb der Drop-Aktion sofort mit dem Laden jedes angenommenen Providers mithilfe von
loadDataRepresentation(forTypeIdentifier:completionHandler:). - Decodiere die zurückgegebenen Daten, prüfe die Version und sammle die Ergebnisse. Führe die Sammlungsänderung erst dann auf dem Main Actor aus, wenn alle erforderlichen Ergebnisse verfügbar sind.
Apple dokumentiert die explizite Registrierung von Daten und die Beschränkung der Provider-Sichtbarkeit auf die eigene App. Die Lademethode ist asynchron und gibt ein Progress-Objekt zurück. Ein übereinstimmender Typbezeichner bedeutet, dass ein Ladeversuch unternommen werden kann; er garantiert nicht, dass Laden und Decodieren erfolgreich sind. registerDataRepresentation(forTypeIdentifier:visibility:loadHandler:), NSItemProviderRepresentationVisibility.ownProcess, loadDataRepresentation(forTypeIdentifier:completionHandler:).
Zwei Zeitpunkte sind wichtig. Erstens sagt Apple, dass das Laden eines Providers innerhalb der .onDrop-Aktion beginnen muss, auch wenn der Abschluss später erfolgen kann. Wird der anfängliche Ladeaufruf in eine spätere Aufgabe auf einem anderen Actor verschoben, verletzt das diese dokumentierte Anforderung. Zweitens warnt Apple davor, Exportarbeiten aus Darstellungs-Handlern an den Main Actor oder Main Thread zu übergeben, da dies zum Stillstand führen kann. Halte den Export unabhängig von Änderungen an der Benutzeroberfläche; aktualisiere den Sammlungszustand nach Abschluss des Imports. onDrop(of:isTargeted:perform:), onDrag(_:).
Unterscheide bei einer asynchronen Provider-Implementierung zwischen der Annahme des Ladeauftrags und dem Abschluss der Modelltransaktion. Erhalte die Provider-Reihenfolge mit indexierten Ergebnissen, melde Lade- oder Decodierungsfehler und lasse die Sammlung unverändert, wenn laut Vertrag alle Elemente erfolgreich sein müssen. Vermeide es, fehlgeschlagene Elemente durch einen Filtervorgang stillschweigend zu verwerfen. Dies sind Implementierungsregeln, die sich aus der Annahmerichtlinie für Pakete in diesem Beispiel ergeben.
AppKit-Ereignisverantwortung und Verschiebeverhalten beibehalten
Wenn eine vorhandene AppKit-Ansicht die Mausinteraktion bereits verwaltet, lasse dort auch den Sitzungsstart. Apples beginDraggingSession(with:event:source:) erwartet das ursprüngliche Maus-Down-Ereignis, Drag-Elemente, deren Frames die Koordinaten dieser Ansicht verwenden, sowie eine Quelle, die NSDraggingSource entspricht. Rückrufe der Quelle decken den Sitzungsstart, Bewegungen sowie den Abschluss oder Abbruch ab. beginDraggingSession(with:event:source:).
Auch die AppKit-Darstellungsgrenze ist explizit: NSDraggingItem(pasteboardWriter:) erfordert ein Objekt, das NSPasteboardWriting entspricht. Ein SwiftUI-Provider-Rückruf und ein AppKit-Pasteboard-Writer sind unterschiedliche Schnittstellen. Wenn du eine benutzerdefinierte AppKit-Ansicht in SwiftUI integrierst, soll die AppKit-Ansicht weiterhin für ihre Ereignisse und Sitzungen zuständig sein; das gemeinsame Modell verwaltet angenommene Sammlungsänderungen. init(pasteboardWriter:).
Das Beispiel kopiert Elemente. Wenn stattdessen vorhandene Elemente zwischen Sammlungen verschoben werden sollen, füge eine stabile Element-ID hinzu und lege genau eine Zuständigkeit für das Entfernen fest. Ein gemeinsamer Store kann ein Element einmal verschieben, oder ein Ziel kann es einfügen, während die Quelle es nach bestätigtem Verschieben entfernt. Eine Kombination beider Strategien birgt das Risiko, das Element zweimal zu entfernen.
Die aktuelle Apple-Dokumentation umfasst DragConfiguration, eine sitzungsbasierte typisierte Drop-Überladung und die Beobachtung von Drag-Sitzungen für macOS 26. Die Quelle schlägt unterstützte Aktionen vor, das Ziel wählt die Aktion aus, und der Abschluss der Sitzung ermöglicht es der Quelle, auf das Ergebnis zu reagieren. Prüfe die SDK-Verfügbarkeit jedes Symbols, bevor du diesen Ansatz übernimmst. DragConfiguration, dropDestination(for:isEnabled:action:), onDragSessionUpdated(_:).
Entferne kein Quellelement, wenn der Zeiger in das Ziel gelangt oder eine Vorschau erscheint. Diese Ereignisse treten vor der Annahme auf. Für ein Verschieben innerhalb der eigenen App gilt die hilfreiche Invariante, dass sich nach Abschluss der Transaktion jedes logische Element in genau einer Sammlung befindet.
Den Vertrag anhand sichtbarer Ergebnisse prüfen
Gehe die folgenden Schritte auf einem Mac durch, um die Implementierung zu prüfen:
- Ziehe
CircleinCollection: Das Ziel erhält einen Kreis, und der Eintrag in der Palette bleibt erhalten. - Wiederhole den Vorgang: Das Ziel erhält ein zweites, unabhängig identifiziertes Element.
- Ziehe
Squareaußerhalb des Ziels und lasse los: Die Sammlung bleibt unverändert. - Ändere die Version der Quellnutzlast vorübergehend in
2: Das Ziel meldet eine Ablehnung und fügt nichts hinzu; stelle anschließend1wieder her. - Gib bei einer Provider-Implementierung absichtlich einen Kodierungs- oder Ladefehler zurück: Zeige den Fehler an, ohne ein Teilpaket einzufügen.
- Brich bei einer Verschiebeimplementierung einen Drag ab und bestätige, dass das ursprüngliche Element weiterhin seiner ursprünglichen Sammlung angehört.
Wähle für diese Kopieraufgabe mit zwei Bereichen SwiftUI mit typisiertem Drag-and-Drop, sofern keine konkrete Kompatibilitäts- oder Integrationsanforderung Provider oder AppKit erfordert. Entscheidend ist der Vertrag: Ein definierter Wert überschreitet die Grenze, das Ziel validiert ihn, und genau eine modellverantwortliche Stelle übernimmt die angenommene Änderung.
Quellen
Drag and drop — Apple.
Transferable — Apple.
Making a view into a drag source — Apple.
visibility(_:) — Apple.
ownProcess — Apple.
Sendable — Apple.
onDrag(_:) — Apple.
onDrop(of:isTargeted:perform:) — Apple.
registerDataRepresentation(forTypeIdentifier:visibility:loadHandler:) — Apple.
loadDataRepresentation(forTypeIdentifier:completionHandler:) — Apple.
init(pasteboardWriter:) — Apple.
DragConfiguration — Apple.
onDragSessionUpdated(_:) — Apple.