Karartılmış şekilleri, ana hatları ve sürükleme etiketlerini sürükleme kaynağının oturum sonu geri çağrısından geri yükleyin. Hedefin çıkış geri çağrısı yalnızca işaretçinin o hedeften ayrıldığı anlamına gelir. Mac uygulamasında, kullanılabiliyorsa SwiftUI’nin kaynak oturumu API’sini kullanın ya da sürüklemeyi bir AppKit kaynağının yönetmesini sağlayın.
Bu kılavuz tek bir görevi ele alıyor: kullanıcı sürüklemeyi bitirdikten sonra sentetik bir şekli, hiçbir hedef kabul etmese bile, normal görünümüne döndürmek. Örnek içeriğini bellekte tutar ve kabul edilen bırakmayı kopyalama olarak ele alır; bu nedenle geri bildirimi geri yüklemek kaynağı silmeyi veya taşımayı gerektirmez.
Bırakma hedeflerinin dışındayken sürükleme geri bildirimi neden etkin kalıyor?
Yaygın bir uygulama, sürükleme başladığında bir isDragging bayrağını ayarlar ve performDrop içinde temizler. Böylece temizlik, yalnızca kabul eden bir hedefin işleyebileceği bir olaya bağlanır. Pencerenin boş alanına bırakıldığında kaynağı sıfırlamaktan sorumlu hiçbir hedef kalmaz.
Apple, DropDelegate türünü, bırakmaları kabul edecek şekilde değiştirilmiş bir görünüm için arayüz olarak tanımlar. dropExited(info:) yöntemi, doğrulanmış bir sürüklemenin o görünümden çıktığını bildirir. Sürükleme başka bir hedefe devam edebilir, ilk hedefe geri dönebilir ya da başka bir yerde sona erebilir. Bu nedenle hedeften çıkış, kaynak oturumunun sona erdiğini gösteremez. DropDelegate
Aynı ayrım dropDestination(for:action:isTargeted:) için de geçerlidir: isTargeted, işaretçinin hedef alanının içinde olup olmadığını belirtir. Bunu hedefin kenarlığı veya arka planı için kullanın. Kaynağın karartılmasını veya sürükleme etiketini kaynak oturumu geri çağrısının yönetmesini sağlayın. dropDestination(for:action:isTargeted:)
Bu belgelenmiş sınırlar kullanışlı bir tasarım kuralı ortaya koyar: her görsel durumu, onu güvenilir biçimde sona erdirebilecek olayın sahibine atayın.
| Geri bildirim veya sonuç | Sorumlu | Sıfırlama veya uygulama noktası |
|---|---|---|
| Karartılmış kaynak şekli | Sürükleme kaynağı | Kaynak oturumu sona erdiğinde |
| Vurgulanmış hedef | Bırakma hedefi | İşaretçi çıktığında; yerel bir sürüklemede kaynak tamamlandığında da ek güvenlik temizliği yapılır |
| Kabul edilen sentetik öğe | Hedef/model | Bırakma doğrulanıp başarılı olduğunda |
| Geçici dışa aktarma kaynakları | Aktarım yaşam döngüsü | Uygun olduğunda aktarım tamamlandığında |
Mac uygulaması hangi desteklenen yaklaşımı kullanmalı?
Apple’ın güncel başvuru belgesi, onDragSessionUpdated(_:) öğesinin macOS 26 ve sonraki sürümlerde kullanılabildiğini belirtiyor. Bu öğe, alt görünüm hiyerarşisindeki en yakın sürükleme oturumuna ilişkin güncellemeleri izler. Kapsamı açık tutmak için bunu doğrudan kaynağın sürükleme değiştiricisinin dışına ekleyin. Yöntemi gerçekten bildiren bir SDK kullanın; çalışma zamanı kullanılabilirliği, SDK’de bulunmayan bir bildirimin derlenmesini sağlamaz. onDragSessionUpdated(_:)
Belgelerde ele alınması gereken dar kapsamlı bir tutarsızlık var: değiştiricinin örneğinde konum ve işlem bağımsız değişkenleri içeren sona ermiş bir aşama gösterilirken, aşamaya ayrılmış başvuru ended(DropOperation) öğesini bildiriyor. Aşağıdaki örnek, görsel geri yükleme ilişkili değerleri gerektirmediği için .ended ile bu değerleri çıkarmadan eşleşiyor. İşleme özel davranış eklemeden önce kurulu SDK’nızdaki bildirimi inceleyin. DragSession.Phase.ended(_:)
Dağıtım hedefiniz veya SDK’niz SwiftUI geri çağrısını kullanamıyorsa, AppKit sürükleme kaynağı belgelenmiş tamamlanma sınırını sağlar. Apple, sürükleme sona erdiğinde veya iptal edildiğinde kaynağın açıkça draggingSession(_:endedAt:operation:) aldığını belirtiyor. Özel bir kaynak bu oturumu kendisi başlatmalıdır. beginDraggingSession(with:event:source:)
Yalnızca bir şekli tek bir tuval içinde yeniden konumlandırmanız gerekiyorsa, sistem sürükle-bırak oturumu yerine yerel bir DragGesture kullanmayı değerlendirin. @GestureState, hareket etkin olmadığında sıfırlanır; bu nedenle o hareketin geçici geri bildirimi için uygundur. Bu seçimi, hedeflere aktarılabilir içerik sağlayan sistem sürüklemesinden ayrı tutun. GestureState
SwiftUI’de kaynağın yönettiği temizliği uygulama
onDragSessionUpdated(_:) öğesini sunan bir SDK ile macOS SwiftUI uygulaması oluşturun, dağıtım hedefini macOS 26 veya üstüne ayarlayın ve oluşturulan uygulama ile içerik görünümü bildirimlerini bu eksiksiz programla değiştirin. Program, shape.circle adlı tek bir sentetik dize ve tek bir alıcı alan sağlar.
Kaynak, aktarılabilir içerik sağlamak için draggable(_:) kullanır. Hedef tam olarak eşleşen tek bir dizeyi kabul eder, diğer girdiler için false döndürür. Apple, hedefin Boolean sonucunun bırakmanın başarılı olup olmadığını belirttiğini belgeliyor. 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
}
}
İki bayrağın görevleri farklıdır. isDragging kaynağın opaklığını değiştirir. isTargeted alıcı alanın dolgusunu değiştirir. Bu nedenle alıcı alana giriş ve çıkış, sürükleme devam ederken kaynağı yanlışlıkla normal görünümüne döndüremez.
Aşama başvuru belgesi, initial öğesini başlamak üzere olan oturum, active öğesini hareket ve ended öğesini tamamlanma olarak tanımlar. Örnek geri bildirimi ended aşamasında geri yükler; ayrıca dataTransferCompleted aşamasında aynı sıfırlamayı güvenle yinelenebilir hâle getirir. Başarısız bir sürüklemenin geri bildirimini geri yüklemek için yalnızca aktarımın tamamlanmasını beklemez. DragSession.Phase
Apple, dataTransferCompleted öğesini sürüklenen öğelerin aktarıldığı ve geçici öğelerin temizlenebileceği an olarak tanımlar. Bu örnekte geçici dışa aktarma kaynağı yoktur. Daha büyük bir uygulamada, opaklığı geri yüklemenin aktarımın hâlâ ihtiyaç duyduğu bir öğeyi silmemesi için görsel temizliği kaynak temizliğinden ayrı tutun. DragSession.Phase.dataTransferCompleted
Daire, oturum boyunca görünüm hiyerarşisinde kalır. Kabul edilen bir bırakma hedefin sayısını artırır; kabul edilmeyen bir bırakma içeriği değiştirmez. Böylece örnek, geri bildirimi yeniden sıralama veya taşıma semantiğinden bağımsız olarak incelemek için kullanışlıdır. Kod, alınan bildirimlere göre incelendi; kullanılabilir ortam SwiftUI içermeyen Linux olduğu için derlenmedi veya çalıştırılmadı.
AppKit yedeği olay sahipliğini nasıl korumalı?
Desteklenen daha eski bir macOS sürümü için sürükleme kaynağı uygulamasını NSViewRepresentable aracılığıyla barındırılan bir NSView ile değiştirin. Temsilci yerel görünümü oluşturup günceller ve geri çağrılarını SwiftUI durumuna bağlar. SwiftUI, temsil edilen görünümün yerleşimini denetler; bu nedenle temsil edilen görünümün çerçevesini kendiniz atamak yerine SwiftUI boyutlandırma değiştiricilerini kullanın. NSViewRepresentable
Yedeği, kaynağın yönettiği tek bir sıra olarak uygulayın:
- Yerel kaynak görünümünün
mouseDown(with:)içinde, bu görünüme iletilen gerçek fare aşağı olayını saklayın. mouseDragged(with:)içinde seçtiğiniz sürükleme eşiğini uygulayın ve ikinci bir oturum başlatılmasını engelleyin.- Sentetik öğe tanımlayıcısını taşıyan, bellekteki bir pano yazıcısından
NSDraggingItemoluşturun. - Oluşturulmuş bir şekil görüntüsü sağlayın ve sürükleme çerçevesini kaynak görünümün koordinat sisteminde ayarlayın.
- Saklanan fare aşağı olayını ve
NSDraggingSourceuygulayan nesneyi kullanarakbeginDraggingSession(with:event:source:)öğesini çağırın. - Kaynak geri bildirimini
draggingSession(_:willBeginAt:)aşamasında gösterin vedraggingSession(_:endedAt:operation:)aşamasında koşulsuz olarak geri yükleyin. - İzleme tamamlandığında saklanan giriş olayını ve oturum kaydını temizleyin. Oturum tamamlanana kadar kaynağı ve geri çağrı sahibini canlı tutun.
Bu ayrıntılar AppKit’in açık sözleşmesini izler: oturum başlatma yöntemi başlatıcı fare aşağı olayını alır, sürükleme öğesi çerçeveleri görünümün koordinat sistemini kullanır ve kaynak yaşam döngüsü iletilerini alır. NSDraggingItem, NSPasteboardWriting protokolüne uyan bir nesneyi kabul eder; tek görüntü alan kolaylık yöntemi sürükleme çerçevesini ve içeriğini ayarlar. beginDraggingSession(with:event:source:), init(pasteboardWriter:), setDraggingFrame(_:contents:)
Bu uygulama içi etkileşim için NSDraggingContext.withinApplication durumunda .copy, diğer bağlamlarda ise boş bir işlem kümesi döndürün. Bu, yalnızca yerel kopyalar sunmaya yönelik açık bir tercihtir. AppKit, kaynağın hangi işlemlere izin verdiğini draggingSession(_:sourceOperationMaskFor:) aracılığıyla sorar. draggingSession(_:sourceOperationMaskFor:)
Görünümü yalnızca başarılı kopyalarda değil, her tamamlanma sonucunda geri yükleyin. Model değişikliklerini uygulamanızın gerçekten gerçekleştirdiği işleme bağlı tutun. Tamamlanma geri çağrısındaki nokta ekran koordinatlarındadır; bunu doğrudan SwiftUI hedefinin yerel sınırlarıyla karşılaştırmayın. draggingSession(_:endedAt:operation:)
Mevcut bir SwiftUI .draggable öğesinin arkasına yerleştirilen şeffaf bir yerel görünüm, yalnızca NSDraggingSource protokolüne uyarak o oturumun kaynağı hâline gelmez. Kaynak, oturum başlatıldığında iletilen nesnedir. Başlatma ve tamamlanma için tek bir sahip seçin; yerel sürükleme yüzeyini şeklin amaçlanan etkileşim alanıyla sınırlı tutun.
Fare bırakma izlemeyi veya sağlayıcının bellekten çıkarılmasını neden kullanmamalı?
Uygulama çapında fare bırakma izleyicisi, sürükleme tamamlanmasından farklı bir sınır olan giriş dağıtımını gözlemler. Apple, yerel olay izleyicilerinin iç içe izleme döngüleri tarafından tüketilen olayları almadığını belgeliyor. Bu sınırlama, izleyiciyi kaynağın belgelenmiş tamamlanma geri çağrısı yerine kullanmayı uygunsuz kılar. addLocalMonitorForEvents(matching:handler:)
Benzer şekilde, bir öğe sağlayıcısının deinit bildirimi nesnenin yaşam süresini belirtir. Swift’te bir örnek bellekten çıkarılmadan hemen önce sınıf yıkıcısı çağrılır; bu sözleşme sürüklemenin ne zaman sona erdiğini belirlemez. Sağlayıcının yok edilmesini tamamlanma sinyali olarak kullanmak, kullanıcı arayüzü durumunu sürükleme yaşam döngüsü yerine referans sahipliğine bağlar. Deinitialization
Sistem sürüklemesinin yanına ikinci bir DragGesture eklemek de aynı kavramsal sorunu yaratır: bu hareketin durumu o harekete aittir. Yerel hareket etkileşimi yönetiyorsa @GestureState kullanın; sistem sürüklemesi yönetiyorsa sürükleme kaynağının oturum geri çağrısını kullanın. GestureState
Düzeltmeyi bir tamamlanma matrisiyle doğrulama
Her yolu ayrı ayrı denetlemek için sentetik daireyi kullanın. Bunlar, bildirilen test sonuçları değil, uygulama için beklenen davranışlardır.
| Etkileşim | Beklenen kaynak geri bildirimi | Beklenen hedef sonucu |
|---|---|---|
| Daireyi alıcı alanın içine bırakın | Normale döner | Sayı bir artar |
| Pencerenin boş alanına bırakın | Normale döner | Sayı değişmez |
| Sürükleme devam ederken alıcı alandan çıkın | Karartılmış kalır | Hedef vurgusu temizlenir |
| Yeniden girip geçerli bir bırakmayla tamamlayın | Tamamlanınca normale döner | Sayı bir artar |
| Kabul eden bir hedef olmadan uygulama penceresinin dışına bırakın | Normale döner | Sayı değişmez |
| Etkin bir sürüklemeyi iptal edin | Normale döner | Sayı değişmez |
| Birkaç başarısız sürüklemeyi tekrarlayın | Her oturum normal biçimde geri yüklenir | Kabul edilen kopyalar birikmez |
Kaynak karartılmış kalıyorsa önce tamamlanma gözlemcisinin gerçek sürükleme kaynağını sardığını ve kaynağın görünür kalmayı sürdürdüğünü kontrol edin. Yalnızca hedef vurgusu takılı kalıyorsa hedefin giriş/çıkış işlemesini ve paylaşılan temizliği inceleyin. Kabul edilen sayı reddedilen bir bırakmada değişiyorsa doğrulamayı ve model değişikliğini görsel geri yüklemeden ayrı inceleyin.
Kalıcı çözüm, tamamlanma sahipliğini açıkça belirlemektir: kaynak oturum genelindeki geri bildirimi geri yükler, hedef kendi üzerine gelme durumunu ve kabul edilen içeriği yönetir, aktarım temizliği de belgelenmiş yaşam döngüsünü izler. Bu sorumluluklar ayrıldığında, her hedefin dışına bırakmak görsel durumu hedef geri çağrısını bekler hâlde bırakmaz.
Kaynaklar
onDragSessionUpdated(_:) — Apple
DragSession.Phase — Apple
DragSession.Phase.ended(_:) — Apple
draggable(_:) — Apple
DropDelegate — Apple
GestureState — Apple
NSViewRepresentable — Apple
init(pasteboardWriter:) — Apple
setDraggingFrame(_:contents:) — Apple
Deinitialization — Swift.org