Üst öğenin, alt öğe etkileşimleriyle birlikte çalışabilmesi için sürükleme hareketini simultaneousGesture(_:including:) ile ekleyin. Tıklama eylemlerini alt öğe düğmelerinde tutun, sürükleme için bir hareket eşiği belirleyin ve seçim ile sürüklemenin birlikte gerçekleşip gerçekleşemeyeceğine açıkça karar verin.
Bu adım adım anlatımda, üç örnek öğe düğmesi içeren ve taşınabilen bir panel oluşturuluyor. Okuyucuya yönelik görev açık: Bir öğeye tıklamak onu seçer; paneli sürüklemek ise aynı Mac uygulamasında panelin konumunu değiştirir. Örnek, seçim ve taşımanın birlikte gerçekleşmesine izin verir; bu uygun olduğunda etkileşim bölgelerini ayıran alternatif bir tutamak seçeneği de sunulur.
Hareket önceliğini değiştirmeden önce etkileşim kurallarını belirleyin
Apple, simultaneousGesture(_:including:) öğesini, bir hareketi mevcut hareketlerle aynı öncelik düzeyinde eklemek olarak açıklar. Apple’ın örneğinde, alt öğedeki dokunma işleyicisiyle kapsayıcıdaki dokunma işleyicisi bir tıklamaya birlikte yanıt verir. Üst öğenin, alt öğeye bilerek öncelik tanımadan etkileşime katılması gerektiğinde kullanılacak bileşim mekanizması budur. Apple’ın eşzamanlı hareketler belgeleri
Ancak eşzamanlı tanıma, “sürükleme başladığında tüm alt öğe eylemleri iptal edilmeli” kuralını tek başına ifade etmez. Apple’ın örneği, her düğme stilinin ve her kapsayıcının düğme ile sürükleme arasındaki önceliği aynı şekilde belirlediğini de göstermez. Hareketin katılımını ve uygulamanın davranışını ayrı kararlar olarak ele alın.
Bu panelde üst öğe, panelin ofsetini ve seçili öğeyi yönetir. Her alt öğe, seçim isteğini bir closure üzerinden ileten bir düğmeye sahiptir. Aynı işaretçi etkileşimi paneli hareket ettirse bile seçim isteği kabul edilir. Gereken davranış hareket sırasında etkinleştirmeyi engelliyorsa, belgelenmemiş geri çağrı sırasına bel bağlamak yerine aşağıdaki ayrı tutamak seçeneğini kullanın.
Alt öğe eylemi için Button kullanın. Apple, standart erişilebilirlik davranışı da dahil olmak üzere düğme davranışı gereken etkileşimlerde düğme kullanılmasını özellikle önerir. Özel bir şekil düğme etiketi olarak kullanılabilir; düz bir dokunma hareketine dönüşmesi gerekmez. Apple’ın Hareketler belgeleri
Alt öğe düğmeleriyle eksiksiz bir taşınabilir panel oluşturun
Xcode’da bir macOS SwiftUI uygulaması oluşturun, dağıtım hedefini macOS 14 veya üstü olarak ayarlayın ve oluşturulan uygulama ile içerik görünümünü aşağıdaki tek dosyayla değiştirin. Hedefte yalnızca bir uygulama giriş noktası bırakın. Burada kullanılan modern coordinateSpace(_:) değiştiricisi için macOS 14 gereklidir. Apple’ın koordinat uzayı belgeleri
Tüm öğeler ve durum bellekte bulunur. Örnek, düğmeleriyle birlikte panelin tamamını sabit bir çalışma alanında taşır. Öğelerin başka bir yere aktarılmasını veya uygulama penceresinin taşınmasını uygulamaz.
import SwiftUI
@main
struct GesturePanelApp: App {
var body: some Scene {
WindowGroup {
PanelDemo()
}
}
}
private struct DemoItem: Identifiable {
let id: Int
let title: String
let color: Color
}
private struct ItemTarget: View {
let item: DemoItem
let isSelected: Bool
let select: () -> Void
var body: some View {
Button(action: select) {
HStack(spacing: 10) {
Circle()
.fill(item.color)
.frame(width: 18, height: 18)
Text(item.title)
Spacer()
if isSelected {
Text("Selected")
.font(.caption)
}
}
.padding(12)
.frame(width: 220)
.background(
isSelected
? Color.accentColor.opacity(0.18)
: Color.gray.opacity(0.12)
)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}
}
private struct PanelDemo: View {
private let items = [
DemoItem(id: 1, title: "Item A", color: .blue),
DemoItem(id: 2, title: "Item B", color: .orange),
DemoItem(id: 3, title: "Item C", color: .purple)
]
@State private var selectedID: Int? = nil
@State private var committedOffset: CGSize = .zero
@GestureState private var liveTranslation: CGSize = .zero
private var pan: some Gesture {
DragGesture(
minimumDistance: 8,
coordinateSpace: .named("stage")
)
.updating($liveTranslation) { value, translation, _ in
translation = value.translation
}
.onEnded { value in
committedOffset = CGSize(
width: committedOffset.width + value.translation.width,
height: committedOffset.height + value.translation.height
)
}
}
var body: some View {
VStack(spacing: 12) {
Text("Click an item or drag the panel")
ZStack {
Color.gray.opacity(0.06)
VStack(spacing: 10) {
Text("Drag panel")
.font(.headline)
.frame(width: 244, height: 32)
.contentShape(Rectangle())
ForEach(items) { item in
ItemTarget(
item: item,
isSelected: selectedID == item.id,
select: { selectedID = item.id }
)
}
}
.padding(16)
.background(Color.gray.opacity(0.15))
.contentShape(Rectangle())
.simultaneousGesture(pan, including: .all)
.offset(
x: committedOffset.width + liveTranslation.width,
y: committedOffset.height + liveTranslation.height
)
}
.frame(width: 640, height: 420)
.coordinateSpace(.named("stage"))
Button("Reset panel") {
committedOffset = .zero
}
}
.padding(20)
}
}
Bu program, alınan API belgelerine göre incelendi; ancak bu ortamda Swift derleyicisi veya macOS kullanıcı arayüzü çalışma zamanı bulunmadığından derlenmedi ya da etkileşimli olarak test edilmedi. Programı Xcode’da derleyin ve aşağıdaki kontrolleri desteklediğiniz macOS sürümlerinde gerçekleştirin.
Sürükleme neden sıfırdan büyük bir eşik kullanıyor?
DragGesture, hareket tanınmadan önce aşılması gereken en düşük hareket mesafesini belirtmenizi sağlar. Örnek olarak verilen 8 değeri, üst öğe sürüklemeye başlamadan önce sıradan tıklamalara bir miktar hareket toleransı tanır. Bu, uygulamaya özgü bir tercihtir; Apple’ın belirlediği bir eşik ya da tıklama ile sürükleme arasında tam bir davranış politikası değildir. Sıfır mesafeli bir sürükleme, bu ayrımı ortadan kaldırır. Apple’ın DragGesture belgeleri
Geçici ve kalıcı hareket neden ayrı tutuluyor?
Hareket tanınırken updating(_:body:), geçerli ötelemeyi liveTranslation içine yazar. Apple, bu hareket durumu sona erdiğinde veya iptal edildiğinde sıfırlandığını belirtir. Örnek, son ofseti yalnızca onEnded içinde kaydeder; böylece iptal, eski bir geçici öteleme bırakmak yerine kaydedilmemiş önizlemeyi siler. Apple’ın updating belgeleri
Gösterilen konum, kaydedilmiş ofset ile geçerli ötelemenin toplamıdır. Apple, translation değerini sürüklemenin başlangıcından itibaren toplam yer değiştirme olarak tanımlar. Dolayısıyla her değişiklikte bu değeri kaydedilmiş ofsete eklemek, hareketi tekrar tekrar sayar. Örneğin, yatay yönde kaydedilmiş ofset 40 ve geçerli öteleme 12 ise ekranda gösterilen ofset 52 olur; sürükleme tamamlandığında 52 değeri bir kez kaydedilir. Apple’ın translation belgeleri
Adlandırılmış koordinat uzayı, taşınan paneli çevreleyen sabit çalışma alanına aittir. Böylece hareket hesabı açıkça belirlenmiş bir referans bölgesine göre yapılır. Alt öğe düğmeleri panelin konumunun kendi kopyalarını tutmaz.
Seçim neden üst öğede tutuluyor?
Üst öğe selectedID değerini saklar ve her alt öğeye geçerli seçili durumunu ve bir seçim closure’ını sağlar. Bu, durumu ihtiyaç duyan en üst görünümde tutup değerleri veya bağlamaları hiyerarşideki alt öğelere aktarma yönündeki Apple kılavuzuna uygundur. Apple’ın State belgeleri
Closure, geçerli öğeyi yakalar ve üst öğenin durumunu güncelleme isteğinde bulunur. Swift, closure’ların çevrelerindeki bağlamdan değerleri yakalamasını destekler; bu geri çağrıyı uygulamak için AppKit olayını kopyalamak veya iletmek gerekmez. The Swift Programming Language: Closures
Tıklama alanlarını ve hareket maskelerini anlayın
contentShape(Rectangle()), hit test için kullanılacak şekli tanımlar. Düğme etiketinde dolgu ve çerçeveden sonra geldiğinden, amaçlanan dikdörtgen etiket alanını açıkça belirtir. Üst öğede ise panelin etkileşim şeklini tanımlar. Hareket önceliğini belirlemez ve iki hareketin birlikte çalışmasını sağlamaz. Apple’ın content shape belgeleri
Hareket değiştiricisine verilen maske, hangi hareketlerin etkin kalacağını belirler. Bu seçenekleri yanlış yorumlamak kolaydır: subviews, eklenen üst öğe hareketinin devre dışı bırakıldığı anlamına gelir; alt öğelerle otomatik olarak birlikte çalıştığı anlamına gelmez. Apple’ın GestureMask belgeleri
| Maske | Belgelenen etkisi | Buradaki uygunluğu |
|---|---|---|
.all |
Eklenen hareketi ve görünüm ile alt görünümlerindeki diğer hareketleri etkinleştirir | Ortak katılım için başlangıç noktası |
.gesture |
Eklenen hareketi etkinleştirir ve alt görünüm hiyerarşisindeki hareketleri devre dışı bırakır | Alt öğelerin hareketlerini korumakla çelişir |
.subviews |
Alt görünüm hareketlerini etkinleştirir ve eklenen hareketi devre dışı bırakır | Üst öğenin sürüklemesini devre dışı bırakır |
.none |
Eklenen hareket dahil alt görünüm hiyerarşisindeki hareketleri devre dışı bırakır | Amaçlanan etkileşimi devre dışı bırakır |
highPriorityGesture öğesini, üst öğenin eksik sürükleme geri çağrılarını genel olarak düzeltmenin bir yolu olarak görmeyin. Belgelenen amacı, eklenen harekete daha yüksek öncelik vermektir. Apple’ın örneğinde üst öğe, alt öğenin dokunma hareketine öncelik kazanır. Bu öncelik amaçlanan davranışsa kullanın. Apple’ın yüksek öncelikli hareket belgeleri
Etkinleştirme alanını ayrı tutmak gerektiğinde sürükleme tutamağı kullanın
Düğmelerin hiçbir ortak sürükleme alanı olmaması gereken bir panelde aynı durumu ve hareket hesabını koruyun, ancak pan öğesini yalnızca başlığa ekleyin. Üst paneldeki .simultaneousGesture(pan, including: .all) öğesini kaldırın.
Mevcut başlık ifadesini, yukarıdaki eksiksiz programın içindeki bu parçayla değiştirin. Önceden tanımlanmış pan özelliğini kullanır; başka hiçbir ön koşul değişmez.
Text("Drag panel")
.font(.headline)
.frame(width: 244, height: 32)
.contentShape(Rectangle())
.gesture(pan)
Başlık ile öğe düğmeleri artık farklı bölgelerde bulunur ve düğmelerin bir üst öğesi olarak panel sürüklemesi eklenmez. Bir öğenin üzerinde başlamak düğme etkileşimini başlatır; paneli taşımak için başlığın üzerinde başlamak gerekir. Ortak katılımın sonucu belirsizleştireceği durumlarda bu tasarım kullanışlıdır.
Alt öğe bir eylem başlattığında yerel düğmeyi kullanmaya devam edin. Apple, Button öğesini bir eylem ve bir etiketin birleşimi olarak tanımlar ve örnekteki sentetik renkli öğelerde olduğu gibi özel etiket görünümlerine izin verir. Bunu elle oluşturulmuş bir işaretçi işleyicisiyle değiştirmek, denetimin zaten sunduğu davranışları yeniden oluşturmanızı gerektirir. Apple’ın Button belgeleri
Çerçeve sınırlarında AppKit olay sahipliğini koruyun
Panel bir AppKit görünümü içeriyorsa, hit testing sorunlarını SwiftUI hareket bileşiminden ayrı olarak inceleyin. AppKit’in hitTest(_:) yöntemi, eşleşen en derin alt öğeyi döndürür ve pencerenin fare düğmesine basma olayını hangi öğenin alacağını belirlemesine yardımcı olur. Girdi noktası, görünümün üst görünümüne göre ifade edilir. Apple’ın AppKit hit testing belgeleri
Hit test sırasında üst öğeyi döndürmek, ilk hedefi değiştirir; alt ve üst öğelerin eşzamanlı olarak tanımasını sağlamaz. Benzer şekilde, bir katmanın hit testinde nil döndürmek o katmanı değerlendirme dışı bırakır; katmanı, alt öğenin etkileşimini baştan sona pasif biçimde izleyen bir öğeye dönüştürmez.
AppKit, varsayılan mouseDown(with:) uygulamasının iletiyi sonraki yanıtlayıcıya ilettiğini belirtir. Bu, yanıtlayıcı zincirinin davranışıdır; bir denetim olayı işledikten sonra her üst öğenin olayın bir kopyasını bağımsız olarak alacağının garantisi değildir. Denetimin etkinleştirilmesini ve panelin hareketini, açık geri çağrılar veya sahip olduğunuz tanıyıcılar üzerinden birbirine bağlayın. Apple’ın mouse-down belgeleri
İki AppKit hareket tanıyıcısına sahipseniz ve eşzamanlı tanıma gerekiyorsa AppKit, gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) yöntemini sunar. Apple, true döndürmenin iki hareketin birlikte tanınmasına izin verdiğini belirtir; false döndürmek, diğer tanıyıcının temsilcisi izin veriyorsa tanıyıcıların birbirini dışlayacağını garanti etmez. Bu politikayı amaçlanan tanıyıcı çiftiyle sınırlayın. Apple’ın tanıyıcı temsilcisi belgeleri
Etkileşimi küçük bir davranış matrisiyle doğrulayın
Uygulamanızın desteklediği macOS sürümlerinde tam olarak kullanacağınız düğme stilini ve hiyerarşisini test edin. Aşağıdaki kontroller, tanıma sorunlarını hareket hesabından ayırır ve ortak yüzeyin amaçlanan kurallara uyup uymadığını netleştirir.
| Eylem | Kontrol edilecekler |
|---|---|
| Sürüklemeden her öğeye tıklayın | Seçili durumu değişir; panel sabit kalır |
| Başlığı eşiğin ötesine sürükleyin | Panel işaretçiyi izler ve son konumunu korur |
| Paylaşımlı sürümde bir öğenin üzerinden sürükleyin | Üst öğenin hareket ettiğini doğrulayın; düğmenin de etkinleşip etkinleşmediğini belirleyip bunun kabul edilebilir olduğunu onaylayın |
| Arka arkaya iki sürükleme yapın | İkinci sürükleme, her ara ötelemeyi biriktirmeden ilk sürüklemede kaydedilen konumdan başlar |
| Devam eden bir sürüklemeyi kesin | Geçici öteleme temizlenir; tamamlanmamış hareket kaydedilmez |
| Tutamak seçeneğini deneyin | Öğeler bağımsız olarak etkinleşir; hareket başlıktan başlar |
| Klavyeyle gezinerek öğe düğmelerini etkinleştirin | Seçim, işaretçiyle sürükleme gerektirmeden çalışır |
Boş panel alanı doğru sürükleniyor ancak öğelerin bulunduğu bölgeler sürüklenmiyorsa alt denetimleri ve hareket bileşimini inceleyin. Tıklamalar algılanmıyorsa hareket maskelerini, önceliği ve tıklama alanlarını inceleyin. Panel hızlanıyor veya sıçrıyorsa daha fazla tanıyıcı eklemeden önce ötelemenin biriktirilmesini ve seçilen koordinat uzayını inceleyin.
Örnekte hareket bilerek sınırlandırılmadığından panel görünür çalışma alanının dışına çıkabilir; Paneli sıfırla düğmesi konumunu geri yükler. Tamamlanmış ve sınırları belirli bir çalışma alanı için çalışma alanı ile panel boyutlarına bağlı açık bir konum kısıtı ekleyin. Aynı sahiplik yapısını koruyun: alt öğeler eylem isteğinde bulunur, üst öğe hareketi yönetir ve etkileşim kuralları tanıma alanlarının örtüşüp örtüşmeyeceğini belirler.
Kaynaklar
- simultaneousGesture(_:including:) — Apple
- Hareketler — Apple
- coordinateSpace(_:) — Apple
- DragGesture — Apple
- updating(_:body:) — Apple
- translation — Apple
- State — Apple
- Closures — Swift projesinin Swift Programlama Dili
- contentShape(_:eoFill:) — Apple
- GestureMask — Apple
- highPriorityGesture(_:including:) — Apple
- Button — Apple
- hitTest(_:) — Apple
- mouseDown(with:) — Apple
- gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) — Apple