Untuk operasi seret dan lepas di dalam app Mac, sumber menyatakan operasi yang didukung, tujuan memilih salah satunya, lalu model Anda menerapkan hasilnya. Di SwiftUI pada macOS 26 atau lebih baru, konfigurasikan kedua ujungnya dan hapus item asli hanya setelah pemindahan selesai.
Artikel ini membuat interaksi desktop sederhana: seret satu lingkaran sintetis ke Salin di sini atau Pindahkan di sini. Semuanya tetap berada di memori. Contoh ini memperjelas operasi, identitas item, dan pembersihan sumber, lalu memetakan tanggung jawab yang sama ke AppKit.
Apa yang harus disepakati oleh sumber dan tujuan?
Payload yang dapat ditransfer menjelaskan apa yang berpindah antar-tampilan. Salin atau pindahkan menjelaskan apa yang terjadi pada item asli. Keduanya merupakan keputusan terpisah. Dokumentasi SwiftUI Apple menyatakan bahwa operasi seret secara default mendukung penyalinan; mengaktifkan pemindahan menambahkan operasi lain yang didukung. Tujuan kemudian memilih cara menerima item. DragConfiguration
Gunakan tiga tanggung jawab berikut untuk merancang interaksi:
| Tanggung jawab | Salin | Pindahkan |
|---|---|---|
| Kemampuan sumber | Tawarkan salin | Tawarkan pindahkan |
| Komit tujuan | Sisipkan item independen | Terima item yang sudah ada |
| Penyelesaian sumber | Pertahankan item asli | Hapus item asli setelah pemindahan selesai |
Untuk contoh lingkaran ini, salinan independen mendapat pengenal baru. Pemindahan mempertahankan pengenal sambil mengubah lokasinya. Ini adalah pilihan desain aplikasi yang selaras dengan persyaratan Swift bahwa suatu entitas yang dapat diidentifikasi memiliki identitas yang stabil; Swift menyerahkan cakupan dan durasi identitas tersebut kepada aplikasi. Identifiable
Konsekuensi praktisnya, mengembalikan operasi pindahkan tidak memindahkan elemen di dalam array Anda. Anda tetap perlu menyisipkannya di tujuan dan menghapusnya dari sumber. Apple secara khusus menunjukkan pengamatan terhadap operasi seret yang selesai untuk memberi tahu model kapan item sumber harus dihapus. onDragSessionUpdated(_:)
API SwiftUI apa yang sebaiknya digunakan app Mac?
Untuk implementasi ini, gunakan target deployment macOS 26 atau lebih baru dan SDK yang memuat modifier konfigurasi yang didokumentasikan. Tambahkan draggable(_:) ke sumber, dragConfiguration(_:) untuk menyatakan operasinya, dan dropConfiguration(_:) untuk memilih operasi tujuan. Kedua modifier konfigurasi tersebut didokumentasikan tersedia di macOS 26. dragConfiguration(_:), dropConfiguration(_:)
Overload dropDestination(for:isEnabled:action:) saat ini memberikan DropSession ke aksinya. Aksi tersebut mengembalikan Void. Ini berbeda dari overload lama yang menerima lokasi pada aksinya dan mengembalikan Boolean. Contoh ini menetapkan isEnabled: secara eksplisit agar overload yang dimaksud jelas. dropDestination(for:isEnabled:action:)
Di tujuan, periksa suggestedOperations sebelum memilih operasi. Untuk target umum yang mengutamakan pemindahan, pilih pindahkan jika tersedia, jika tidak pilih salin jika tersedia, dan jika tidak, pilih dilarang. Contoh Apple yang terdokumentasi menggunakan fallback dari pindahkan ke salin ini dan mengingatkan bahwa closure konfigurasi sering dijalankan, jadi buatlah tetap ringan. dropConfiguration(_:)
Kedua target kita sengaja memiliki fungsi tetap. Salin di sini menerima salin; Pindahkan di sini menerima pindahkan. Target mengembalikan dilarang jika operasi yang disebutkan tidak tersedia. Dengan begitu hasilnya dapat diprediksi tanpa memerlukan tombol modifier.
Buat contoh salin dan pindahkan lengkap di memori
Buat app SwiftUI untuk macOS, atur target deployment ke macOS 26 atau lebih baru, lalu ganti deklarasi app dan tampilan konten yang dihasilkan dengan satu file berikut. Contoh ini mentransfer string bawaan yang berisi pengenal item, sehingga tidak memerlukan tipe transferable atau deklarasi tipe khusus. Apple mendokumentasikan payload transferable untuk sumber seret dan payload string untuk tujuan lepas. draggable(_:), dropDestination(for:isEnabled:action:)
Ini adalah app minimal lengkap yang ditujukan untuk satu jendela terbuka dan satu item sumber. Saya memeriksa referensi API terhadap dokumentasi yang diperoleh; saya tidak dapat mengompilasi atau menjalankannya di sini karena Swift dan SDK macOS tidak tersedia.
import SwiftUI
import Foundation
@main
struct CopyMoveDemoApp: App {
var body: some Scene {
WindowGroup {
CopyMoveDemo()
}
}
}
struct CopyMoveDemo: View {
@State private var sourceID = UUID()
@State private var sourcePresent = true
@State private var copiedIDs: [UUID] = []
@State private var movedIDs: [UUID] = []
@State private var moveReceived = false
private var sourceConfiguration: DragConfiguration {
DragConfiguration(
operationsWithinApp: .init(allowMove: true),
operationsOutsideApp: .init(
allowCopy: false,
allowMove: false,
allowDelete: false
)
)
}
var body: some View {
VStack(spacing: 24) {
Text("Drag the circle to a target")
.font(.headline)
if sourcePresent {
Circle()
.fill(.blue)
.frame(width: 90, height: 90)
.overlay(Text("Circle").foregroundStyle(.white))
.draggable(sourceID.uuidString)
.dragConfiguration(sourceConfiguration)
.onDragSessionUpdated { session in
if session.phase == .initial {
moveReceived = false
}
if session.phase == .ended(.move), moveReceived {
sourcePresent = false
}
if case .ended = session.phase {
moveReceived = false
}
}
} else {
Text("Original moved")
.frame(width: 90, height: 90)
}
HStack(alignment: .top, spacing: 20) {
target(moving: false)
target(moving: true)
}
}
.padding(24)
.frame(minWidth: 520, minHeight: 320)
}
private func target(moving: Bool) -> some View {
let operation: DropOperation = moving ? .move : .copy
let ids = moving ? movedIDs : copiedIDs
return VStack(spacing: 10) {
Text(moving ? "Move here" : "Copy here")
.font(.headline)
ForEach(ids, id: \.self) { id in
Text("Circle \(id.uuidString.prefix(4))")
}
Spacer(minLength: 20)
}
.frame(width: 210, height: 150)
.background(.blue.opacity(0.08))
.contentShape(Rectangle())
.dropDestination(for: String.self, isEnabled: true) {
tokens, session in
guard session.localSession != nil,
sourcePresent,
tokens == [sourceID.uuidString] else {
return
}
if moving {
guard !movedIDs.contains(sourceID) else { return }
movedIDs.append(sourceID)
moveReceived = true
} else {
copiedIDs.append(UUID())
}
}
.dropConfiguration { session in
guard sourcePresent,
session.localSession != nil,
session.itemsCount == 1,
session.suggestedOperations.contains(operation) else {
return DropConfiguration(operation: .forbidden)
}
return DropConfiguration(operation: operation)
}
}
}
Konfigurasi sumber menawarkan operasi salin dan pindahkan di dalam app. Operasi di luar app dinonaktifkan menggunakan initializer macOS untuk operasi di luar app. Tujuan juga memeriksa localSession, yang menurut dokumentasi Apple tidak ada jika seret berasal dari tempat lain. Pengaturan ini menjaga fokus contoh pada item dalam memori di jendelanya sendiri. DragConfiguration.OperationsWithinApp, init(allowCopy:allowMove:allowDelete:), DropSession
Aksi lepas memvalidasi token yang tepat sebelum menyisipkan apa pun. Salin menambahkan pengenal baru. Pindahkan menambahkan pengenal asli dan mencatat bahwa tujuan ini menerimanya. Sumber kemudian menghapus item asli yang ditampilkan hanya jika sesi berakhir dengan operasi pindahkan dan ada tanda terima tersebut. Tanda terima ini merupakan pengaman pada tingkat aplikasi untuk demonstrasi terbatas ini, bukan fitur transaksi SwiftUI.
Deklarasi fase seret Apple saat ini mencakup initial, active, ending, ended, dan dataTransferCompleted. Contoh menggunakan kasus ended(DropOperation) yang didokumentasikan untuk hasil operasi. Sumber tidak dihapus saat kursor melayang atau selama fase akhir yang lebih awal. DragSession.Phase
Tegaskan batas kepemilikan dan gestur
String tersebut adalah token pencarian, bukan model lingkaran secara utuh. Tujuan mencarinya pada status sumber yang dimiliki tampilan ini. Untuk beberapa dokumen atau jendela, ganti pencarian lokal tersebut dengan pemilik model yang eksplisit dan tanda terima yang terkait dengan sesi seret serta item sebenarnya. Seret yang berasal dari dalam app tidak dengan sendirinya menentukan dokumen mana yang memiliki suatu item.
Untuk beberapa item yang dapat diseret, gunakan pengenal model yang stabil dan, jika tersedia, ambil pengenal item yang diseret dari sesi. Apple menyediakan draggedItemIDs(for:) untuk payload yang dapat diidentifikasi atau pengenal yang diberikan secara terpisah. Hindari menghapus item apa pun yang kebetulan sedang dipilih saat penyelesaian terjadi; pilihan dan identitas item yang diseret merupakan masukan berbeda. DragSession
Biarkan modifier draggable(_:) SwiftUI memasang interaksi seret. Jangan lakukan penghapusan model saat membuat payload. Apple mendokumentasikan bahwa modifier tersebut menambahkan gestur yang sesuai dan mengingatkan agar closure ekspor tidak dikirim ke aktor utama. draggable(_:)
Bagaimana kesepakatan yang sama bekerja di AppKit?
Gunakan AppKit jika target deployment atau arsitektur tampilan yang sudah ada memerlukan siklus hidup seret yang eksplisit. Protokol sumbernya menyatakan operasi yang diizinkan melalui draggingSession(_:sourceOperationMaskFor:). Konteks tersebut memungkinkan Anda membedakan tujuan di dalam dan di luar aplikasi. Untuk interaksi di atas, tawarkan salin dan pindahkan di dalam app serta tanpa operasi di luar app. draggingSession(_:sourceOperationMaskFor:)
Tujuan mendaftarkan diri untuk tipe pasteboard yang relevan, lalu memilih operasi di draggingEntered(_:) dan memperbarui pilihan itu bila perlu di draggingUpdated(_:). Apple mewajibkan tujuan mempertimbangkan irisan antara operasi sumber dan operasi serta tipe data yang didukungnya sendiri. Kembalikan satu operasi, atau tidak ada operasi jika tidak ada irisan yang dapat diterima. NSDraggingDestination, draggingEntered(_:)
Lakukan penyisipan di performDragOperation(_:), setelah persiapan berhasil. Kembalikan true hanya jika tujuan menerima data. Sumber menerima operasi yang selesai melalui draggingSession(_:endedAt:operation:); gunakan hasil tersebut bersama aturan kepemilikan model Anda untuk menyelesaikan penghapusan di sisi sumber saat pemindahan. performDragOperation(_:), draggingSession(_:endedAt:operation:)
Jika Anda menyematkan permukaan seret AppKit dalam antarmuka SwiftUI, biarkan permukaan itu menangani interaksi AppKit. beginDraggingSession(with:event:source:) menerima peristiwa mouse-down yang memulai seret, dan bingkai item yang diseret menggunakan sistem koordinat tampilan. Pertahankan hubungan peristiwa dan koordinat tersebut alih-alih membuat peristiwa palsu dari nilai gestur SwiftUI yang muncul belakangan. beginDraggingSession(with:event:source:)
Kode tujuan SwiftUI yang lebih lama dapat mengembalikan DropProposal(operation: .move) dari DropDelegate.dropUpdated(info:). Ini menyatakan usulan dari tujuan. Hal itu tidak menggantikan konfigurasi kemampuan sumber, pengamatan operasi yang selesai, atau mutasi model Anda. Jika API sumber SwiftUI yang lebih baru tidak tersedia dan Anda memerlukan kendali eksplisit di kedua ujung, siklus hidup sumber dan tujuan AppKit menyediakan jalur tersebut. dropUpdated(info:), NSDraggingSource
Verifikasi operasi dan model secara bersamaan
Untuk contoh ini, gunakan daftar pemeriksaan status yang diharapkan berikut. Ini adalah syarat penerimaan yang diturunkan dari implementasi, bukan hasil pengujian yang dilaporkan:
| Tindakan | Asli | Target salin | Target pindahkan |
|---|---|---|---|
Lepaskan sekali di Salin di sini |
Tetap ada | Mendapat satu pengenal baru | Tidak berubah |
| Salin lagi | Tetap ada | Mendapat satu pengenal baru lagi | Tidak berubah |
Lepaskan di Pindahkan di sini |
Hilang setelah pemindahan selesai | Tidak berubah | Menerima pengenal asli |
| Batalkan atau lepaskan jauh dari kedua target | Tetap ada | Tidak berubah | Tidak berubah |
Jika perilakunya berbeda, periksa tahap-tahapnya secara berurutan: kemampuan sumber, operasi tujuan, penyisipan tujuan, dan penyelesaian sumber. Duplikasi item asli setelah pemindahan mengarah pada pembersihan sumber yang tidak dilakukan. Dua penghapusan mengarah pada pemilik model yang saling bersaing. Salinan yang tidak diharapkan mengarah pada sumber yang tidak menawarkan pindahkan atau tujuan yang memilih salin.
Pastikan tanggung jawab akhirnya jelas: operasi hasil negosiasi menjelaskan transfer, sedangkan model Anda menerapkan lokasi dan identitas item. Tawarkan operasi yang Anda dukung, pilih operasi yang diizinkan di tujuan, dan lakukan setiap perubahan model satu kali pada batas siklus hidup yang terdokumentasi.
Sumber
DragConfiguration — Apple
Identifiable — Apple
onDragSessionUpdated(_:) — Apple
dragConfiguration(_:) — Apple
dropConfiguration(_:) — Apple
draggable(_:) — Apple
DropSession — Apple
DragSession.Phase — Apple
DragSession — Apple
NSDraggingDestination — Apple
draggingEntered(_:) — Apple
performDragOperation(_:) — Apple
dropUpdated(info:) — Apple
NSDraggingSource — Apple