Om du vill att ett syntetiskt kort ska visas intakt under en inbyggd macOS-dragning ger du dess yttre behållare en enda dragkälla och visar hela kortet som förhandsvisning. Använd AppKit när du behöver uttrycklig kontroll över förhandsvisningens ursprungliga ram och förskjutningen i förhållande till pekaren.
Den här guiden beskriver hur du drar ett kort som finns i minnet mellan två paneler i din egen Mac-app. Det praktiska målet är att bevara kortets former, text, mellanrum och identitet, samtidigt som ansvaret för gesthantering och modelluppdateringar hålls tydligt avgränsat.
Vilken del av dragningen behöver ändras?
Apple skiljer mellan de överförda uppgifterna och den visuella representation som visas under dragningen. SwiftUI:s draggable(_:preview:) skapar en återgivning av förhandsvisningsvyn när dragningen börjar. Enligt dokumentationen placeras förhandsvisningen centrerad över källvyn. Själva källvyn överförs inte. Se Apples dokumentation om draggable(_:preview:).
Använd den här vägledningen för att hitta den kontroll du faktiskt behöver:
| Önskat beteende | Rekommenderad utgångspunkt |
|---|---|
| Hålla kortets etikett, former och bakgrund tillsammans | En enda dragkälla på den yttre behållaren med en fullständig anpassad förhandsvisning |
| Justera konturen under lyftövergången | contentShape(.dragPreview, shape) |
| Ändra vilken del av kortet som kan starta dragningen | contentShape(.interaction, shape) |
| Flytta ett kort till en annan panel | Validera nyttolasten och uppdatera modellen på målet |
| Ange den ursprungliga rektangeln för dragbilden och pekarens position i förhållande till den | En AppKit-styrd session med NSDraggingItem |
| Flytta ett element kontinuerligt på en arbetsyta utan inbyggda släppmål | En lokal DragGesture-interaktion |
Skillnaden mellan de två innehållsformerna är viktig: Apple dokumenterar att .dragPreview påverkar förhandsvisningens form, medan .interaction styr träfftestningen. Om du bara ändrar förhandsvisningens kontur utökar du inte området som startar dragningen. Se ContentShapeKinds.dragPreview.
En lokal DragGesture rapporterar en dragrörelse genom en gestsekvens. Välj det tillvägagångssättet när uppgiften är att uppdatera en position på en arbetsyta. För den här artikelns uppgift med inbyggd dragning mellan paneler låter du draggable installera draggesterna. Se Apples referens för DragGesture och guiden om dragkällor.
Skapa en förhandsvisning för hela det syntetiska kortet
Placera dragmodifieraren på den minsta behållare som omfattar allt som ska följa med. För ett kort som består av en cirkel, två textrader och en rundad bakgrund är den behållaren hela kortet. Om du lägger separata dragkällor på dess underordnade vyer beskriver du separata interaktioner.
Återanvänd samma presentationsvy för källan och förhandsvisningen. Ge vyn uttryckliga mått och en egen bakgrund i stället för att förlita dig på stil som ärvs från en omgivande panel. Det här är en designrekommendation som bygger på hur den återgivna förhandsvisningen fungerar: en fristående presentation gör det enklare att få de båda utseendena att stämma överens.
Fullständigt SwiftUI-exempel
Skapa en macOS-app med SwiftUI och macOS 13 eller senare som lägsta distributionsversion. Ersätt den genererade appens startpunkt och innehållsvy med den här enda filen, så att målet innehåller exakt en @main-deklaration. Både modifieraren för anpassade förhandsvisningar och den målöverlagring som används här är dokumenterade som tillgängliga från macOS 13. Se draggable(_:preview:) och 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
}
}
}
Det här exemplet använder bara syntetisk grafik och en genererad identifierare som finns i minnet. String uppfyller redan protokollet Transferable, så ingen anpassad överföringsrepresentation behövs. Apple dokumenterar den inbyggda konformansen i Gör en vy till en dragkälla.
Den förväntade interaktionen är enkel: dra Tile A till den andra panelen, släpp kortet och dra det sedan tillbaka. Hela CardFace utgör den rörliga förhandsvisningen. Målet godtar exakt en token som stämmer och ändrar det enda platsvärdet. Om true returneras rapporteras ett lyckat släpp; om false returneras avvisas det, enligt den dokumenterade kontraktsbeskrivningen för målets åtgärd.
Det här är ett komplett exempel avsett för ett appmål för macOS. Det har inte kompilerats eller körts här eftersom forskningsmiljön saknar både Swift-verktygskedja och macOS-runtime.
Håll visuell kontinuitet åtskild från modellens ansvar
I exemplet är originalkortet synligt tills ett godkänt släpp ändrar isOnRight. Om du släpper utanför panelerna ändras inte modellen. Om du släpper tillbaka kortet på den panel där det redan finns tilldelas den befintliga platsen igen. En token som inte stämmer eller flera objekt avvisas innan tillståndet ändras.
Det beteendet förhindrar att kortet tas bort när dragningen börjar och att du sedan måste återskapa det efter en avbruten dragning. Förhandsvisningen ger visuell kontinuitet; målet ansvarar för ändringen av plats. På en större spelplan tillämpar du samma mönster på en stabil objektidentifierare och en enda auktoritativ samling.
Värdesemantik i Swift hjälper till att förklara varför nyttolasten inte automatiskt flyttar originalmodellen. Strukturer och strängar har värdesemantik; att tilldela eller skicka dem flyttar inte implicit ett objekt i appens samling. Modelluppdateringen måste uttryckligen genomföra flytten. Se Strukturer och klasser.
Skilj också mellan en flytt på programnivå och den inbyggda dragoperationen. Exemplet ändrar ett delat tillstånd för spelplanen efter ett godkänt släpp; det konfigurerar inte förhandling om inbyggd flytt och garanterar inte en flyttmarkör.
För macOS 26 och senare dokumenterar Apple DragConfiguration för att ange vilka åtgärder som stöds och onDragSessionUpdated(_:) för att observera den utförda åtgärden. Ett mål väljer vilken åtgärd det utför. Om du tar bort objektet från källan efter en inbyggd flytt samordnar du det med målets hantering så att samma delade objekt inte tas bort två gånger. Se DragConfiguration och onDragSessionUpdated(_:).
När bör AppKit styra dragsessionen?
Använd AppKit när godkännandekriteriet kräver en uttryckligt placerad dragbild, till exempel att pekaren ska befinna sig över samma punkt inuti kortet när det plockas upp. SwiftUIs dokumenterade modifierare för anpassade förhandsvisningar tar emot en nyttolast och en förhandsvisningsvy och beskriver en centrerad förhandsvisning. AppKit ger direkt åtkomst till dragningsobjektets ram.
Följ den här sekvensen för en AppKit-implementation:
- Låt källans
NSViewta emot den faktiska nedtryckningshändelsen för musen och hålla reda på om interaktionen övergår till en dragning. - Återge hela det syntetiska kortet till en enda
NSImageoch skapa ettNSDraggingItemmed dess skrivare för urklippstavlan. - Ange bilden och dess ursprungliga rektangel med
setDraggingFrame(_:contents:). - Uttryck rektangeln i den initierande vyns koordinatsystem och anropa sedan
beginDraggingSession(with:event:source:)med den initierande nedtryckningshändelsen för musen och dinNSDraggingSource. - Låt målet validera och genomföra släppet. Använd källans återanropsfunktion för slutförande för att städa upp sessionen.
Apple anger att musens position i den initierande händelsen bestämmer förskjutningen för den dragna ikonen, och att varje objekts ursprungliga ram måste använda källvyns koordinater. Se till att kortets rektangel och händelsens position använder samma koordinatsystem. Se beginDraggingSession(with:event:source:).
setDraggingFrame(_:contents:) är den dokumenterade bekvämlighetsmetoden för ett dragningsobjekt som representeras av en enda bild. Ett syntetiskt kort kan innehålla flera ritade element och ändå representeras av den enda bilden. Se setDraggingFrame(_:contents:).
Bevara gränserna för händelser och sessioner
Välj en enda initiativtagare till dragningen för kortets interaktiva område. Som implementeringsregel bör du undvika att lägga en AppKit-startare för sessioner ovanpå en aktiv SwiftUI-källa draggable för samma område. Båda skulle försöka styra interaktionen. Skapa inte konstgjorda mushändelser för att kompensera för otydligt gestansvar.
Du kan bädda in AppKit-källan med NSViewRepresentable. Behåll den inbyggda händelsehanteringen i dess NSView och kommunicera godkända modelländringar via bindningar eller en koordinator. SwiftUI styr den representerade vyns layout; Apple säger uttryckligen att du inte ska ange frame eller bounds direkt för den hanterade vyn. Rektangeln för en dragningsbild är en separat sak. Se NSViewRepresentable.
Behåll inte ett NSDraggingItem för att ändra det senare. Apple dokumenterar dess begränsade livslängd: efter att sessionen har skapats ändrar du dragningsobjekt genom sessionsuppräkning, och varje uppräknat objekt används bara inuti dess återanropsfunktion. Ändringar på målsidan kan också åsidosätta ändringar av bilder på källsidan medan dragningen pågår över det målet. Se NSDraggingItem och enumerateDraggingItems(options:for:classes:searchOptions:using:).
Hur felsöker du en förhandsvisning som fortfarande ser fel ut?
Kontrollera problemet vid det steg där det uppstår:
| Symptom | Första kontroll |
|---|---|
| Bara cirkeln eller texten följer med | Lägg en enda dragkälla på kortets fullständiga behållare. |
| Förhandsvisningens mått skiljer sig från kortets | Återanvänd samma presentationsvy och uttryckliga mått. |
| Upphämtningskonturen skiljer sig från den rörliga bilden | Kontrollera .dragPreview separat från stängningen för den anpassade förhandsvisningen. |
| Tomt utrymme startar oväntat dragningen | Granska .interaction och källbehållarens gränser. |
| AppKit-bilden hoppar vid upphämtning | Kontrollera den initierande nedtryckningshändelsen för musen och källvyns koordinater. |
| Senare ändringar av AppKit-bilden gör ingen skillnad | Utför dem inuti sessionsuppräkningen. |
| Kortet försvinner när dragningen avbryts | Flytta modelländringen till hanteringen av godkända släpp och granska städningen. |
Verifiera separat en avbruten dragning, ett godkänt släpp, ett släpp tillbaka på den ursprungliga panelen och en avvisad nyttolast. Den viktiga invarianten är att en enda modellplats förblir auktoritativ hela tiden. Börja med den fullständiga SwiftUI-förhandsvisningen och använd sedan en AppKit-styrd källa bara om den nödvändiga bildplaceringen kräver uttrycklig kontroll över ram och händelse.
Källor
draggable(_:preview:) — Apples utvecklardokumentation.
Gör en vy till en dragkälla — Apples utvecklardokumentation.
dragPreview — Apples utvecklardokumentation.
DragGesture — Apples utvecklardokumentation.
dropDestination(for:action:isTargeted:) — Apples utvecklardokumentation.
Strukturer och klasser — Swift.org.
DragConfiguration — Apples utvecklardokumentation.
onDragSessionUpdated(_:) — Apples utvecklardokumentation.
beginDraggingSession(with:event:source:) — Apples utvecklardokumentation.
setDraggingFrame(_:contents:) — Apples utvecklardokumentation.
NSViewRepresentable — Apples utvecklardokumentation.
NSDraggingItem — Apples utvecklardokumentation.
enumerateDraggingItems(options:for:classes:searchOptions:using:) — Apples utvecklardokumentation.