Untuk kanvas Mac dengan zon jatuhan bertindih, letakkan pengendalian jatuhan natif pada satu leluhur yang stabil dan tentukan destinasi logik daripada keadaan model semasa. Semak semula kelayakan apabila mengesahkan jatuhan; panggilan balik yang ditolak bukan serahan kepada paparan lain yang didokumenkan.

Artikel ini membina interaksi tersebut untuk aplikasi yang mengandungi label bentuk sintetik dan dua panel bertindih. Tugas khususnya ialah membolehkan panel bawah menerima jatuhan apabila panel atas berhenti menerima item, termasuk apabila kelayakannya berubah semasa seretan. Dasar penghalaan di bawah ialah reka bentuk aplikasi yang diterbitkan daripada panggilan balik yang didokumenkan.

Mengapakah perubahan keadaan tidak menyerahkan seretan kepada sasaran lain dengan pasti?

Mulakan dengan memisahkan tiga soalan: paparan natif yang menerima seretan, panel logik yang dianggap layak oleh aplikasi anda, dan model yang menerima item akhir. Perubahan keadaan boleh mengubah jawapan anda kepada soalan kedua tanpa menetapkan jawapan baharu kepada soalan pertama.

Apple mendokumenkan validateDrop(info:) SwiftUI sebagai pengesahan apabila jatuhan yang serasi memasuki destinasi. Apple mendokumenkan dropUpdated(info:) sebagai kemas kini apabila jatuhan yang telah disahkan bergerak di dalam paparan yang diubah suai. Kedua-dua huraian itu tidak menjanjikan carian destinasi baharu setiap kali keadaan paparan pemilik yang tidak berkaitan berubah. Pengesahan SwiftUI dan kemas kini jatuhan.

Sempadan AppKit lebih jelas: hanya satu destinasi menerima urutan mesej draggingUpdated(_:) pada satu-satu masa. Apabila dua paparan bertindih ialah destinasi yang sah, paparan paling atas menerima mesej tersebut sehingga imej dilepaskan atau diseret keluar. Ini menerangkan penghalaan AppKit; ia tidak menetapkan kontrak keutamaan adik-beradik SwiftUI yang umum. Kemas kini destinasi AppKit.

Memulangkan false daripada tindakan jatuhan SwiftUI yang lebih lama melaporkan bahawa jatuhan tidak berjaya. Apple tidak menerangkan nilai pulangan itu sebagai permintaan untuk mencuba semula destinasi di bawahnya. Begitu juga, memulangkan nil daripada dropUpdated(info:) mengekalkan cadangan sah yang terakhir atau lalai kepada penyalinan; ini tidak bermaksud “luluskan.” Pengendalian jatuhan berasaskan penutupan dan tingkah laku cadangan.

Akibat praktikalnya ialah menjadikan sandaran sebagai peraturan aplikasi yang jelas. Untuk satu kanvas, satu penerima natif boleh memilih antara beberapa panel logik tanpa bergantung pada serahan natif antara penerima anak yang bertindih.

Tentukan pemilikan dan sandaran sebelum memasang pengubah suai

Dalam contoh ini, panel jingga berada di belakang panel biru. Jingga sentiasa menerima label sampel. Biru hanya menerimanya semasa blueEnabled bernilai benar. Tingkah laku yang dikehendaki ialah:

Lokasi penuding Biru menerima jatuhan Penerima logik
Kawasan jingga sahaja Mana-mana nilai Jingga
Kawasan bertindih Ya Biru
Kawasan bertindih Tidak Jingga
Kawasan biru sahaja Tidak Tiada
Di luar kedua-dua panel Mana-mana nilai Tiada

Jadual ini menjadi panduan keputusan untuk pelaksanaan. Khususnya, menyahdayakan biru tidak menjadikan setiap titik di dalam segi empat tepatnya sebagai destinasi jingga. Jingga hanya menerima item apabila penuding juga berada di dalam segi empat tepat jingga.

Simpan keadaan yang mengawal penghalaan di atas panel visual. Apple mengesyorkan agar nilai keadaan diletakkan pada paparan tertinggi yang memerlukannya dan dikongsi dengan paparan turunannya. Di sini, satu induk memiliki model yang mengandungi kelayakan, geometri dan item yang diterima. Segi empat tepat anak menentukan rupa, bukannya mengendalikan jatuhan secara berasingan. Pemilikan keadaan SwiftUI.

Baca model itu apabila tindakan jatuhan dijalankan. Elakkan menyimpan nilai Boolean kelayakan atau pengecam destinasi semasa kemasukan lalu menganggapnya berwibawa kemudian. Penutupan Swift boleh mengekalkan nilai dan rujukan daripada konteks penciptaannya; rujukan yang dikekalkan juga boleh terus menunjuk kepada model lama selepas pemilik diganti. Semak objek yang benar-benar ditangkap oleh panggilan balik. Penangkapan penutupan Swift.

