Pulihkan bentuk yang dimalapkan, garis luar dan label seretan melalui panggil balik tamat sesi sumber seretan. Panggil balik keluar destinasi hanya bermaksud penuding meninggalkan sasaran itu. Untuk app Mac, gunakan API sesi sumber SwiftUI jika tersedia, atau biarkan sumber AppKit mengendalikan seretan.

Panduan ini menangani satu tugas: mengembalikan bentuk sintetik kepada rupa asal selepas pengguna selesai menyeret, termasuk apabila tiada destinasi menerimanya. Contoh ini menyimpan kandungannya dalam memori dan menganggap lepasan yang diterima sebagai salinan, jadi pemulihan maklum balas tidak memerlukan sumber dipadamkan atau dialihkan.

Mengapa maklum balas seretan kekal aktif di luar sasaran lepasan?

Pelaksanaan lazim menetapkan bendera isDragging apabila seretan bermula dan mengosongkannya dalam performDrop. Ini mengaitkan pembersihan dengan peristiwa yang hanya boleh dikendalikan oleh destinasi yang menerima seretan. Melepaskan di ruang kosong dalam tetingkap menyebabkan tiada destinasi yang bertanggungjawab menetapkan semula sumber.

Apple mentakrifkan DropDelegate sebagai antara muka untuk paparan yang diubah suai agar menerima lepasan. Kaedah dropExited(info:) melaporkan bahawa seretan yang disahkan telah keluar dari paparan itu. Seretan mungkin diteruskan ke sasaran lain, kembali ke sasaran pertama atau berakhir di tempat lain. Oleh itu, keluar dari sasaran tidak dapat memastikan bahawa sesi sumber telah berakhir. DropDelegate

Perbezaan yang sama terpakai pada dropDestination(for:action:isTargeted:): isTargeted menerangkan sama ada penuding berada di dalam kawasan sasaran. Gunakannya untuk sempadan atau latar belakang sasaran. Biarkan panggil balik sesi sumber mengawal pemalapan sumber atau label seretan. dropDestination(for:action:isTargeted:)

Peraturan reka bentuk yang berguna terbit daripada batas yang didokumenkan ini: tetapkan setiap keadaan visual kepada pemilik peristiwa yang boleh menamatkannya dengan pasti.

Maklum balas atau hasil Pemilik Titik tetapan semula atau pelaksanaan
Bentuk sumber yang dimalapkan Sumber seretan Sesi sumber berakhir
Destinasi yang diserlahkan Destinasi lepasan Penuding keluar, dengan pembersihan defensif apabila seretan setempat selesai
Item sintetik yang diterima Destinasi/model Lepasan disahkan dan berjaya
Sumber sementara yang dieksport Kitar hayat pemindahan Pemindahan selesai, jika berkenaan

Pendekatan yang disokong manakah patut digunakan oleh app Mac?

Rujukan semasa Apple menyenaraikan onDragSessionUpdated(_:) sebagai tersedia pada macOS 26 dan lebih baharu. Ia memerhatikan kemas kini kepada sesi seretan terdekat dalam hierarki paparan anaknya. Lampirkannya terus di luar pengubah suai seretan sumber supaya skopnya jelas. Gunakan SDK yang benar-benar mengisytiharkan kaedah itu; ketersediaan semasa jalan tidak membolehkan kod dikompil apabila pengisytiharan tiada dalam SDK. onDragSessionUpdated(_:)

Terdapat percanggahan dalam dokumentasi yang wajar ditangani secara khusus: contoh pengubah suai menunjukkan fasa berakhir dengan argumen lokasi dan operasi, manakala rujukan fasa khusus mengisytiharkan ended(DropOperation). Contoh di bawah memadankan .ended tanpa mengekstrak nilai berkaitan kerana pemulihan visual tidak memerlukannya. Semak pengisytiharan dalam SDK yang dipasang sebelum menambah tingkah laku khusus operasi. DragSession.Phase.ended(_:)

