Untuk mengklik kawalan SwiftUI melalui lubang lutsinar dalam paparan bertindih, berikan paparan atas bentuk ujian hit yang jelas dan mengecualikan lubang itu. Panduan ini membina interaksi tersebut dalam satu app Mac, menggunakan gelang dalam memori dan butang di bawahnya.
Tugas khususnya ialah antara muka lukisan desktop yang ringkas: mengklik gelang jingga memilih gelang itu; mengklik bahagian tengahnya yang lutsinar mengaktifkan kawalan biru yang kelihatan di bawahnya. Kedua-dua lapisan berada dalam hierarki paparan SwiftUI yang sama. Contoh ini menggunakan geometri yang dikongsi oleh lukisan dan ujian hit, jadi ia tidak memerlukan fail imej atau pensampelan piksel.
Pilih peraturan ujian hit sebelum mengubah pengendalian peristiwa
Apple mendokumenkan dua kawalan SwiftUI yang berkaitan. allowsHitTesting(_:) menentukan sama ada sesuatu paparan mengambil bahagian dalam ujian hit. contentShape(_:eoFill:) menetapkan bentuk yang digunakan untuk ujian hit. Gunakan yang pertama untuk hiasan yang tidak sepatutnya menerima klik; gunakan yang kedua untuk tindanan interaktif yang mempunyai lubang. Dokumentasi Apple tentang penyertaan dalam ujian hit dan dokumentasi bentuk kandungan menerangkan tanggungjawab yang berbeza ini.
| Keperluan lapisan atas | Pilihan pelaksanaan |
|---|---|
| Hiasan harus mengabaikan semua klik | Gunakan .allowsHitTesting(false) pada hiasan itu |
| Bulatan harus bertindak balas di dalam garis luarnya | Berikan bentuk kandungan bulat kepada paparan interaktifnya |
| Gelang harus bertindak balas di sekeliling bahagian tengah yang lutsinar | Gunakan bentuk gabungan dan isian ganjil-genap untuk lukisan dan ujian hit |
| Subpepohon AppKit milik anda memerlukan penyertaan terpilih | Tindih sempadan hitTest(_:) dan tolak titik di luar kawasan interaksinya |
API ini menerangkan penyertaan dan geometri; API ini tidak menetapkan ujian automatik terhadap kelegapan piksel akhir yang dipaparkan. Bagi bentuk sintetik, cara yang disokong ialah menerangkan kawasan interaksi yang dimaksudkan secara jelas. Oleh itu, rupa yang lutsinar sahaja belum cukup untuk menentukan interaksi. Perbezaan ini berasaskan antara muka Boolean dan berasaskan bentuk yang didokumenkan, bukannya jaminan tentang setiap pengubah pemaparan.
Bina contoh SwiftUI lengkap gelang-di-atas-butang
Cipta app macOS dalam Xcode menggunakan SwiftUI, dan tetapkan sasaran penggunaan kepada macOS 12 atau lebih baharu. Gantikan pengisytiharan app dan paparan awal yang dijana dengan program lengkap di bawah, dan pastikan hanya ada satu pengisytiharan @main dalam sasaran. Contoh ini menggunakan .contentShape(.interaction, …), yang jenis interaksinya tersedia mulai macOS 12 dan mengawal ujian hit secara jelas. Dokumentasi interaksi Apple menerangkan tingkah laku dan ketersediaan tersebut.
Gelang ini mengandungi dua sublaluan elips sepusat. Lukisan dan interaksi sama-sama menggunakan peraturan ganjil-genap, menjadikan elips dalam sebagai lubang. Apple mendokumenkan Shape.path(in:) sebagai geometri dalam segi empat yang dibekalkan dan FillStyle sebagai konfigurasi pemaparan, termasuk isian ganjil-genap. Geometri bentuk dan FillStyle menerangkan kontrak asasnya.
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()
}
}
}
Butang di bawah masih mengendalikan tindakannya sendiri. Gerak isyarat gelang menaikkan pembilang yang berbeza. Penutupan hujung ini menyatakan kedua-dua tindakan secara langsung; panduan bahasa Swift mendokumenkan penutupan sebagai fungsi yang boleh menangkap nilai di sekelilingnya. The Swift Programming Language: Closures menerangkan sintaks dan tingkah laku tangkapan.
Garis luar kelabu ialah hiasan, jadi ujian hitnya dilumpuhkan secara berasingan. Kekalkan pengubah itu pada garis luar: menerapkannya pada bekas yang menempatkan lapisan interaktif akan memberikan dasar penyertaan yang salah kepada bekas tersebut. Gelang itu sendiri mesti kekal layak untuk interaksi pada jalur jingganya.
Mengapa peraturan isian yang sama penting dua kali
Panggilan lukisan dan pengubah bentuk kandungan masing-masing menerima eoFill: true. Menetapkannya hanya untuk lukisan akan menerangkan lubang yang kelihatan tanpa menetapkan secara jelas bahawa ujian hit menggunakan peraturan yang sama. Apple mendokumenkan parameter bentuk kandungan secara berasingan daripada pemaparan, jadi kedua-dua pengisytiharan diperlukan dalam contoh ini. Pengubah bentuk kandungan mentakrifkan bentuk interaksi dan peraturan lilitannya.
Pastikan dimensi dan transformasi juga sejajar. Di sini, lukisan gelang dan bentuk kandungannya berkongsi segi empat sama berukuran 240 titik. Jika anda kemudian mengalihkan atau menskalakan seluruh paparan gelang, lakukan operasi tersebut bersama-sama. Jika sebaliknya anda hanya mengubah laluan lukisan di dalam bekas yang lebih besar, kemas kini geometri interaksi supaya menerangkan kawasan yang sama seperti yang dimaksudkan.
Sahkan penghalaan dengan tiga klik khusus
Aktifkan tetingkap app, kemudian klik jauh dari sempadan bentuk. Titik berikut ialah koordinat setempat ilustrasi dalam segi empat sama berukuran 240 titik, diukur dari penjuru kiri atasnya. Titik ini membezakan lubang di tengah, jalur jingga dan penjuru lutsinar tanpa bergantung pada piksel tepi antialias.
| Titik | Geometri lapisan atas yang kelihatan | Perubahan pembilang yang dijangka |
|---|---|---|
(120, 120) |
Lubang tengah | Below bertambah sekali |
(210, 120) |
Jalur jingga | Ring bertambah sekali |
(10, 10) |
Di luar bulatan luar gelang | Below bertambah sekali |
Jejari luar ialah 120 titik. Jarak inset dalam ialah 72 titik pada setiap sisi, menjadikan jejari dalam 48 titik. Ketiga-tiga titik ujian masing-masing berada pada jarak 0, 90 dan kira-kira 155.6 titik dari pusat. Saya menyemak pengiraan geometri yang pasti ini dengan Python; app SwiftUI telah diperiksa tetapi tidak dapat dikompil atau dijalankan dalam persekitaran ini kerana rantaian alat Swift tidak tersedia.
Ujian ini juga berguna sebagai urutan diagnostik. Jika jalur jingga tidak bertindak balas, periksa gerak isyarat gelang dan pengubah penyertaannya. Jika bahagian tengah mencetuskan gelang, periksa bentuk kandungan dan parameter ganjil-genapnya. Jika kedua-dua lapisan tidak bertindak balas pada lubang, keluarkan sementara tindanan tambahan, kemudian pulihkannya satu demi satu untuk mengenal pasti lapisan yang mengubah penghalaan.
Untuk menyemak geometri secara langsung dalam kod anda sendiri, Path.contains(_:eoFill:) menyokong pilihan ganjil-genap yang sama. Berikan titik yang dinyatakan dalam sistem koordinat laluan yang anda bina. Dengan cara ini, ujian geometri boleh mengemukakan soalan yang sama tentang bahagian dalam atau luar tanpa menjadikan tangkapan skrin sebagai model interaksi. Dokumentasi Apple tentang kandungan laluan menerangkan operasi ini.
Gunakan ujian hit AppKit hanya pada sempadan yang anda miliki
Apabila tindanan sudah dilaksanakan dalam AppKit, NSView.hitTest(_:) ialah titik penyesuaian yang didokumenkan. Ia memulangkan subpaparan yang dipilih bagi sesuatu titik dan digunakan terutamanya untuk memilih penerima mouse-down. Titik inputnya berada dalam koordinat superview penerima, jadi tukarkannya sebelum membandingkannya dengan geometri setempat. Dokumentasi Apple tentang ujian hit NSView menetapkan butiran ini.
Bagi paparan tersuai milik anda, penindihan kaedah boleh menolak titik di luar kawasan interaksinya dan menyerahkan titik yang diterima kepada super.hitTest(_:). Berikut ialah fragmen kaedah untuk subkelas NSView tersuai yang sedia ada, bukannya app lengkap kedua. Fragmen ini menggunakan RingShape yang ditakrifkan di atas dan memerlukan import AppKit bersama import SwiftUI.
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)
}
Gunakan fragmen ini hanya apabila kandungan yang kelihatan dan subpepohon paparan tersuai itu benar-benar mengikut geometri gelang tersebut. Memulangkan nil menolak subpepohon itu untuk titik berkenaan; ia tidak menentukan destinasi atau mengarahkan SwiftUI untuk mengaktifkan saudara paparan tertentu. Sama ada lapisan bawah yang dimaksudkan boleh menerima klik masih bergantung pada hierarki sekeliling. Paparan pembungkus yang terus menerima titik tersebut boleh kekal sebagai penghalang.
Apabila membenamkan paparan AppKit milik anda itu dalam SwiftUI, gunakan NSViewRepresentable untuk menciptanya dan mengemas kini konfigurasinya. Selaraskan keadaan item dan geometri interaksi melalui kaedah kemas kini representable; gunakan koordinator apabila merapatkan mesej perwakilan atau target-action. Apple juga mengarahkan pembangun supaya membiarkan frame dan bounds paparan akar yang diwakili di bawah kawalan susun atur SwiftUI. Dokumentasi Apple tentang NSViewRepresentable menerangkan sempadan kitar hayat dan pemilikan ini.
Kekalkan pemilik peristiwa dan gerak isyarat asal
Buat keputusan penghalaan semasa ujian hit. Elakkan menangkap klik lapisan atas lalu memanggil tindakan kawalan bawah secara manual: tindakan itu akan menggantikan interaksi biasa kawalan bawah dengan arahan aplikasi. Begitu juga, contoh ini tidak memerlukan penghantaran semula peristiwa, penerokaan paparan pelaksanaan SwiftUI, atau penggantian pengendali gerak isyarat lapisan bawah. Setiap lapisan interaktif yang kelihatan mengendalikan tingkah lakunya sendiri.
Skop di sini ialah pengaktifan butang utama biasa dalam hierarki paparan app. Klik tengah yang berjaya tidak menetapkan tingkah laku untuk penatalan, seretan, tuding atau menu konteks; uji semuanya secara berasingan jika antara muka anda menambahkannya. Khususnya, Apple membezakan bentuk kandungan interaksi daripada bentuk pratonton seret dan pratonton menu konteks, yang tidak mengubah ujian hit. Dokumentasi Apple tentang bentuk kandungan menerangkan jenis yang berbeza itu.
Untuk tugas desktop ini, kekalkan laluan gelang sebagai takrifan bersama bagi jalur yang kelihatan dan kawasan yang boleh diklik. Ini memberikan maksud yang tepat kepada bahagian tengah yang lutsinar: lapisan atas menolak titik itu, dan kawalan SwiftUI di bawahnya kekal bertanggungjawab untuk bertindak balas melalui sistem interaksi biasa.
Sumber
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