Om du vill kopiera syntetiska former mellan två paneler i en Mac-app kan du börja med typad dra och släpp i SwiftUI. Välj objektleverantörer när ett befintligt leverantörsgränssnitt eller ett äldre macOS-mål kräver det. Definiera nyttolasten, reglerna för mottagning och vem som äger modellen innan du väljer API.

Definiera vad en lyckad släppning innebär

Uppgiften för användaren är specifik: låt någon dra en syntetisk cirkel eller kvadrat från en palett till en samling i samma Mac-app. Målet skapar ett nytt objekt; paletten förblir oförändrad. Allt finns i minnet, och när fönstret stängs försvinner samlingen.

Apple rekommenderar överförbara objekt för dra och släpp i Swift. Dokumentationen stöder även modifierare som bygger på leverantörer, så valet beror på distributionskraven och vilket gränssnitt du behöver integrera. Dra och släpp — Apple.

Skriv överföringskontraktet innan du implementerar vyerna:

Fråga om kontraktet Beslut för det här exemplet
Vad passerar gränsen? Ett versionshanterat värde som beskriver en formtyp
Vem kan ta emot representationen? Endast källappen
Vad gör en lyckad släppning? Lägger till ett nytt, identifierat objekt i samlingen
Vem ansvarar för ändringen? Målets tillstånd för samlingen
Vad händer vid avbrytning eller avvisning? Ingen modelländring
Vad betyder släppplatsen? Ingenting här; mottagna objekt läggs till i ordning

Det här är beslut om appens utformning. En förhandsvisning vid dragning visar nyttolasten, medan målet avgör hur nyttolasten ändrar modellen. När ansvarsområdena hålls isär går det att ändra överföringssättet utan att skriva om samlingens regler.

Välj det minsta API som uttrycker kontraktet

Situation Utgångspunkt Skäl
Källa och mål i SwiftUI, macOS 13 eller senare Transferable, .draggable och typad .dropDestination Uttrycker den accepterade modelltypen direkt
Befintlig integration med NSItemProvider .onDrag och .onDrop Behåller explicit registrering och inläsning av representationer
Ett mål före macOS 13 Kontrollera tillgängligheten för leverantörsmodifieraren för det målet Den typade grundlösningen kräver macOS 13; leverantörsöverlagringen som diskuteras nedan kräver macOS 11
En befintlig AppKit-vy äger mushanteringen AppKits drag-API:er Bevarar vyns händelsehantering och livscykeln för dragsessionen

Apple dokumenterar Transferable och den ursprungliga typade släppdestinationen från macOS 13. Den leverantörsbaserade överlagringen onDrop(of:isTargeted:perform:) som tar emot en array med UTType-värden finns från macOS 11. Kontrollera den specifika överlagringen i stället för att anta att alla API:er för dra och släpp har samma lägsta version. Transferable, dropDestination(for:action:isTargeted:), onDrop(of:isTargeted:perform:).

Båda metoderna kan begränsas till den egna appen. För en typad representation använder du .visibility(.ownProcess); Apple definierar den nivån som synlig endast inom källappen. Det finns alltså ingen anledning att välja en objektleverantör enbart för att hålla överföringen inom appen. visibility(_:), ownProcess.

Bygg den typade kopieringsinteraktionen

Det här fullständiga exemplet består av två Swift-filer i ett nytt macOS-mål för SwiftUI-appen. Ställ in distributionsmålet på macOS 13 eller senare och använd Swift 5-läget för den här grundlösningen. Ersätt appens genererade startpunkt med den andra filen och behåll bara en @main-deklaration. Koden har granskats men kompilerades inte här, eftersom det tillgängliga skalet saknar en Swift-verktygskedja.

Deklarera nyttolasten och dess innehållstyp

Lägg till en deklaration för en exporterad typ i målets informationslista. I Xcodes plist-redigerare skapar du UTExportedTypeDeclarations som en array med en ordbok. Sätt UTTypeIdentifier i ordboken till com.example.shape-transfer, UTTypeDescription till Synthetic Shape Transfer och UTTypeConformsTo till en array som innehåller public.data. Den här identifieraren är bara ett exempel; använd en unik identifierare för din faktiska app.