Jika sasaran penggunaan atau SDK anda tidak dapat menggunakan panggil balik SwiftUI, sumber seretan AppKit menyediakan batas penyiapan yang didokumenkan. Apple menyatakan dengan jelas bahawa sumber menerima draggingSession(_:endedAt:operation:) apabila seretannya berakhir atau dibatalkan. Sumber tersuai mesti memulakan sesi itu sendiri. beginDraggingSession(with:event:source:)

Jika anda hanya perlu mengubah kedudukan bentuk dalam satu kanvas, pertimbangkan DragGesture setempat dan bukannya sesi seret dan lepas sistem. @GestureState ditetapkan semula apabila gerak isyaratnya menjadi tidak aktif, jadi ia sesuai untuk maklum balas sementara gerak isyarat itu. Asingkan pilihan tersebut daripada seretan sistem yang membekalkan kandungan boleh pindah kepada destinasi. GestureState

Laksanakan pembersihan yang dimiliki sumber dalam SwiftUI

Cipta app SwiftUI macOS menggunakan SDK yang mendedahkan onDragSessionUpdated(_:), tetapkan sasaran penggunaan kepada macOS 26 atau lebih baharu, dan gantikan pengisytiharan app serta paparan kandungan yang dijana dengan atur cara lengkap ini. Ia membekalkan satu rentetan sintetik, shape.circle, dan satu kawasan penerima.

Sumber menggunakan draggable(_:) untuk membekalkan kandungan boleh pindah. Destinasi menerima tepat satu rentetan yang sepadan dan mengembalikan false untuk input lain. Apple mendokumenkan hasil Boolean destinasi sebagai petunjuk sama ada lepasan berjaya. draggable(_:), dropDestination(for:action:isTargeted:)

import SwiftUI

@main
struct DragFeedbackDemoApp: App {
    var body: some Scene {
        WindowGroup {
            DragFeedbackDemo()
        }
    }
}

struct DragFeedbackDemo: View {
    private let payload = "shape.circle"

    @State private var isDragging = false
    @State private var isTargeted = false
    @State private var acceptedCopies = 0

    var body: some View {
        VStack(spacing: 24) {
            Text("Drag the circle into the receiving area")

            Circle()
                .fill(Color.blue)
                .frame(width: 72, height: 72)
                .opacity(isDragging ? 0.4 : 1)
                .draggable(payload)
                .onDragSessionUpdated { session in
                    switch session.phase {
                    case .initial, .active:
                        isDragging = true
                    case .ended, .dataTransferCompleted:
                        restoreFeedback()
                    default:
                        break
                    }
                }

            Text(isDragging ? "Dragging" : "Ready")

            RoundedRectangle(cornerRadius: 12)
                .fill(isTargeted
                      ? Color.blue.opacity(0.2)
                      : Color.gray.opacity(0.12))
                .overlay {
                    Text("Receiving area")
                }
                .frame(width: 280, height: 120)
                .dropDestination(for: String.self) { items, _ in
                    guard items.count == 1,
                          items.first == payload else {
                        return false
                    }
                    acceptedCopies += 1
                    return true
                } isTargeted: { targeted in
                    isTargeted = targeted
                }

            Text("Accepted copies: \(acceptedCopies)")
        }
        .padding(32)
        .frame(minWidth: 380, minHeight: 400)
    }

    private func restoreFeedback() {
        isDragging = false
        isTargeted = false
    }
}

Kedua-dua bendera mempunyai tugas yang berbeza. isDragging mengubah kelegapan sumber. isTargeted mengubah isian kawasan penerima. Oleh itu, memasuki dan meninggalkan kawasan penerima tidak boleh memulihkan sumber secara tidak sengaja semasa seretan masih berlangsung.

Rujukan fasa mengenal pasti initial sebagai sesi yang bakal bermula, active sebagai pergerakan dan ended sebagai penyiapan. Contoh ini memulihkan maklum balas pada ended; ia juga mengulangi tetapan semula yang sama tanpa mudarat pada dataTransferCompleted. Ia tidak sekali-kali bergantung sepenuhnya pada penyiapan pemindahan untuk memulihkan seretan yang tidak berjaya. DragSession.Phase

