Üst üste binen bir görünümdeki saydam bir delikten SwiftUI denetimine tıklamak için üst görünüme, deliği dışarıda bırakan açık bir isabet testi şekli verin. Bu anlatım, bellek içi bir halka ve onun altındaki bir düğmeyi kullanarak bu etkileşimi tek bir Mac uygulamasında oluşturur.
Somut görev, küçük bir masaüstü çizim arayüzüdür: turuncu halkaya tıklamak halkayı seçer; saydam merkezine tıklamak, altında görünen mavi denetimi etkinleştirir. Her iki katman da aynı SwiftUI görünüm hiyerarşisine aittir. Örnek, çizim ve isabet testi için paylaşılan geometriyi kullanır; bu nedenle görüntü dosyalarına veya piksel örneklemeye ihtiyaç duymaz.
Olay işlemeyi değiştirmeden önce isabet testi kuralını seçin
Apple, SwiftUI'de konuyla ilgili iki denetim belgeler. allowsHitTesting(_:), bir görünümün isabet testine katılıp katılmayacağını belirler. contentShape(_:eoFill:) ise isabet testi için kullanılan şekli belirtir. Tıklama almaması gereken süslemeler için ilkini; delikleri olan etkileşimli bir katman için ikincisini kullanın. Apple'ın isabet testine katılım belgeleri ve içerik şekli belgeleri bu ayrı sorumlulukları açıklar.
| Üst katmanın gereksinimi | Uygulama seçeneği |
|---|---|
| Süsleme hiçbir tıklamayı almamalı | Bu süslemeye .allowsHitTesting(false) uygulayın |
| Bir daire, dış çizgisinin içinde yanıt vermeli | Etkileşimli görünümüne dairesel bir içerik şekli verin |
| Halka, saydam bir merkez çevresinde yanıt vermeli | Çizim ve isabet testi için bileşik şekil ve çift-parite doldurma kullanın |
| Sahip olduğunuz bir AppKit alt ağacı seçici katılıma ihtiyaç duyuyor | hitTest(_:) sınırını geçersiz kılın ve etkileşim bölgesinin dışındaki noktaları reddedin |
Bu API'ler katılımı ve geometriyi tanımlar; oluşturulan son pikselin alfa kanalını otomatik olarak sınadıklarını belirtmez. Sentetik şekiller için desteklenen yöntem, amaçlanan etkileşim bölgesini açıkça tanımlamaktır. Bu nedenle yalnızca saydam bir görünüm, etkileşimi eksiksiz tanımlamaz. Bu ayrım, her oluşturma değiştiricisine ilişkin bir vaatten değil, belgelenmiş Boolean ve şekil tabanlı arayüzlerden kaynaklanır.
Halka ve düğme içeren eksiksiz bir SwiftUI örneği oluşturun
Xcode'da SwiftUI kullanarak bir macOS uygulaması oluşturun ve dağıtım hedefini macOS 12 veya sonraki bir sürüm olarak ayarlayın. Oluşturulan uygulama bildirimini ve ilk görünümü aşağıdaki eksiksiz programla değiştirin; hedefte yalnızca bir @main bildirimi bırakın. Örnek, etkileşim türü macOS 12'den itibaren kullanılabilen ve isabet testini açıkça denetleyen .contentShape(.interaction, …) kullanır. Apple'ın etkileşim belgeleri bu davranışı ve kullanılabilirliği belirtir.
Halka, iç içe iki eliptik alt yol içerir. Çizim ve etkileşim, iç elipsi deliğe dönüştüren çift-parite kuralını kullanır. Apple, Shape.path(in:)'i sağlanan dikdörtgen içindeki geometri; FillStyle'ı ise çift-parite doldurma dahil oluşturma yapılandırması olarak belgeler. Şekil geometrisi ve FillStyle temel sözleşmeleri açıklar.
import SwiftUI
struct RingShape: Shape {
func path(in rect: CGRect) -> Path {
let side = min(rect.width, rect.height)
let outer = CGRect(
x: rect.midX - side / 2,
y: rect.midY - side / 2,
width: side,
height: side
)
let inner = outer.insetBy(
dx: side * 0.30,
dy: side * 0.30
)
var path = Path()
path.addEllipse(in: outer)
path.addEllipse(in: inner)
return path
}
}
struct RoutingDemo: View {
@State private var lowerClicks = 0
@State private var ringClicks = 0
var body: some View {
VStack(spacing: 18) {
Text("Click the center or the orange ring")
ZStack {
Button {
lowerClicks += 1
} label: {
Rectangle()
.fill(Color.blue)
.frame(width: 240, height: 240)
.overlay {
Text("Below")
.foregroundColor(.white)
}
.contentShape(Rectangle())
}
.buttonStyle(.plain)
RingShape()
.fill(Color.orange, style: FillStyle(eoFill: true))
.frame(width: 240, height: 240)
.contentShape(.interaction, RingShape(), eoFill: true)
.onTapGesture {
ringClicks += 1
}
Rectangle()
.stroke(Color.gray, lineWidth: 1)
.frame(width: 240, height: 240)
.allowsHitTesting(false)
}
.frame(width: 240, height: 240)
Text("Below: \(lowerClicks) | Ring: \(ringClicks)")
}
.padding(24)
}
}
@main
struct TransparentRoutingApp: App {
var body: some Scene {
WindowGroup {
RoutingDemo()
}
}
}
Alt düğme kendi eyleminden sorumlu olmaya devam eder. Halka hareketi farklı bir sayacı artırır. Sondaki kapanışlar iki eylemi doğrudan ifade eder; Swift dil kılavuzu, kapanışları çevrelerindeki değerleri yakalayabilen işlevsellikler olarak belgeler. Swift Programlama Dili: Kapanışlar sözdizimini ve yakalama davranışını açıklar.
Gri dış çizgi süslemedir; bu nedenle isabet testi yalnızca bu çizgi için devre dışı bırakılır. Bu değiştiriciyi dış çizgide tutun: etkileşimli katmanları içeren kapsayıcıya uygulamak, kapsayıcıya yanlış katılım politikasını verir. Halkanın turuncu bandı etkileşime açık kalmalıdır.
Aynı doldurma kuralı neden iki kez gerekli?
Çizim çağrısına ve içerik şekli değiştiricisine eoFill: true verilir. Bunu yalnızca çizim için ayarlamak, görünür deliği tanımlar; ancak isabet testine aynı kuralı açıkça vermez. Apple, içerik şekli parametresini oluşturmadan ayrı olarak belgeliyor; bu nedenle bu örnekte her iki bildirim de yer almalıdır. İçerik şekli değiştiricisi, etkileşim şeklini ve sarım kuralını tanımlar.
Boyutları ve dönüşümleri de uyumlu tutun. Burada halkanın çizimi ve içerik şekli aynı 240 noktalık kareyi paylaşır. Daha sonra tüm halka görünümünü taşır veya ölçeklerseniz bu işlemleri birlikte uygulayın. Bunun yerine daha büyük bir kapsayıcı içindeki yalnızca çizim yolunu değiştirirseniz etkileşim geometrisini de aynı amaçlanan bölgeyi tanımlayacak şekilde güncelleyin.
Yönlendirmeyi üç belirli tıklamayla doğrulayın
Uygulama penceresini etkinleştirin, ardından şekil sınırlarından yeterince uzaktaki noktalara tıklayın. Aşağıdaki noktalar, sol üst köşeden ölçülen 240 noktalık karenin içindeki örnek yerel koordinatlardır. Kenar yumuşatılmış sınır piksellerine dayanmadan merkez deliğini, turuncu bandı ve saydam köşeyi birbirinden ayırırlar.
| Nokta | Üst katmanın görünen geometrisi | Beklenen sayaç değişikliği |
|---|---|---|
(120, 120) |
Merkez deliği | Below bir kez artar |
(210, 120) |
Turuncu bant | Ring bir kez artar |
(10, 10) |
Dış çemberin dışında | Below bir kez artar |
Dış yarıçap 120 noktadır. İç boşluk her kenarda 72 noktadır; böylece iç yarıçap 48 nokta olur. Üç test noktası merkezden sırasıyla 0, 90 ve yaklaşık 155,6 nokta uzaklıktadır. Bu belirlenimci geometri hesabını Python ile kontrol ettim; Swift araç zinciri bulunmadığından SwiftUI uygulaması bu ortamda incelendi, ancak derlenemedi veya çalıştırılamadı.
Bu test aynı zamanda yararlı bir tanılama dizisidir. Turuncu bant yanıt vermiyorsa halkanın hareketini ve katılım değiştiricilerini inceleyin. Merkez halkayı tetikliyorsa içerik şeklini ve çift-parite parametresini inceleyin. Delikte iki katman da yanıt vermiyorsa ek kaplamaları geçici olarak kaldırın, ardından yönlendirmeyi hangi katmanın değiştirdiğini belirlemek için bunları teker teker geri ekleyin.
Kendi kodunuzda doğrudan geometri denetimleri için Path.contains(_:eoFill:) aynı çift-parite seçeneğini destekler. Oluşturduğunuz yolun koordinat sisteminde ifade edilen bir nokta geçirin. Böylece bir geometri testi, ekran görüntüsünü etkileşim modeli olarak kullanmadan aynı içeride mi dışarıda mı sorusunu sorabilir. Apple'ın yol içindelik belgeleri bu işlemi açıklar.
AppKit isabet testini yalnızca sahibi olduğunuz bir sınırda kullanın
Bir kaplama zaten AppKit'te uygulanmışsa, NSView.hitTest(_:) belgelenmiş özelleştirme noktasıdır. Bir nokta için seçilen alt görünümü döndürür ve öncelikli olarak fareyle basma olayının alıcısını seçmek için kullanılır. Girdi noktası üst görünüm koordinatlarındadır; bu nedenle yerel geometriyle karşılaştırmadan önce dönüştürün. Apple'ın NSView isabet testi belgeleri bu ayrıntıları belirtir.
Sahibi olduğunuz özel bir görünüm için geçersiz kılma yöntemi, etkileşim bölgesinin dışındaki noktaları reddedebilir ve kabul edilen noktaları super.hitTest(_:) yöntemine bırakabilir. Aşağıdaki kod, ikinci bir eksiksiz uygulama değil, mevcut bir özel NSView alt sınıfına ait yöntem parçasıdır. Yukarıda tanımlanan RingShape'i kullanır ve import SwiftUI'nin yanında import AppKit gerektirir.
override func hitTest(_ point: NSPoint) -> NSView? {
guard let parent = superview else {
return super.hitTest(point)
}
let localPoint = convert(point, from: parent)
let region = RingShape().path(in: bounds)
guard region.contains(localPoint, eoFill: true) else {
return nil
}
return super.hitTest(point)
}
Bu parçayı yalnızca özel görünümün görünür içeriği ve alt ağacı gerçekten bu halka geometrisini izliyorsa kullanın. nil döndürmek, söz konusu noktada o alt ağacı reddeder; bir hedef belirtmez veya SwiftUI'ye belirli bir kardeş görünümü etkinleştirmesini söylemez. Amaçlanan alt katmanın tıklamayı alıp alamayacağı yine çevresindeki hiyerarşiye bağlıdır. Noktayı kabul etmeye devam eden bir kapsayıcı görünüm engel oluşturabilir.
Sahibi olduğunuz AppKit görünümünü SwiftUI'ye gömerken, görünümü oluşturmak ve yapılandırmasını güncellemek için NSViewRepresentable kullanın. Öğenin durumunu ve etkileşim geometrisini representable'ın güncelleme yöntemi aracılığıyla eşzamanlı tutun; temsilci veya hedef-eylem iletilerini köprülemek için koordinatör kullanın. Apple ayrıca geliştiricilere, temsil edilen kök görünümün frame ve bounds özelliklerini SwiftUI'nin düzen denetimine bırakmalarını söyler. Apple'ın NSViewRepresentable belgeleri bu yaşam döngüsü ve sahiplik sınırlarını açıklar.
Özgün olay ve hareket sahibini koruyun
Yönlendirme kararını isabet testi sırasında verin. Üst katmandaki tıklamayı yakalayıp alt denetimin eylemini elle çağırmaktan kaçının: bu, alt denetimin normal etkileşiminin yerine uygulama komutunu koyar. Benzer şekilde, bu örnekte olayları yeniden göndermek, SwiftUI'nin uygulama görünümlerini dolaşmak veya alt katmanın hareket işleyicisini değiştirmek gerekmez. Görünen her etkileşimli katman kendi davranışına sahiptir.
Buradaki kapsam, uygulamanın görünüm hiyerarşisi içinde birincil fare düğmesiyle yapılan olağan etkinleştirmedir. Başarılı bir merkez tıklaması; kaydırma, sürükleme, üzerine gelme veya bağlam menüleri için davranışı kanıtlamaz; arayüzünüz bunları ekliyorsa ayrıca sınayın. Özellikle Apple, etkileşim içerik şeklini sürükleme önizlemesi ve bağlam menüsü önizlemesi şekillerinden ayırır; bu şekiller isabet testini değiştirmez. Apple'ın içerik şekli belgeleri bu ayrı türleri açıklar.
Bu masaüstü görevi için halkanın yolunu, görünür bandı ve tıklanabilir bölgeyi tanımlayan ortak kaynak olarak kullanın. Böylece saydam merkezin anlamı kesinleşir: üst katman noktayı reddeder ve alt SwiftUI denetimi olağan etkileşim sistemi üzerinden yanıt vermekten sorumlu kalır.
Kaynaklar
allowsHitTesting(_:) — Apple
contentShape(_:eoFill:) — Apple
interaction — Apple
path(in:) — Apple
FillStyle — Apple
Closures — Swift.org
contentShape(::eoFill:) — Apple
contains(_:eoFill:) — Apple
hitTest(_:) — Apple
NSViewRepresentable — Apple