Untuk penggantian pemilik, pilih dasar yang berasingan: sama ada tentukan destinasi berdasarkan pemilik baharu atau tolak kerja milik pemilik terdahulu. Reka bentuk penolakan yang berguna menggunakan nombor semakan pada pemilik, menaikkannya apabila penggantian berlaku, kemudian membandingkan semakan yang ditangkap sebelum mengesahkan kerja tertangguh. Nilai sementara “sedang diserlahkan” tidak sepatutnya menentukan pemilikan.

Bina satu permukaan jatuhan SwiftUI yang stabil

Contoh lengkap berikut memerlukan aplikasi SwiftUI macOS dengan sasaran penggunaan macOS 26 dan SDK yang mengandungi dropDestination(for:isEnabled:action:). Apple mendokumenkan beban berlebihan itu sebagai menerima jenis Transferable, bendera yang didayakan dan tindakan yang menerima item yang dinyahkod bersama DropSession. Destinasi itu memenuhi saiz dan kedudukan paparan yang diubah suai. API destinasi jatuhan semasa.

Cipta projek Mac App baharu menggunakan SwiftUI. Gantikan pengisytiharan aplikasi yang dijana dengan dua blok kod di bawah dan letakkannya berturutan dalam satu fail Swift. Kedua-duanya membentuk satu program. Program ini hanya menggunakan rentetan dan segi empat tepat dalam memori. Label sumber kekal tersedia selepas jatuhan, jadi contoh ini berfungsi sebagai operasi salin.

Model dan penghalaan deterministik

Model menyemak biru terlebih dahulu untuk menyatakan keutamaan visual yang dipilih. Paparan dan penghalaan menggunakan segi empat tepat yang sama. Kaedah komitnya mengira semula penerima menggunakan kelayakan semasa dan mengesahkan keseluruhan kelompok sampel sebelum menambah apa-apa.

import SwiftUI
import Combine

@MainActor
final class CanvasModel: ObservableObject {
    enum Zone: String {
        case orange = "Orange"
        case blue = "Blue"
    }

    let orangeRect = CGRect(x: 30, y: 30, width: 230, height: 170)
    let blueRect = CGRect(x: 150, y: 100, width: 230, height: 170)
    let samples = ["Circle", "Square"]

    @Published var blueEnabled = true
    @Published private(set) var orangeItems: [String] = []
    @Published private(set) var blueItems: [String] = []
    @Published private(set) var message = "Drag a sample onto a panel."

    func recipient(at point: CGPoint) -> Zone? {
        if blueEnabled && blueRect.contains(point) {
            return .blue
        }
        if orangeRect.contains(point) {
            return .orange
        }
        return nil
    }

    func receive(_ items: [String], at point: CGPoint, isLocal: Bool) {
        guard isLocal,
              !items.isEmpty,
              items.allSatisfy({ samples.contains($0) }) else {
            message = "No items added: use this window's sample labels."
            return
        }
        guard let zone = recipient(at: point) else {
            message = "No items added: this location has no eligible panel."
            return
        }
        switch zone {
        case .orange:
            orangeItems.append(contentsOf: items)
        case .blue:
            blueItems.append(contentsOf: items)
        }
        message = "Added \(items.joined(separator: ", ")) to \(zone.rawValue)."
    }
}

Aplikasi dan penerima jatuhan

Segi empat tepat induk memiliki satu-satunya pengubah suai jatuhan. Tiada satu pun panel berwarna mendaftarkan destinasi yang bersaing. DropSession memberikan lokasi dalam ruang koordinat setempat destinasi dan localSession yang mengenal pasti seretan yang bermula dalam aplikasi. Model juga menyemak nilai sampel. Maklumat sesi jatuhan.

@main
struct DropRoutingDemoApp: App {
    var body: some Scene {
        WindowGroup {
            CanvasDemo()
        }
    }
}

@MainActor
struct CanvasDemo: View {
    @StateObject private var model = CanvasModel()
    @State private var scheduledChange: Task<Void, Never>?

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            HStack {
                ForEach(model.samples, id: \.self) { item in
                    Text(item)
                        .padding(10)
                        .background(Color.gray.opacity(0.15))
                        .draggable(item)
                }
            }

            Toggle("Blue accepts drops", isOn: $model.blueEnabled)

            Button("Toggle blue in 3 seconds") {
                scheduledChange?.cancel()
                scheduledChange = Task { @MainActor in
                    do {
                        try await Task.sleep(for: .seconds(3))
                    } catch {
                        return
                    }
                    model.blueEnabled.toggle()
                }
            }

