Üst üste binen bırakma bölgeleri olan bir Mac tuvali için yerel bırakma işlemeyi kararlı tek bir üst görünüme ekleyin ve mantıksal hedefi güncel model durumuna göre belirleyin. Bırakmayı tamamlamadan önce uygunluğu yeniden kontrol edin; reddedilen bir geri çağrının başka bir görünüme devredileceği belgelenmemiştir.

Bu makalede, sentetik şekil etiketleri ve üst üste binen iki panel içeren bir uygulama için bu etkileşim oluşturuluyor. Somut amaç, üst panel öğeleri kabul etmeyi bıraktığında, buna sürükleme sırasında uygunluğunun değişmesi de dâhil olmak üzere, alt panelin bırakmayı almasını sağlamaktır. Aşağıdaki yönlendirme ilkesi, belgelenmiş geri çağrılardan hareketle tasarlanmış bir uygulama yaklaşımıdır.

Durum değişikliği, sürüklemenin başka bir hedefe geçmesini neden güvenilir biçimde sağlamaz?

Üç soruyu birbirinden ayırarak başlayın: sürüklemeyi hangi yerel görünüm alıyor, uygulamanız hangi mantıksal paneli uygun kabul ediyor ve son öğeyi hangi model alıyor? Durum değişikliği, ikinci soruya verdiğiniz yanıtı değiştirebilir; ancak ilk soruya yeni bir yanıt belirlemez.

Apple, SwiftUI’daki validateDrop(info:) yöntemini uyumlu bir bırakma hedefe girdiğinde yapılan doğrulama olarak belgeliyor. dropUpdated(info:) yöntemini ise doğrulanmış bir bırakma, değiştirilmiş görünümün içinde hareket ettiğinde yapılan güncelleme olarak belgeliyor. Bu açıklamaların hiçbiri, ilgisiz bir üst görünümün durumu değiştiğinde hedeflerin yeniden aranacağını garanti etmiyor. SwiftUI doğrulaması ve bırakma güncellemeleri.

AppKit sınırı daha açık: Her seferinde draggingUpdated(_:) iletilerinden oluşan bir diziyi yalnızca tek bir hedef alır. Üst üste binen iki görünüm geçerli hedef olduğunda, görüntü bırakılana veya dışarı sürüklenene kadar bu iletileri en üstteki görünüm alır. Bu, AppKit yönlendirmesini açıklar; SwiftUI’daki kardeş görünümler arasında genel bir öncelik sözleşmesi tanımlamaz. AppKit hedef güncellemeleri.

Eski SwiftUI bırakma eyleminden false döndürmek, bırakmanın başarısız olduğunu bildirir. Apple bu dönüş değerini, alttaki hedefin yeniden denenmesi için bir istek olarak açıklamıyor. Benzer şekilde, dropUpdated(info:) içinden nil döndürmek son geçerli öneriyi korur veya varsayılan olarak kopyalama işlemini seçer; bu, “geçiş yap” anlamına gelmez. Kapanış tabanlı bırakma işleme ve öneri davranışı.

Pratik sonuç, geri dönüş davranışını açık bir uygulama kuralı hâline getirmektir. Tek bir tuvalde, üst üste binen alt hedefler arasında yerel devre bağlı kalmadan birden fazla mantıksal panel arasından seçim yapmak için tek bir yerel alıcı kullanılabilir.

Değiştiricileri eklemeden önce sahiplik ve geri dönüş davranışını tanımlayın

Bu örnekte turuncu panel mavi panelin arkasındadır. Turuncu panel örnek etiketleri her zaman kabul eder. Mavi panel ise bunları yalnızca blueEnabled doğru olduğunda kabul eder. İstenen davranış şöyledir:

İşaretçi konumu Mavi bırakmaları kabul ediyor Mantıksal alıcı
Yalnızca turuncu alan Her iki değer Turuncu
Örtüşen alan Evet Mavi
Örtüşen alan Hayır Turuncu
Yalnızca mavi alan Hayır Hiçbiri
Her iki panelin dışı Her iki değer Hiçbiri