Apple menerangkan dataTransferCompleted sebagai titik apabila item yang diseret telah dipindahkan dan item sementara boleh dibersihkan. Contoh ini tidak mempunyai sumber eksport sementara. Dalam pelaksanaan yang lebih besar, asingkan pembersihan visual daripada pembersihan sumber supaya pemulihan kelegapan tidak membuang sesuatu yang masih diperlukan oleh pemindahan. DragSession.Phase.dataTransferCompleted

Bulatan kekal dalam hierarki sepanjang sesi. Lepasan yang diterima menambah kiraan destinasi; lepasan yang tidak diterima tidak mengubah kandungan. Ini menjadikan contoh ini berguna untuk mendiagnosis maklum balas secara berasingan daripada semantik penyusunan semula atau pemindahan. Kod ini diperiksa berdasarkan pengisytiharan yang diperoleh; ia tidak dikompil atau dijalankan kerana persekitaran yang tersedia ialah Linux tanpa SwiftUI.

Bagaimanakah sandaran AppKit patut mengekalkan pemilikan peristiwa?

Untuk versi macOS lama yang masih disokong, gantikan pelaksanaan sumber seretan dengan NSView yang dihoskan melalui NSViewRepresentable. Representable mencipta dan mengemas kini paparan natif serta menghubungkan panggil baliknya dengan keadaan SwiftUI. SwiftUI mengawal reka letak paparan yang diwakilkan, jadi gunakan pengubah suai saiz SwiftUI dan bukannya menetapkan bingkai paparan yang diwakilkan sendiri. NSViewRepresentable

Laksanakan sandaran sebagai satu urutan yang dimiliki sumber:

  1. Dalam mouseDown(with:) paparan sumber natif, simpan peristiwa tekan tetikus sebenar yang dihantar kepada paparan itu.
  2. Dalam mouseDragged(with:), gunakan ambang seretan pilihan anda dan pastikan sesi kedua tidak dimulakan.
  3. Cipta NSDraggingItem daripada penulis papan klip dalam memori yang membawa pengecam item sintetik.
  4. Sediakan imej bentuk yang dijana dan tetapkan bingkai seretannya dalam sistem koordinat paparan sumber.
  5. Panggil beginDraggingSession(with:event:source:) menggunakan peristiwa tekan tetikus yang disimpan dan objek yang melaksanakan NSDraggingSource.
  6. Paparkan maklum balas sumber daripada draggingSession(_:willBeginAt:) dan pulihkannya tanpa syarat daripada draggingSession(_:endedAt:operation:).
  7. Kosongkan peristiwa input yang disimpan dan rekod sesi apabila penjejakan selesai. Pastikan sumber dan pemilik panggil baliknya kekal hidup sehingga sesi selesai.

Butiran ini mengikuti kontrak nyata AppKit: kaedah mula sesi menerima peristiwa tekan tetikus yang memulakannya, bingkai item seretan menggunakan sistem koordinat paparan dan sumber menerima mesej kitar hayat. NSDraggingItem menerima objek yang mematuhi NSPasteboardWriting; kaedah mudah imej tunggalnya menetapkan bingkai seretan dan kandungan. beginDraggingSession(with:event:source:), init(pasteboardWriter:), setDraggingFrame(_:contents:)

Untuk interaksi dalam app ini, kembalikan .copy bagi NSDraggingContext.withinApplication dan set operasi kosong untuk konteks lain. Ini ialah pilihan nyata untuk menawarkan salinan setempat sahaja. AppKit bertanya kepada sumber operasi yang dibenarkannya melalui draggingSession(_:sourceOperationMaskFor:). draggingSession(_:sourceOperationMaskFor:)