Swift-deklarationen och identifieraren i plist-filen måste stämma överens. Apple kräver motsvarande plist-poster för anpassade enhetliga typer och beskriver hur exporterade deklarationer används för format som en app definierar. Det här exemplet överför data i stället för filer, så det behövs inget filnamnstillägg. Definiera fil- och datatyper för din app.

Skapa 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 tillhandahåller konverteringen mellan nyttolasten och överförbara data. Nyttolasten innehåller ett värde i stället för en vy eller en referens till en muterbar samling. Den uttryckliga Sendable-konformansen följer Swifts regel om att de lagrade medlemmarna i en sändbar struktur också måste vara sändbara. Transferable, Sendable.

Det finns medvetet bara en representation. En textproxy skulle lägga till ytterligare ett sätt att använda objektet, vilket inte tillför något för det här kontraktet. Apple dokumenterar ordningen för representationer och reservformat; lägg bara till sådana när en mottagare faktiskt behöver dem. Gör en vy till en dragkälla.

Ta emot nyttolasten och genomför en uppdatering av samlingen

Skapa 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"
        }
    }
}

De uttryckliga etiketterna action: och isTargeted: väljer den ursprungliga överlagringen för den typade destinationen. Apple definierar dess booleska returvärde som lyckat eller misslyckat och platsargumentet i destinationsvyns koordinatsystem. Här används inte platsen avsiktligt, eftersom samlingen lägger till former i slutet i stället för att placera dem. dropDestination(for:action:isTargeted:).

Mottagningsregeln kontrollerar varje levererad nyttolast innan något läggs till. En version som inte stöds avvisar hela den levererade gruppen; varje giltig nyttolast får ett nytt id i målet. Om samma palettdragning upprepas skapas alltså ännu en kopia. Regeln gäller värden som levereras till avslutningen; avkodningsfel uppstår innan de blir värden av typen ShapePayload.

Måltillståndet påverkar bara presentationen. Det lägger inte till objekt och tar inte bort något från källan. Den uppdelningen ger även avbrytning ett enkelt utfall: utan en godkänd släppning förblir samlingen oförändrad.

Använd objektleverantörer när integrationen kräver dem

En leverantörsimplementering kan överföra samma version och formtyp. Behåll kontraktet oförändrat och byt överföringssätt med följande steg:

  1. Skapa en NSItemProvider i .onDrag-avslutningen och registrera en datarepresentation med samma anpassade typidentifierare och synligheten .ownProcess.
  2. Fånga ett oföränderligt nyttolastvärde eller förberedda byte i dess inläsningshanterare. Koda värdet och anropa avslutningshanteraren för representationen med antingen data eller ett fel.
  3. Konfigurera .onDrop så att den accepterar den anpassade typen.
  4. Starta inläsningen av varje godkänd leverantör omedelbart i släppåtgärden med loadDataRepresentation(forTypeIdentifier:completionHandler:).
  5. Avkoda den returnerade datan, validera versionen och samla resultaten. Genomför den slutliga ändringen av samlingen på huvudaktören först när de nödvändiga resultaten finns tillgängliga.

Apple dokumenterar uttrycklig dataregistrering och synlighet inom den egna appen för leverantörer. Inläsningsmetoden är asynkron och returnerar ett förloppsobjekt. En matchande typidentifierare visar att ett inläsningsförsök kan göras; den garanterar inte att inläsning och avkodning lyckas. registerDataRepresentation(forTypeIdentifier:visibility:loadHandler:), NSItemProviderRepresentationVisibility.ownProcess, loadDataRepresentation(forTypeIdentifier:completionHandler:).

Två tidsgränser är viktiga. För det första säger Apple att inläsningen av leverantören måste starta i .onDrop-åtgärden, även om den kan slutföras senare. Om det första anropet till inläsningen flyttas till en senare uppgift på en annan aktör bryter det mot det dokumenterade kravet. För det andra varnar Apple för att skicka exportarbete till huvudaktören eller huvudtråden från representationshanterare, eftersom det kan leda till att appen hänger sig. Håll exporten frikopplad från UI-ändringar och uppdatera samlingens tillstånd när importen är klar. onDrop(of:isTargeted:perform:), onDrag(_:).