Bu tablo, uygulama için karar rehberidir. Özellikle mavi paneli devre dışı bırakmak, dikdörtgeninin içindeki her noktayı turuncu panel için bir hedef hâline getirmez. Turuncu panel öğeyi yalnızca işaretçi turuncu dikdörtgenin de içindeyse alır.

Yönlendirmeyi belirleyen durumu görsel panellerin üstünde tutun. Apple, bir durum değerinin ona ihtiyaç duyan en üst görünüme yerleştirilmesini ve alt görünümlerle paylaşılmasını öneriyor. Burada uygunluğu, geometrileri ve alınan öğeleri içeren bir modele tek bir üst görünüm sahip. Alt dikdörtgenler, bağımsız bırakma işleyicileri yerine görünümü sağlıyor. SwiftUI durum sahipliği.

Bırakma eylemi çalıştığında bu modeli okuyun. Giriş sırasında bir uygunluk Boolean’ı veya hedef tanımlayıcısı kaydedip daha sonra bunu yetkili kabul etmeyin. Swift kapanışları, oluşturuldukları bağlamdaki değerleri ve başvuruları saklayabilir; ayrıca saklanan bir başvuru, sahibi değiştirildikten sonra da eski modele işaret etmeyi sürdürebilir. Geri çağrının gerçekte hangi nesneyi yakaladığını denetleyin. Swift kapanış yakalama.

Sahip değişimi için ayrı bir ilke seçin: ya yeni sahibin durumuna göre çözümleyin ya da önceki sahibine ait işi reddedin. Kullanışlı bir ret tasarımında sahibe bir revizyon numarası eklenir, sahibi değiştirilirken bu numara artırılır ve gecikmeli işi tamamlamadan önce yakalanan revizyonla karşılaştırılır. Geçici bir “şu anda vurgulanıyor” değeri sahipliği belirlememelidir.

Kararlı bir SwiftUI bırakma yüzeyi oluşturun

Aşağıdaki eksiksiz örnek, macOS 26 dağıtım hedefi ve dropDestination(for:isEnabled:action:) içeren bir SDK ile macOS SwiftUI uygulaması gerektirir. Apple, bu aşırı yüklemenin Transferable türü, etkinlik bayrağı ve çözümlenmiş öğelerle bir DropSession alan eylemi kabul ettiğini belgeliyor. Hedef, değiştiricinin uygulandığı görünümün boyutunu ve konumunu kaplar. Güncel bırakma hedefi API’si.

SwiftUI kullanan yeni bir macOS App projesi oluşturun. Oluşturulan uygulama bildirimini aşağıdaki iki kod bloğuyla değiştirip ikisini tek bir Swift dosyasına art arda yerleştirin. Birlikte tek bir program oluştururlar. Yalnızca bellekte tutulan dizeleri ve dikdörtgenleri kullanır. Kaynak etiketler bırakmadan sonra da kullanılabilir durumda kalır; bu nedenle örnek, kopyalama işlemi gibi davranır.

Model ve deterministik yönlendirme

Model, seçilen görsel önceliği ifade etmek için önce maviyi kontrol eder. Hem çizim hem de yönlendirme aynı dikdörtgenleri kullanır. Tamamlama yöntemi, güncel uygunluğa göre alıcıyı yeniden hesaplar ve herhangi bir öğeyi eklemeden önce örnek grubunun tamamını doğrular.

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)."
    }
}

Uygulama ve bırakma alıcısı

Tek bırakma değiştiricisi üst dikdörtgene aittir. Renkli panellerin hiçbirinde rakip bir hedef kaydı yapılmaz. DropSession, hedefin yerel koordinat uzayındaki konumu ve uygulama içinden başlayan bir sürüklemeyi belirten bir localSession sağlar. Model ayrıca örnek değerleri de kontrol eder. Bırakma oturumu bilgileri.

@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)
    }
}

SwiftUI’daki draggable(_:), kullanıcı girişiyle sürüklemeyi başlatmak için gereken hareketleri kurar. Gecikmeli düğme yalnızca örneğin uygunluk durumunu değiştirir; sürükleme oturumunu başlatmaz, yeniden başlatmaz veya başka bir yere aktarmaya çalışmaz. SwiftUI sürükleme kaynağı davranışı.