Pulihkan rupa bagi setiap hasil penyiapan, bukan hanya salinan yang berjaya. Pastikan sebarang perubahan model bergantung pada operasi yang benar-benar dilaksanakan oleh app anda. Titik dalam panggil balik penyiapan menggunakan koordinat skrin; jangan bandingkannya secara langsung dengan sempadan setempat sasaran SwiftUI. draggingSession(_:endedAt:operation:)

Paparan natif lutsinar yang diletakkan di belakang .draggable SwiftUI sedia ada tidak menjadi sumber sesi itu hanya kerana mematuhi NSDraggingSource. Sumbernya ialah objek yang dihantar apabila sesi bermula. Pilih satu pemilik untuk permulaan dan penyiapan, dan hadkan permukaan seretan natif kepada kawasan interaksi yang dimaksudkan bagi bentuk itu.

Mengapa tidak menggunakan pemantauan lepas tetikus atau penyahinisialisasian pembekal?

Pemantau lepas tetikus seluruh app memerhatikan penghantaran input, iaitu batas yang berbeza daripada penyiapan seretan. Apple mendokumenkan bahawa pemantau peristiwa setempat tidak menerima peristiwa yang digunakan oleh gelung penjejakan bersarang. Batasan itu menjadikan pemantau pengganti yang tidak sesuai bagi panggil balik penyiapan sumber yang didokumenkan. addLocalMonitorForEvents(matching:handler:)

Begitu juga, deinit pembekal item melaporkan jangka hayat objek. Swift memanggil penyahinisialisasi sejurus sebelum sesuatu tika dinyahagihkan; kontrak itu tidak menentukan bila seretan berakhir. Menggunakan pemusnahan pembekal sebagai isyarat penyiapan mengaitkan keadaan UI dengan pemilikan rujukan dan bukannya kitar hayat seretan. Deinitialization

Menambah DragGesture kedua bersama seretan sistem mempunyai masalah konsep yang sama: keadaannya dimiliki oleh gerak isyarat itu. Gunakan @GestureState apabila gerak isyarat setempat memiliki interaksi; gunakan panggil balik sesi sumber seretan apabila seretan sistem memilikinya. GestureState

Sahkan pembaikan menggunakan matriks penyiapan

Gunakan bulatan sintetik untuk menyemak setiap laluan secara berasingan. Ini ialah tingkah laku yang dijangka bagi pelaksanaan ini, bukannya hasil ujian yang dilaporkan.

Interaksi Maklum balas sumber yang dijangka Hasil destinasi yang dijangka
Lepaskan bulatan dalam kawasan penerima Kembali normal Kiraan bertambah sekali
Lepaskan di ruang kosong dalam tetingkap Kembali normal Kiraan kekal sama
Tinggalkan kawasan penerima semasa masih menyeret Kekal dimalapkan Sorotan sasaran dikosongkan
Masuk semula dan lengkapkan lepasan yang sah Kembali normal apabila selesai Kiraan bertambah sekali
Lepaskan di luar tetingkap app tanpa destinasi yang menerima Kembali normal Kiraan kekal sama
Batalkan seretan aktif Kembali normal Kiraan kekal sama
Ulang beberapa seretan yang gagal Setiap sesi pulih seperti biasa Tiada salinan diterima terkumpul

Jika sumber kekal dimalapkan, mula-mula semak sama ada pemerhati penyiapannya membalut sumber seretan sebenar dan sama ada sumber itu masih wujud. Jika hanya sorotan sasaran yang melekat, periksa pengendalian masuk/keluar sasaran dan pembersihan bersama. Jika kiraan diterima berubah apabila lepasan ditolak, periksa pengesahan dan pengubahan model secara berasingan daripada pemulihan visual.

Pembaikan yang berkekalan menjadikan pemilikan penyiapan jelas: sumber memulihkan maklum balas seluruh sesi, destinasi mengendalikan keadaan penuding dan kandungan yang diterima, manakala pembersihan pemindahan mengikut kitar hayat yang didokumenkan. Dengan tanggungjawab ini diasingkan, melepaskan di luar semua sasaran tidak meninggalkan keadaan visual yang menunggu panggil balik destinasi.

Sumber