Untuk menyalin bentuk sintetik antara dua panel dalam app Mac, mulakan dengan fungsi seret dan lepas SwiftUI bertip. Pilih penyedia item apabila antara muka penyedia sedia ada atau sasaran macOS yang lebih awal memerlukannya. Tentukan muatan, peraturan penerimaan dan pemilik model sebelum memilih mana-mana API.
Tentukan maksud lepas yang berjaya
Tugas pengguna di sini khusus: membolehkan seseorang menyeret bulatan atau segi empat sama sintetik dari palet ke dalam koleksi dalam app Mac yang sama. Destinasi mencipta item baharu; palet kekal tanpa perubahan. Semuanya disimpan dalam memori, dan menutup tetingkap akan membuang koleksi.
Apple mengesyorkan item boleh pindah untuk fungsi seret dan lepas Swift. Dokumentasinya juga menyokong pengubah suai berasaskan penyedia, jadi pilihan bergantung pada keperluan penggunaan dan antara muka yang perlu anda integrasikan. Seret dan lepas — Apple.
Tulis kontrak pemindahan sebelum melaksanakan paparan:
| Soalan kontrak | Keputusan untuk contoh ini |
|---|---|
| Apakah yang merentasi sempadan? | Nilai berversi yang menerangkan jenis bentuk |
| Siapakah yang boleh menerima perwakilan itu? | App sumber sahaja |
| Apakah yang dilakukan oleh lepas yang berjaya? | Menambah satu item koleksi dengan pengecam baharu |
| Siapakah yang memiliki mutasi? | Keadaan koleksi paparan destinasi |
| Apakah yang berlaku apabila dibatalkan atau ditolak? | Tiada perubahan model |
| Apakah maksud lokasi lepas? | Tiada apa-apa di sini; item yang diterima ditambah mengikut turutan |
Ini ialah keputusan reka bentuk aplikasi. Pratonton seretan menggambarkan muatan, manakala destinasi menentukan cara muatan itu mengubah model. Mengasingkan tanggungjawab ini membolehkan cara pemindahan diubah tanpa menulis semula peraturan koleksi.
Pilih API paling ringkas yang menggambarkan kontrak
| Situasi | Titik permulaan | Sebab |
|---|---|---|
| Sumber dan destinasi SwiftUI, macOS 13 atau lebih baharu | Transferable, .draggable dan .dropDestination bertip |
Menyatakan jenis model yang diterima secara langsung |
Integrasi NSItemProvider sedia ada |
.onDrag dan .onDrop |
Mengekalkan pendaftaran dan pemuatan perwakilan secara eksplisit |
| Sasaran lebih awal daripada macOS 13 | Semak ketersediaan pengubah suai penyedia untuk sasaran itu | Asas bertip memerlukan macOS 13; beban lampau penyedia yang dibincangkan di bawah memerlukan macOS 11 |
| Paparan AppKit sedia ada mengendalikan interaksi tetikus | API seretan AppKit | Mengekalkan pengendalian peristiwa paparan dan kitar hayat sesi seretan |
Apple mendokumenkan Transferable dan destinasi lepas bertip asal mulai macOS 13. Beban lampau berasaskan penyedia untuk onDrop(of:isTargeted:perform:) yang menerima tatasusunan nilai UTType bermula pada macOS 11. Semak beban lampau yang khusus dan jangan mengandaikan semua API seret dan lepas berkongsi versi minimum yang sama. Transferable, dropDestination(for:action:isTargeted:), onDrop(of:isTargeted:perform:).
Keterlihatan dalam app sendiri tersedia dengan kedua-dua pendekatan. Untuk perwakilan bertip, gunakan .visibility(.ownProcess); Apple mentakrifkan tahap itu sebagai keterlihatan dalam app sumber sahaja. Oleh itu, memilih penyedia item tidak diperlukan semata-mata untuk memastikan pemindahan ini kekal dalam app. visibility(_:), ownProcess.
Bina interaksi salin bertip
Contoh lengkap ini terdiri daripada dua fail Swift dalam sasaran app SwiftUI macOS baharu. Tetapkan sasaran penggunaan kepada macOS 13 atau lebih baharu dan gunakan mod bahasa Swift 5 untuk asas ini. Gantikan titik masuk app yang dijana dengan fail kedua; kekalkan hanya satu pengisytiharan @main. Kod ini telah diperiksa tetapi tidak dikompil di sini kerana shell yang tersedia tidak mempunyai rantaian alat Swift.
Isytiharkan muatan dan jenis kandungannya
Tambahkan pengisytiharan jenis yang dieksport pada senarai sifat maklumat sasaran. Dalam editor senarai sifat Xcode, cipta UTExportedTypeDeclarations sebagai tatasusunan dengan satu kamus. Tetapkan UTTypeIdentifier dalam kamus itu kepada com.example.shape-transfer, UTTypeDescription kepada Pemindahan Bentuk Sintetik, dan UTTypeConformsTo sebagai tatasusunan yang mengandungi public.data. Pengecam ini hanyalah contoh; gunakan pengecam unik untuk app sebenar anda.
Pengisytiharan Swift dan pengecam senarai sifat mesti sepadan. Apple memerlukan entri senarai sifat yang sepadan untuk jenis seragam tersuai dan menerangkan pengisytiharan yang dieksport bagi format yang ditentukan oleh app. Contoh ini memindahkan data, bukannya fail, jadi tiada sambungan nama fail ditambahkan. Menentukan jenis fail dan data untuk app anda.
Cipta 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 menyediakan penukaran antara muatan dengan data yang boleh dipindahkan. Muatan mengandungi nilai, bukannya paparan atau rujukan koleksi yang boleh diubah. Pemastian pematuhan Sendable secara eksplisit mengikut peraturan Swift bahawa ahli tersimpan bagi struktur yang boleh dihantar mestilah juga boleh dihantar. Transferable, Sendable.
Sengaja hanya ada satu perwakilan. Proksi teks akan menambah cara lain untuk menggunakan item, sedangkan tiada tujuan untuk itu dalam kontrak ini. Apple mendokumenkan susunan perwakilan dan format sandaran; tambahkan semua itu hanya apabila penerima benar-benar memerlukannya. Menjadikan paparan sebagai sumber seretan.
Terima muatan dan laksanakan satu kemas kini koleksi
Cipta 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"
}
}
}
Label action: dan isTargeted: yang dinyatakan secara eksplisit memilih beban lampau destinasi bertip asal. Apple mentakrifkan hasil Boolean sebagai kejayaan atau kegagalan dan argumen lokasi dalam ruang koordinat paparan destinasi. Di sini, lokasi sengaja tidak digunakan kerana koleksi menambah bentuk dan bukannya meletakkannya pada kedudukan tertentu. dropDestination(for:action:isTargeted:).
Peraturan penerimaan menyemak setiap muatan yang dihantar sebelum menambah apa-apa. Versi yang tidak disokong menolak kelompok yang dihantar itu; setiap muatan yang sah mendapat pengecam destinasi yang baharu. Oleh itu, mengulangi seretan palet yang sama akan menghasilkan satu lagi salinan. Peraturan ini meliputi nilai yang dihantar kepada penutupan; kegagalan penyahkodan berlaku sebelum nilai itu menjadi nilai ShapePayload.
Keadaan penyasaran hanya mengubah persembahan. Ia tidak memasukkan item atau mengalih keluar apa-apa daripada sumber. Pengasingan ini juga menjadikan hasil pembatalan mudah: tanpa lepas yang diterima, koleksi kekal tanpa perubahan.
Gunakan penyedia item apabila integrasi memerlukannya
Pelaksanaan penyedia boleh membawa versi dan jenis bentuk yang sama. Kekalkan kontrak dan gantikan cara pemindahan melalui langkah berikut:
- Dalam penutupan
.onDrag, ciptaNSItemProviderdan daftarkan perwakilan data di bawah pengecam jenis tersuai yang sama, menggunakan keterlihatan.ownProcess. - Tangkap nilai muatan yang tidak boleh diubah atau bait yang telah disediakan dalam pengendali pemuatannya. Kodkan nilai itu dan panggil pengendali pelengkapan perwakilan dengan data atau ralat.
- Konfigurasikan
.onDropuntuk menerima jenis tersuai itu. - Dalam tindakan lepas, mulakan pemuatan setiap penyedia yang diterima dengan segera menggunakan
loadDataRepresentation(forTypeIdentifier:completionHandler:). - Nyahkod data yang dikembalikan, sahkan versi dan kumpulkan hasilnya. Lakukan mutasi koleksi yang akan dibuat pada aktor utama hanya selepas hasil yang diperlukan tersedia.
Apple mendokumenkan pendaftaran data secara eksplisit dan keterlihatan dalam app sendiri untuk penyedia. Kaedah pemuatannya berjalan secara tak segerak dan mengembalikan objek kemajuan. Pengecam jenis yang sepadan membolehkan percubaan pemuatan; ia tidak menjamin pemuatan dan penyahkodan akan berjaya. registerDataRepresentation(forTypeIdentifier:visibility:loadHandler:), NSItemProviderRepresentationVisibility.ownProcess, loadDataRepresentation(forTypeIdentifier:completionHandler:).
Dua batas masa perlu diberi perhatian. Pertama, Apple menyatakan pemuatan penyedia mesti dimulakan dalam tindakan .onDrop, walaupun pelengkapan boleh berlaku kemudian. Memindahkan panggilan pemuatan awal ke dalam tugas kemudian pada aktor lain melanggar keperluan yang didokumenkan itu. Kedua, Apple memberi amaran agar kerja eksport tidak dihantar ke aktor utama atau utas utama daripada pengendali perwakilan kerana tindakan ini boleh menyebabkan proses tergantung. Pastikan eksport tidak bergantung pada mutasi UI; kemas kini keadaan koleksi selepas import selesai. onDrop(of:isTargeted:perform:), onDrag(_:).
Untuk pelaksanaan penyedia tak segerak, bezakan penerimaan kerja pemuatan daripada penyempurnaan urus niaga model. Kekalkan turutan penyedia dengan hasil berindeks, laporkan kegagalan pemuatan atau penyahkodan dan biarkan koleksi tanpa perubahan jika kontrak anda menghendaki setiap item berjaya. Elakkan daripada membuang item yang gagal secara senyap melalui operasi penapisan. Ini ialah peraturan pelaksanaan yang terbit daripada dasar penerimaan kelompok contoh ini.
Kekalkan pemilikan peristiwa AppKit dan semantik pemindahan
Jika paparan AppKit sedia ada sudah mengendalikan interaksi tetikus, kekalkan permulaan sesi di situ. beginDraggingSession(with:event:source:) Apple menerima peristiwa tetikus tekan yang memulakan seretan, item seretan dengan bingkai yang menggunakan koordinat paparan itu, serta sumber yang mematuhi NSDraggingSource. Panggilan balik sumber meliputi permulaan sesi, pergerakan dan pelengkapan atau pembatalan. beginDraggingSession(with:event:source:).
Sempadan perwakilan AppKit juga eksplisit: NSDraggingItem(pasteboardWriter:) memerlukan objek NSPasteboardWriting. Panggilan balik penyedia SwiftUI dan penulis papan tampal AppKit ialah antara muka yang berbeza. Apabila menghubungkan paparan AppKit tersuai ke SwiftUI, biarkan paparan AppKit terus mengendalikan peristiwa dan sesinya; biarkan model kongsi mengendalikan perubahan koleksi yang diterima. init(pasteboardWriter:).
Contoh ini melakukan penyalinan. Jika tugas berubah kepada pemindahan item sedia ada antara koleksi, tambahkan pengecam item yang stabil dan tentukan dengan tepat satu pihak yang bertanggungjawab untuk membuang item. Stor kongsi boleh memindahkan item sekali, atau destinasi boleh memasukkan item sementara sumber membuangnya selepas pemindahan disahkan selesai. Menggabungkan kedua-dua strategi berisiko membuang item itu dua kali.
Dokumentasi Apple semasa merangkumi DragConfiguration, beban lampau destinasi lepas bertip berasaskan sesi dan pemerhatian sesi seretan untuk macOS 26. Sumber mencadangkan operasi yang disokong, destinasi memilih operasi itu dan pelengkapan sesi membolehkan sumber bertindak balas terhadap hasilnya. Semak ketersediaan setiap simbol dalam SDK sebelum menggunakan pendekatan ini. DragConfiguration, dropDestination(for:isEnabled:action:), onDragSessionUpdated(_:).
Jangan buang item sumber apabila penuding memasuki destinasi atau apabila pratonton muncul. Peristiwa itu berlaku sebelum penerimaan. Untuk pemindahan dalam app sendiri, invarian yang berguna ialah satu item logik hanya dimiliki oleh satu koleksi selepas urus niaga selesai.
Sahkan kontrak melalui hasil yang boleh diperhatikan
Gunakan urutan ini untuk menyemak pelaksanaan pada Mac:
- Seret
Circleke dalamCollection: destinasi mendapat satu bulatan dan palet mengekalkan entrinya. - Ulangi seretan itu: destinasi mendapat item kedua dengan pengecam tersendiri.
- Seret
Squareke luar destinasi dan lepaskan: koleksi tidak berubah. - Ubah versi muatan sumber buat sementara waktu kepada
2: destinasi melaporkan penolakan dan tidak menambah apa-apa; selepas itu pulihkan1. - Untuk pelaksanaan penyedia, sengaja pulangkan ralat pengekodan atau pemuatan: paparkan kegagalan tanpa memasukkan kelompok separa.
- Untuk pelaksanaan pemindahan, batalkan seretan dan sahkan item asal masih tergolong dalam koleksi asalnya.
Pilih SwiftUI bertip untuk tugas penyalinan dua panel ini melainkan keperluan keserasian atau integrasi yang khusus memerlukan penyedia atau AppKit. Bahagian yang kekal ialah kontraknya: nilai yang ditakrifkan merentasi sempadan, destinasi mengesahkannya dan seorang pemilik model melaksanakan perubahan yang diterima.
Sumber
Seret dan lepas — Apple.
Transferable — 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.