Toggle blue in 3 seconds düğmesine basın, ardından Circle öğesini örtüşen alana sürükleyin ve mavi panel solana kadar orada tutun. Örtüşen alanın içinde bırakın: amaçlanan alıcı turuncu paneldir. Mavi devre dışıyken yalnızca maviye ait örtüşmeyen alanda bırakırsanız hiçbir öğe dizisi değişmez ve durum metni nedenini açıklar.

Bu alıcı, yönlendirme ilkesinin tamamını uygulayabilmek için tüm tuval boyunca etkin kalır. Bu nedenle örnek, konuma özgü yerel bir ret imleci veya üzerine gelme vurgusu sağlamaz. Durum metni, bırakma sonrasında uygulamanın kabulünü bildirir. Ürün böyle bir geri bildirime ihtiyaç duyuyorsa öneri ve üzerine gelme işlemeyi ayrıca ekleyin.

Kod, alınan API bildirimleriyle karşılaştırılarak incelendi. Mevcut ortamda Swift araç zinciri veya macOS çalışma zamanı bulunmadığından burada derlenmedi ve çalıştırılmadı.

Üzerine gelme güncellemelerini ve eski dağıtım hedeflerini ayrı ele alın

Daha yeni aşırı yüklemeyi içermeyen sistemleri destekliyorsanız, kullanılabilir bir alıcı API’si seçerken aynı modeli ve geometri ilkesini koruyun. Eski dropDestination(for:action:isTargeted:), çözümlenmiş öğeleri ve yerel bir noktayı iletir; Boolean sonucu başarıyı bildirir. isTargeted kapanışı, bu tasarımda tüm tuval olan yerel hedef alanına giriş ve çıkışı açıklar. İşaretçinin altındaki mantıksal paneli tanımlamaz. Eski bırakma hedefi sözleşmesi.

Açık doğrulama ve işlem önerileri için onDrop(of:delegate:) bir DropDelegate kurar. İlk uygunluk için validateDrop(info:), güncel öneriler için dropUpdated(info:), son doğrulama ve verileri modele katmak için performDrop(info:) kullanın. .forbidden döndürmek, o konumdaki etkinliğin izin verilmediğini belirtir; başka bir yerel alıcıya devir yapılacağını belgelemez. Temsilci tabanlı hedefler ve bırakma işlemleri.

Sağlayıcılar kullanıldığında yükleme açısından önemli bir sınır vardır: DropInfo.itemProviders(for:) yalnızca performDrop süresince geçerlidir. Sağlayıcı içeriğini bu yöntem dönmeden önce yüklemeye başlayın. Tamamlama daha sonra gelebilir; bu nedenle modeli değiştirmeden önce hedefin kimliğini, revizyonunu ve hâlâ uygun olup olmadığını yeniden kontrol edin. Reddedilen veya başarısız olan bir yüklemeyi sonucu sessizce yok saymak yerine bildirin. Sağlayıcı erişimi ve bırakma tamamlama gereksinimleri.

Üzerine gelme görünümü için geçici oturum bilgilerini alınan öğelerden ayrı tutun. Daha yeni DropSession belgeleri, aşama değişikliklerini gözlemlemek için onDropSessionUpdated(_:) yöntemine yönlendiriyor. Model durumundaki bir değişikliğin yeni bir işaretçi güncellemesi oluşturduğunu varsaymayın; geçerli bir üzerine gelme konumunuz varsa uygunluk değiştiğinde mantıksal vurguyu da yeniden hesaplayın. Son kabul yine yönlendirme işlevini çağırmalıdır. Bırakma oturumu yaşam döngüsü.

Tuval ne zaman AppKit kullanmalı?

İstenen etkileşim, işaretçi sabit dururken düzenli yerel güncellemelere dayanıyorsa bir AppKit hedefi kullanın. AppKit’te wantsPeriodicDraggingUpdates() bu davranışı denetler: false döndürmek, güncellemeleri fare hareketi veya değiştirici tuş değişiklikleriyle sınırlar; aksi durumda hiçbir şey değişmediğinde bile düzenli güncellemeler yapılır. Bu, sabit duran bir sürükleme sırasında geçerli işlemi yeniden değerlendirmek için belgelenmiş bir yol sağlar. Düzenli sürükleme güncellemeleri.