            ZStack(alignment: .topLeading) {
                Rectangle().fill(Color.gray.opacity(0.08))
                panel("Orange", rect: model.orangeRect,
                      color: .orange, opacity: 0.55)
                panel("Blue", rect: model.blueRect,
                      color: .blue,
                      opacity: model.blueEnabled ? 0.55 : 0.15)
            }
            .frame(width: 420, height: 300)
            .dropDestination(for: String.self, isEnabled: true) {
                items, session in
                model.receive(items, at: session.location,
                              isLocal: session.localSession != nil)
            }

            Text(model.message)
            Text("Orange: \(model.orangeItems.joined(separator: ", "))")
            Text("Blue: \(model.blueItems.joined(separator: ", "))")
        }
        .padding(20)
        .onDisappear {
            scheduledChange?.cancel()
        }
    }

    private func panel(_ title: String, rect: CGRect,
                       color: Color, opacity: Double) -> some View {
        Rectangle()
            .fill(color.opacity(opacity))
            .overlay(Text(title))
            .frame(width: rect.width, height: rect.height)
            .position(x: rect.midX, y: rect.midY)
    }
}

draggable(_:) SwiftUI memasang gerak isyarat yang diperlukan untuk memulakan seretan melalui input pengguna. Butang tertunda hanya mengubah keadaan kelayakan contoh; ia tidak memulakan, memulakan semula atau memindahkan sesi seretan. Tingkah laku sumber seretan SwiftUI.

Tekan Toggle blue in 3 seconds, kemudian seret Circle ke kawasan bertindih dan tahan di situ sehingga biru memudar. Lepaskan di dalam kawasan bertindih: penerima yang dimaksudkan ialah jingga. Jika biru dinyahdayakan, lepaskan di kawasan biru yang tidak bertindih: tiada satu pun tatasusunan item berubah dan status menerangkan sebabnya.

Penerima ini kekal didayakan di seluruh kanvas supaya dapat menggunakan dasar penghalaan yang lengkap. Oleh itu, contoh ini tidak menyediakan kursor penolakan natif yang khusus mengikut lokasi atau sorotan apabila penuding melayang. Status melaporkan penerimaan aplikasi selepas pelepasan. Tambahkan pengendalian cadangan dan keadaan melayang secara berasingan jika produk memerlukan maklum balas tersebut.

Kod ini disemak berasaskan pengisytiharan API yang diperoleh. Kod ini tidak dikompil atau dijalankan di sini kerana persekitaran yang tersedia tidak mempunyai rantaian alat Swift atau masa jalan macOS.

Kendalikan kemas kini semasa melayang dan sasaran penggunaan yang lebih lama secara berasingan

Jika anda menyokong sistem tanpa beban berlebihan yang lebih baharu, kekalkan dasar model dan geometri yang sama sambil memilih API penerima yang tersedia. dropDestination(for:action:isTargeted:) yang lebih lama meneruskan item yang dinyahkod dan titik setempat; hasil Boolean-nya melaporkan kejayaan. Penutupan isTargeted menerangkan kemasukan dan keluar dari kawasan destinasi natif, yang dalam reka bentuk ini ialah seluruh kanvas. Ia tidak mengenal pasti panel logik di bawah penuding. Kontrak destinasi jatuhan lama.

Untuk pengesahan yang jelas dan cadangan operasi, onDrop(of:delegate:) memasang DropDelegate. Gunakan validateDrop(info:) untuk kelayakan awal, dropUpdated(info:) untuk cadangan semasa dan performDrop(info:) untuk pengesahan akhir serta penerapan. Memulangkan .forbidden menerangkan aktiviti yang tidak dibenarkan di lokasi itu; ia tidak mendokumenkan serahan kepada penerima natif lain. Destinasi berasaskan perwakilan dan operasi jatuhan.

Terdapat sempadan pemuatan yang penting apabila menggunakan pembekal: DropInfo.itemProviders(for:) hanya sah semasa performDrop. Mulakan pemuatan kandungan pembekal sebelum kaedah itu memulangkan nilai. Penyelesaian mungkin tiba kemudian, jadi semak semula identiti destinasi, semakan dan kelayakannya yang masih berterusan sebelum mengubah model. Laporkan pemuatan yang ditolak atau gagal dan jangan abaikan hasilnya secara senyap. Akses pembekal dan keperluan penyelesaian jatuhan.

