Untuk menyalin bentuk sintetis di antara dua panel dalam app Mac, mulailah dengan drag and drop SwiftUI bertipe. Pilih penyedia item jika antarmuka penyedia yang sudah ada atau target macOS versi lebih lama mengharuskannya. Tentukan payload, aturan penerimaan, dan pemilik model sebelum memilih salah satu API.
Tentukan arti drop yang berhasil
Tugas pengguna di sini spesifik: memungkinkan seseorang menyeret lingkaran atau persegi sintetis dari palet ke koleksi di app Mac yang sama. Tujuan membuat item baru; palet tetap tidak berubah. Semuanya tersimpan di memori, dan menutup jendela akan membuang koleksi.
Apple merekomendasikan item transferable untuk drag and drop Swift. Dokumentasinya juga mendukung modifier berbasis penyedia, jadi pilihannya bergantung pada persyaratan deployment dan antarmuka yang perlu Anda integrasikan. Drag and drop — Apple.
Tuliskan kontrak transfer sebelum mengimplementasikan tampilan:
| Pertanyaan kontrak | Keputusan untuk contoh ini |
|---|---|
| Apa yang melintasi batas? | Nilai berversi yang mendeskripsikan jenis bentuk |
| Siapa yang dapat menerima representasi? | Hanya app sumber |
| Apa yang dilakukan drop yang berhasil? | Menambahkan satu item koleksi dengan identitas baru |
| Siapa yang memiliki mutasi? | State koleksi pada tampilan tujuan |
| Apa yang terjadi jika dibatalkan atau ditolak? | Model tidak berubah |
| Apa arti lokasi drop? | Tidak ada di sini; item yang diterima ditambahkan secara berurutan |
Ini adalah keputusan desain aplikasi. Pratinjau drag menggambarkan payload, sedangkan tujuan menentukan bagaimana payload tersebut mengubah model. Memisahkan tanggung jawab ini memungkinkan transport diubah tanpa menulis ulang aturan koleksi.
Pilih API terkecil yang menyatakan kontrak
| Situasi | Titik awal | Alasan |
|---|---|---|
| Sumber dan tujuan SwiftUI, macOS 13 atau lebih baru | Transferable, .draggable, dan .dropDestination bertipe |
Menyatakan tipe model yang diterima secara langsung |
Integrasi NSItemProvider yang sudah ada |
.onDrag dan .onDrop |
Mempertahankan pendaftaran dan pemuatan representasi secara eksplisit |
| Target sebelum macOS 13 | Periksa ketersediaan modifier penyedia untuk target tersebut | Baseline bertipe memerlukan macOS 13; overload penyedia yang dibahas di bawah memerlukan macOS 11 |
| Tampilan AppKit yang sudah ada memiliki interaksi mouse | API drag AppKit | Mempertahankan penanganan event tampilan dan siklus sesi drag |
Apple mendokumentasikan Transferable dan tujuan drop bertipe awal sejak macOS 13. Overload berbasis penyedia onDrop(of:isTargeted:perform:) yang menerima array nilai UTType tersedia mulai macOS 11. Periksa overload spesifiknya, jangan menganggap semua API drag-and-drop memiliki versi minimum yang sama. Transferable, dropDestination(for:action:isTargeted:), onDrop(of:isTargeted:perform:).
Visibilitas khusus dalam app sendiri tersedia pada kedua pendekatan. Untuk representasi bertipe, terapkan .visibility(.ownProcess); Apple mendefinisikan level tersebut sebagai visibilitas yang hanya berlaku di dalam app sumber. Jadi, memilih penyedia item tidak diperlukan hanya untuk menjaga transfer ini tetap di dalam app. visibility(_:), ownProcess.
Buat interaksi salin bertipe
Contoh lengkap ini terdiri atas dua file Swift dalam target app SwiftUI macOS baru. Atur target deployment ke macOS 13 atau lebih baru dan gunakan mode bahasa Swift 5 untuk baseline ini. Ganti titik masuk app yang dibuat otomatis dengan file kedua; gunakan hanya satu deklarasi @main. Kode telah diperiksa, tetapi tidak dikompilasi di sini karena shell yang tersedia tidak memiliki toolchain Swift.
Deklarasikan payload dan tipe kontennya
Tambahkan deklarasi tipe yang diekspor ke daftar properti informasi target. Di editor daftar properti Xcode, buat UTExportedTypeDeclarations sebagai array berisi satu kamus. Atur UTTypeIdentifier pada kamus tersebut ke com.example.shape-transfer, UTTypeDescription ke Synthetic Shape Transfer, dan UTTypeConformsTo sebagai array yang berisi public.data. Identifier ini hanya contoh; gunakan identifier unik untuk app Anda.
Deklarasi Swift dan identifier daftar properti harus cocok. Apple mensyaratkan entri daftar properti yang sesuai untuk tipe uniform kustom dan menjelaskan deklarasi yang diekspor untuk format yang didefinisikan app. Contoh ini mentransfer data, bukan file, jadi tidak menambahkan ekstensi nama file. Defining file and data types for your app.
Buat 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 konversi antara payload dan data transferable. Payload berisi sebuah nilai, bukan tampilan atau referensi koleksi yang dapat berubah. Konformansi Sendable yang eksplisit mengikuti aturan Swift bahwa anggota tersimpan pada struktur yang sendable juga harus sendable. Transferable, Sendable.
Dengan sengaja hanya ada satu representasi. Proksi teks akan menambahkan cara lain untuk menggunakan item, yang tidak berguna bagi kontrak ini. Apple mendokumentasikan urutan representasi dan format fallback; tambahkan itu hanya ketika penerima benar-benar memerlukannya. Making a view into a drag source.
Terima payload dan terapkan satu pembaruan koleksi
Buat 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 eksplisit memilih overload tujuan bertipe awal. Apple mendefinisikan nilai Boolean hasilnya sebagai berhasil atau gagal dan argumen lokasinya dalam sistem koordinat tampilan tujuan. Lokasi sengaja tidak digunakan di sini karena koleksi menambahkan bentuk di akhir, bukan mengatur posisinya. dropDestination(for:action:isTargeted:).
Aturan penerimaan memeriksa setiap payload yang diterima sebelum menambahkan apa pun. Versi yang tidak didukung menolak seluruh batch yang diterima; setiap payload valid mendapat identitas tujuan baru. Jadi, mengulangi drag yang sama dari palet akan membuat salinan lain. Aturan ini berlaku untuk nilai yang diteruskan ke closure; kegagalan decoding terjadi sebelum nilai tersebut menjadi ShapePayload.
State penargetan hanya mengubah tampilan. State ini tidak menyisipkan item atau menghapus apa pun dari sumber. Pemisahan tersebut juga membuat hasil pembatalan sederhana: tanpa drop yang diterima, koleksi tetap tidak berubah.
Gunakan penyedia item jika integrasi memerlukannya
Implementasi penyedia dapat membawa versi dan jenis bentuk yang sama. Pertahankan kontraknya dan ganti transport melalui langkah-langkah berikut:
- Dalam closure
.onDrag, buatNSItemProviderdan daftarkan representasi data dengan identifier tipe kustom yang sama, menggunakan visibilitas.ownProcess. - Tangkap nilai payload yang tidak dapat diubah atau byte yang telah disiapkan dalam handler pemuatannya. Encode nilai tersebut dan panggil handler penyelesaian representasi dengan data atau error.
- Konfigurasikan
.onDropagar menerima tipe kustom tersebut. - Di dalam aksi drop, segera mulai memuat setiap penyedia yang diterima dengan
loadDataRepresentation(forTypeIdentifier:completionHandler:). - Decode data yang dikembalikan, validasi versinya, lalu kumpulkan hasilnya. Lakukan mutasi koleksi pada main actor hanya setelah hasil yang diperlukan tersedia.
Apple mendokumentasikan pendaftaran data secara eksplisit dan visibilitas khusus app sendiri untuk penyedia. Metode pemuatannya bersifat asinkron dan mengembalikan objek progres. Identifier tipe yang cocok menunjukkan bahwa pemuatan dapat dicoba; itu tidak menjamin pemuatan dan decoding akan berhasil. registerDataRepresentation(forTypeIdentifier:visibility:loadHandler:), NSItemProviderRepresentationVisibility.ownProcess, loadDataRepresentation(forTypeIdentifier:completionHandler:).
Ada dua batas waktu yang perlu diperhatikan. Pertama, menurut Apple, pemuatan penyedia harus dimulai di dalam aksi .onDrop, meskipun penyelesaiannya boleh terjadi kemudian. Memindahkan pemanggilan pemuatan awal ke task tertunda di actor lain melanggar persyaratan yang didokumentasikan tersebut. Kedua, Apple memperingatkan agar pekerjaan ekspor dari handler representasi tidak dikirim ke main actor atau main thread karena dapat menyebabkan hang. Pisahkan proses ekspor dari mutasi UI; perbarui state koleksi setelah impor selesai. onDrop(of:isTargeted:perform:), onDrag(_:).
Untuk implementasi penyedia asinkron, bedakan penerimaan pekerjaan pemuatan dari penyelesaian transaksi model. Pertahankan urutan penyedia dengan hasil berindeks, laporkan kegagalan pemuatan atau decoding, dan biarkan koleksi tidak berubah jika kontrak mengharuskan semua item berhasil. Hindari membuang item yang gagal diam-diam dengan operasi pemfilteran. Ini adalah aturan implementasi yang diturunkan dari kebijakan penerimaan batch dalam contoh.
Pertahankan kepemilikan event AppKit dan semantik pemindahan
Jika tampilan AppKit yang sudah ada memiliki interaksi mouse, tetap mulai sesinya di sana. beginDraggingSession(with:event:source:) milik Apple menerima event mouse-down asal, item drag dengan frame yang menggunakan koordinat tampilan tersebut, serta sumber yang mengonformasi ke NSDraggingSource. Callback sumber mencakup awal sesi, pergerakan, dan penyelesaian atau pembatalan. beginDraggingSession(with:event:source:).
Batas representasi AppKit juga eksplisit: NSDraggingItem(pasteboardWriter:) memerlukan objek NSPasteboardWriting. Callback penyedia SwiftUI dan penulis pasteboard AppKit adalah antarmuka yang berbeda. Saat menjembatani tampilan AppKit kustom ke SwiftUI, biarkan tampilan AppKit tetap bertanggung jawab atas event dan sesinya; biarkan model bersama memiliki perubahan koleksi yang diterima. init(pasteboardWriter:).
Contoh ini melakukan penyalinan. Jika tugasnya berubah menjadi memindahkan item yang sudah ada antar koleksi, tambahkan identifier item yang stabil dan tentukan dengan tepat satu pemilik penghapusan. Store bersama dapat memindahkan item satu kali, atau tujuan dapat menyisipkannya sementara sumber menghapusnya setelah pemindahan dikonfirmasi selesai. Menggabungkan kedua strategi berisiko menghapus item dua kali.
Dokumentasi Apple saat ini mencakup DragConfiguration, overload tujuan drop bertipe berbasis sesi, dan pengamatan sesi drag untuk macOS 26. Sumber mengusulkan operasi yang didukung, tujuan memilih operasinya, dan penyelesaian sesi memungkinkan sumber merespons hasilnya. Periksa ketersediaan setiap simbol di SDK sebelum menggunakan jalur ini. DragConfiguration, dropDestination(for:isEnabled:action:), onDragSessionUpdated(_:).
Jangan menghapus item sumber saat penunjuk memasuki tujuan atau saat pratinjau muncul. Peristiwa tersebut terjadi sebelum penerimaan. Untuk pemindahan dalam app sendiri, invarian yang berguna adalah bahwa satu item logis hanya dimiliki oleh satu koleksi setelah transaksi selesai.
Verifikasi kontrak melalui hasil yang dapat diamati
Gunakan urutan berikut untuk memeriksa implementasi di Mac:
- Seret
CirclekeCollection: tujuan mendapat satu lingkaran dan entri palet tetap ada. - Ulangi drag: tujuan mendapat item kedua dengan identitas independen.
- Seret
Squareke luar tujuan lalu lepaskan: koleksi tidak berubah. - Ubah sementara versi payload sumber menjadi
2: tujuan melaporkan penolakan dan tidak menambahkan apa pun; setelah itu kembalikan ke1. - Untuk implementasi penyedia, sengaja kembalikan error encoding atau pemuatan: tampilkan kegagalan tanpa menyisipkan batch sebagian.
- Untuk implementasi pemindahan, batalkan drag dan pastikan item asli tetap menjadi milik koleksi asalnya.
Pilih SwiftUI bertipe untuk tugas penyalinan dua panel ini, kecuali ada persyaratan kompatibilitas atau integrasi konkret yang memerlukan penyedia atau AppKit. Bagian yang bertahan lama adalah kontraknya: sebuah nilai terdefinisi melintasi batas, tujuan memvalidasinya, dan satu pemilik model menerapkan perubahan yang diterima.
Sumber
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.