Buna karşılık gelen mimari yine tuval için tek bir hedeftir. Kabul edilen pano türlerini kaydedin, draggingEntered(_:) ve draggingUpdated(_:) sırasında mantıksal panelleri çözümleyin, bırakma anında kabulü kontrol edin ve öğeyi performDragOperation(_:) içinde modele katın. Apple, tek bir hedef görünümde bulunan grafik veya metin alanlarına göre işlemin özelleştirilmesini özellikle açıklıyor. AppKit hedef yaşam döngüsü ve konuma bağlı işlemler.

Bu hedefi NSViewRepresentable ile gömerken, updateNSView(_:context:) içinde model başvurusunu, uygunluk yapılandırmasını ve geri çağrıları güncelleyin. İlk oluşturma sonraki sahip değişikliklerini eşitleyemez; Apple, AppKit görünümünü SwiftUI durumuyla uyumlu hâle getirmek için güncelleme yönteminin kullanılacağını belirtiyor. AppKit görünümünü güncelleme.

Kaynak olay sınırını da koruyun. AppKit’teki beginDraggingSession(with:event:source:), sürükleme öğeleri ve kaynak fare aşağı olayıyla bir oturum başlatır. Bu, hedefi yeniden yönlendirme API’si değildir. Mevcut bir SwiftUI kaynağı görevi karşılıyorsa onu koruyun; bir AppKit kaynağını yalnızca kaynak tarafındaki bu etkileşimi de yönetmeniz gerektiğinde kullanın. AppKit sürüklemesini başlatma.

Sahipliği etkileyen noktalardaki durum değişikliklerini doğrulayın

Desteklediğiniz Mac dağıtım sürümlerinde aşağıdaki elle yapılan kontrolleri gerçekleştirin. Bu kontroller, doğru yönlendirmeyi yalnızca işe yarıyor gibi görünen görsel bir değişiklikten ayırır:

  • Mavi etkinken örtüşen alana bırakın: yalnızca mavi panelin dizisine bir öğe eklenir.
  • Sürüklemeye başlamadan önce maviyi devre dışı bırakın: örtüşen alan turuncu panele yönlendirilir.
  • İşaretçi sabit dururken sürükleme sırasında maviyi devre dışı bırakın: son kabulde devre dışı durumu kullanılır.
  • Mavi devre dışıyken yalnızca maviye ait alana bırakın: iki dizi de değişmeden kalır ve durum metni sonucu açıklar.
  • Sürükleme sırasında maviyi yeniden etkinleştirin: tamamlama sırasında örtüşen alan mavi panele yönlendirilir.
  • Sürüklemeyi iptal edin: iki dizi de değişmez.
  • Uygulamanız tuval sahiplerini değiştiriyorsa, sürükleme sırasında birini değiştirin ve seçilen sahip değiştirme ilkesini doğrulayın.
  • Uygulamanız sağlayıcıları eşzamansız yüklüyorsa, yükleme tamamlanmadan önce sahibi değiştirin ve revizyon kontrollerini ve görünür hata bildirimini doğrulayın.

Kaydırılabilir, yakınlaştırılabilir veya yeniden boyutlandırılabilir bir tuval için her panelin sınırlarını ve bırakma konumunu tek bir koordinat uzayına dönüştürerek aynı tasarımı genişletin. Bu örnekteki sabit dikdörtgenler bu dönüşümü gerektirmez; koordinatlarını dönüştürülmüş bir düzene kopyalamak yönlendirme ilkesini bozar.

Kararlı bir yerel alıcı ve güncel durumu kullanan bir yönlendirme işlevi, bu etkileşime açık bir sonuç verir: en üstteki uygun panel öğeyi alır, uygun alt panel örtüşen alanda geri dönüş alıcısı olur ve kapsanmayan bir konum modeli değiştirmez. Bu ilkeyi, son kabulün kendi kontrolünü yapmasını sağlayarak hem üzerine gelme geri bildirimi hem de son kabul için ortak kullanın.

Kaynaklar