Untuk persembahan semasa melayang, simpan maklumat sesi sementara secara berasingan daripada item yang diterima. Dokumentasi DropSession yang lebih baharu merujuk kepada onDropSessionUpdated(_:) untuk memerhatikan perubahan fasa. Jangan anggap perubahan keadaan model akan menghasilkan kemas kini penuding yang lain; jika anda sudah mempunyai lokasi melayang yang sah, kira semula sorotan logik apabila kelayakan turut berubah. Penerimaan akhir tetap perlu memanggil fungsi penghalaan sekali lagi. Kitar hayat sesi jatuhan.

Bilakah kanvas patut menggunakan AppKit?

Gunakan destinasi AppKit apabila interaksi yang diperlukan bergantung pada kemas kini natif berkala yang didokumenkan semasa penuding tidak bergerak. wantsPeriodicDraggingUpdates() AppKit mengawal tingkah laku ini: memulangkan false mengehadkan kemas kini kepada pergerakan tetikus atau perubahan pengubah suai; jika tidak, kemas kini berkala berlaku walaupun tiada apa-apa berubah. Ini menyediakan cara yang didokumenkan untuk menilai semula operasi semasa seretan pegun. Kemas kini seretan berkala.

Seni bina yang sepadan masih menggunakan satu destinasi untuk kanvas. Daftarkan jenis papan tampal yang diterima, tentukan panel logik semasa draggingEntered(_:) dan draggingUpdated(_:), semak penerimaan semasa pelepasan, kemudian terapkan item dalam performDragOperation(_:). Apple secara khusus menerangkan cara menyesuaikan operasi untuk kawasan grafik atau teks dalam satu paparan destinasi. Kitar hayat destinasi AppKit dan operasi yang bergantung pada kedudukan.

Apabila menyematkan destinasi itu dengan NSViewRepresentable, kemas kini rujukan model, konfigurasi kelayakan dan panggilan baliknya dalam updateNSView(_:context:). Penciptaan awal sahaja tidak dapat menyegerakkan perubahan pemilik yang berlaku kemudian; Apple menyatakan bahawa kaedah kemas kini ialah tempat untuk menyelaraskan paparan AppKit dengan keadaan SwiftUI. Mengemas kini paparan AppKit.

Kekalkan sempadan peristiwa sumber juga. beginDraggingSession(with:event:source:) AppKit memulakan sesi daripada item seretan dan peristiwa tekan tetikus yang memulakannya. API ini bukan untuk menghala semula destinasi. Kekalkan sumber SwiftUI sedia ada jika ia memenuhi tugas; gunakan sumber AppKit hanya apabila anda juga perlu mengawal interaksi pada sisi sumber itu. Memulakan seretan AppKit.

Sahkan perubahan keadaan pada titik yang mempengaruhi pemilikan

Lakukan pemeriksaan manual berikut pada versi penggunaan Mac yang anda sokong. Pemeriksaan ini membezakan penghalaan yang betul daripada perubahan visual yang sekadar kelihatan berfungsi:

  • Jatuhkan di kawasan bertindih dengan biru didayakan: hanya tatasusunan biru menerima item.
  • Nyahdayakan biru sebelum menyeret: kawasan bertindih dihalakan ke jingga.
  • Nyahdayakan biru semasa seretan pegun: penerimaan akhir menggunakan keadaan nyahdaya.
  • Jatuhkan di ruang biru sahaja semasa dinyahdayakan: kedua-dua tatasusunan kekal tidak berubah dan status menerangkan hasilnya.
  • Dayakan semula biru semasa seretan: kawasan bertindih dihalakan ke biru semasa komit.
  • Batalkan seretan: kedua-dua tatasusunan kekal tidak berubah.
  • Jika aplikasi anda menggantikan pemilik kanvas, gantikan pemilik itu semasa seretan dan sahkan dasar penggantian pemilik yang dipilih.
  • Jika pelaksanaan anda memuatkan pembekal secara tak segerak, ubah pemilik sebelum pemuatan selesai dan sahkan semakan semakan serta pelaporan kegagalan yang jelas.

Untuk kanvas yang menatal, mengezum atau boleh diubah saiznya, lanjutkan reka bentuk yang sama dengan menukar sempadan setiap panel dan lokasi jatuhan kepada satu ruang koordinat. Segi empat tepat tetap dalam contoh ini mengelakkan penukaran tersebut; menyalin nombornya ke dalam bentangan yang diubah akan merosakkan dasar penghalaan.

Penerima natif yang stabil dan fungsi penghalaan yang menggunakan keadaan semasa memberikan hasil yang jelas untuk interaksi ini: panel layak paling atas menerima item, panel bawah yang layak menerima sandaran kawasan bertindih, dan lokasi yang tidak diliputi membiarkan model tidak berubah. Gunakan dasar itu untuk maklum balas semasa melayang dan penerimaan akhir, dengan penerimaan akhir melakukan semakannya sendiri.

Sumber