I en asynkron leverantörsimplementering ska du skilja mellan att acceptera inläsningsarbetet och att slutföra modelltransaktionen. Bevara leverantörernas ordning med indexerade resultat, rapportera inläsnings- och avkodningsfel och låt samlingen förbli oförändrad om kontraktet kräver att alla objekt lyckas. Undvik att tyst kasta bort misslyckade objekt med en filtreringsoperation. Det här är implementeringsregler som följer av exempelts policy för att godkänna hela grupper.

Bevara AppKits ägarskap över händelser och semantik för flytt

Om en befintlig AppKit-vy redan äger mushanteringen ska du behålla initieringen av sessionen där. Apples beginDraggingSession(with:event:source:) tar emot den ursprungliga musnedhändelsen, dragobjekt vars ramar använder vyns koordinater samt en källa som följer protokollet NSDraggingSource. Återanropen till källan täcker när sessionen startar, när den flyttas och när den slutförs eller avbryts. beginDraggingSession(with:event:source:).

Även AppKits representationsgräns är uttrycklig: NSDraggingItem(pasteboardWriter:) kräver ett objekt som följer NSPasteboardWriting. En återanropsfunktion för SwiftUI-leverantörer och en pasteboard-skrivare i AppKit är olika gränssnitt. När en anpassad AppKit-vy kopplas till SwiftUI låter du AppKit-vyn behålla ansvaret för sina händelser och sin session, medan den delade modellen ansvarar för godkända ändringar av samlingen. init(pasteboardWriter:).

Exemplet utför kopieringar. Om uppgiften ändras till att flytta befintliga objekt mellan samlingar lägger du till en stabil objektidentifierare och definierar exakt en ägare för borttagningen. En delad lagring kan flytta ett objekt en gång, eller så kan målet infoga det medan källan tar bort det efter att flytten har bekräftats. Om båda strategierna används riskerar objektet att tas bort två gånger.

Apples aktuella dokumentation innehåller DragConfiguration, en typad släppöverlagring baserad på sessioner och observation av dragsessioner för macOS 26. Källan föreslår operationer som stöds, målet väljer operationen och när sessionen är klar kan källan reagera på resultatet. Kontrollera tillgängligheten för varje symbol i SDK:t innan du använder den här vägen. DragConfiguration, dropDestination(for:isEnabled:action:), onDragSessionUpdated(_:).

Ta inte bort ett källobjekt när en pekare går in i målet eller när en förhandsvisning visas. De händelserna inträffar innan något har godkänts. Vid en flytt inom appen är den användbara invarianten att ett logiskt objekt har exakt en ägande samling efter att transaktionen har slutförts.

Verifiera kontraktet med observerbara resultat

Följ den här sekvensen för att kontrollera implementeringen på en Mac:

  • Dra Circle till Collection: målet får en cirkel och paletten behåller sitt objekt.
  • Upprepa dragningen: målet får ett andra, självständigt identifierat objekt.
  • Dra Square utanför målet och släpp: samlingen ändras inte.
  • Ändra tillfälligt källnyttolastens version till 2: målet rapporterar avvisning och lägger inte till något; återställ 1 efteråt.
  • För en leverantörsimplementering ska du avsiktligt returnera ett kodnings- eller inläsningsfel: visa felet utan att infoga en ofullständig grupp.
  • För en flyttimplementering avbryter du en dragning och bekräftar att det ursprungliga objektet fortfarande tillhör sin ursprungliga samling.

Välj typad SwiftUI för den här kopieringsuppgiften med två paneler, såvida inte ett konkret kompatibilitets- eller integrationskrav kräver leverantörer eller AppKit. Det bestående är kontraktet: ett definierat värde passerar gränsen, målet validerar det och en enda modellägare genomför den godkända ändringen.

Källor