Untuk mengeklik kontrol SwiftUI melalui lubang transparan pada tampilan yang bertumpuk, berikan bentuk pengujian hit eksplisit pada tampilan atas yang mengecualikan lubang tersebut. Panduan ini membangun interaksi tersebut di dalam satu app Mac, menggunakan cincin dalam memori dan tombol di bawahnya.

Tugas konkretnya adalah antarmuka gambar desktop sederhana: mengeklik cincin oranye memilih cincin tersebut; mengeklik bagian tengahnya yang transparan mengaktifkan kontrol biru yang terlihat di bawahnya. Kedua lapisan berada dalam hierarki tampilan SwiftUI yang sama. Contoh ini menggunakan geometri yang sama untuk menggambar dan menguji hit, sehingga tidak memerlukan file gambar atau pengambilan sampel piksel.

Tentukan aturan pengujian hit sebelum mengubah penanganan peristiwa

Apple mendokumentasikan dua kontrol SwiftUI yang relevan. allowsHitTesting(_:) menentukan apakah suatu tampilan ikut serta dalam pengujian hit. contentShape(_:eoFill:) menentukan bentuk yang digunakan untuk pengujian hit. Gunakan yang pertama untuk dekorasi yang seharusnya tidak pernah menerima klik; gunakan yang kedua untuk overlay interaktif yang memiliki lubang. Dokumentasi Apple tentang keikutsertaan dalam pengujian hit dan dokumentasi bentuk konten menjelaskan tanggung jawab yang terpisah ini.

Persyaratan lapisan atas Pilihan implementasi
Dekorasi harus mengabaikan semua klik Terapkan .allowsHitTesting(false) pada dekorasi tersebut
Lingkaran harus merespons klik di dalam garis luarnya Berikan bentuk konten lingkaran pada tampilan interaktifnya
Cincin harus merespons klik di sekeliling bagian tengah yang transparan Gunakan bentuk majemuk dan pengisian genap-ganjil untuk menggambar dan menguji hit
Subpohon AppKit milik Anda memerlukan keikutsertaan selektif Ganti batas hitTest(_:)-nya dan tolak titik di luar wilayah interaksinya

API ini menjelaskan keikutsertaan dan geometri; API tersebut tidak menetapkan pengujian otomatis terhadap alfa piksel akhir yang dirender. Untuk bentuk sintetis, cara yang didukung adalah menjelaskan wilayah interaksi yang dimaksud secara eksplisit. Karena itu, tampilan transparan saja belum menjadi spesifikasi interaksi yang lengkap. Pembedaan ini didasarkan pada antarmuka Boolean dan berbasis bentuk yang terdokumentasi, bukan janji tentang setiap modifier perenderan.

Buat contoh lengkap SwiftUI cincin di atas tombol

Buat app macOS di Xcode menggunakan SwiftUI, lalu tetapkan target deployment ke macOS 12 atau lebih baru. Ganti deklarasi app dan tampilan awal yang dibuat otomatis dengan program lengkap di bawah ini, dan sisakan hanya satu deklarasi @main di target. Contoh ini menggunakan .contentShape(.interaction, …), yang jenis interaksinya tersedia mulai macOS 12 dan secara eksplisit mengendalikan pengujian hit. Dokumentasi interaksi Apple menjelaskan perilaku dan ketersediaan tersebut.

Cincin ini berisi dua subjalur elips konsentris. Penggambaran dan interaksi sama-sama menggunakan aturan genap-ganjil, sehingga elips bagian dalam menjadi lubang. Apple mendokumentasikan Shape.path(in:) sebagai geometri di dalam persegi panjang yang diberikan dan FillStyle sebagai konfigurasi perenderan, termasuk pengisian genap-ganjil. Geometri bentuk dan FillStyle menjelaskan kontrak yang mendasarinya.

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

Tombol di bawah tetap bertanggung jawab atas tindakannya sendiri. Gestur pada cincin menaikkan penghitung yang berbeda. Closure penutup tersebut menyatakan kedua tindakan secara langsung; panduan bahasa Swift mendokumentasikan closure sebagai fungsionalitas yang dapat menangkap nilai di sekitarnya. The Swift Programming Language: Closures menjelaskan sintaks dan perilaku penangkapan tersebut.

Garis tepi abu-abu hanya dekorasi, jadi pengujian hit dinonaktifkan khusus untuknya. Pertahankan modifier ini pada garis tepi: menerapkannya pada kontainer yang menampung lapisan interaktif akan memberikan kebijakan keikutsertaan yang keliru pada kontainer tersebut. Cincin itu sendiri harus tetap memenuhi syarat untuk interaksi pada pita oranyenya.

Mengapa aturan pengisian yang sama penting dua kali

Pemanggilan penggambaran dan modifier bentuk konten masing-masing menerima eoFill: true. Menetapkannya hanya untuk penggambaran akan menjelaskan lubang yang terlihat tanpa secara eksplisit memberikan aturan yang sama pada pengujian hit. Apple mendokumentasikan parameter bentuk konten secara terpisah dari perenderan, jadi kedua deklarasi diperlukan dalam contoh ini. Modifier bentuk konten menentukan bentuk interaksi dan aturan lilitannya.

Selaraskan juga dimensi dan transformasinya. Di sini, penggambaran cincin dan bentuk kontennya menggunakan persegi berukuran 240 poin yang sama. Jika nanti Anda memindahkan atau mengubah skala seluruh tampilan cincin, lakukan operasi tersebut bersama-sama. Jika Anda hanya mengubah jalur gambar di dalam kontainer yang lebih besar, perbarui geometri interaksi agar menjelaskan wilayah yang sama seperti yang dimaksudkan.

Verifikasi perutean dengan tiga klik tertentu

Aktifkan jendela app, lalu klik jauh dari batas bentuk. Titik berikut adalah koordinat lokal ilustratif di dalam persegi berukuran 240 poin, diukur dari sudut kiri atasnya. Titik-titik ini membedakan lubang tengah, pita oranye, dan sudut transparan tanpa bergantung pada piksel tepi antialias.

Titik Geometri lapisan atas yang terlihat Perubahan penghitung yang diharapkan
(120, 120) Lubang tengah Below bertambah satu kali
(210, 120) Pita oranye Ring bertambah satu kali
(10, 10) Di luar lingkaran luar cincin Below bertambah satu kali

Jari-jari luar adalah 120 poin. Inset bagian dalam adalah 72 poin di setiap sisi, sehingga jari-jari bagian dalam menjadi 48 poin. Ketiga titik pengujian masing-masing berjarak 0, 90, dan sekitar 155,6 poin dari pusat. Saya memeriksa perhitungan geometri deterministik ini dengan Python; app SwiftUI telah diperiksa, tetapi tidak dapat dikompilasi atau dijalankan di lingkungan ini karena toolchain Swift tidak tersedia.

Pengujian ini juga merupakan urutan diagnostik yang berguna. Jika pita oranye tidak merespons, periksa gestur cincin dan modifier keikutsertaannya. Jika bagian tengah memicu cincin, periksa bentuk konten dan parameter genap-ganjilnya. Jika kedua lapisan tidak merespons di lubang, hapus sementara overlay tambahan, lalu pasang kembali satu per satu untuk mengetahui lapisan mana yang mengubah perutean.

Untuk pemeriksaan geometri langsung dalam kode Anda sendiri, Path.contains(_:eoFill:) mendukung pilihan genap-ganjil yang sama. Berikan titik dalam sistem koordinat jalur yang Anda buat. Dengan begitu, pengujian geometri dapat mengajukan pertanyaan di dalam atau di luar yang sama tanpa menjadikan tangkapan layar sebagai model interaksi. Dokumentasi Apple tentang penentuan isi jalur menjelaskan operasi ini.

Gunakan pengujian hit AppKit hanya pada batas yang Anda miliki

Jika overlay sudah diimplementasikan di AppKit, NSView.hitTest(_:) adalah titik kustomisasi yang terdokumentasi. Metode ini mengembalikan turunan yang dipilih untuk suatu titik dan terutama digunakan untuk memilih penerima mouse-down. Titik masukannya berada dalam koordinat superview penerima, jadi konversikan sebelum membandingkannya dengan geometri lokal. Dokumentasi Apple tentang pengujian hit NSView menjelaskan rincian ini.

Untuk tampilan kustom yang Anda miliki, penggantian metode dapat menolak titik di luar wilayah interaksinya dan meneruskan titik yang diterima ke super.hitTest(_:). Berikut ini adalah fragmen metode untuk subclass NSView kustom yang sudah ada, bukan app lengkap kedua. Fragmen ini menggunakan RingShape yang didefinisikan di atas dan memerlukan import AppKit di samping 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 jika konten yang terlihat dan subpohon tampilan kustom tersebut memang mengikuti geometri cincin itu. Mengembalikan nil menolak subpohon tersebut untuk titik itu; tindakan ini tidak menentukan tujuan atau memerintahkan SwiftUI untuk mengaktifkan saudara tampilan tertentu. Kemampuan lapisan di bawahnya menerima klik tetap bergantung pada hierarki di sekitarnya. Tampilan pembungkus yang tetap menerima titik tersebut bisa terus menjadi penghalang.

Saat menyematkan tampilan AppKit milik Anda ke dalam SwiftUI, gunakan NSViewRepresentable untuk membuatnya dan memperbarui konfigurasinya. Selaraskan status item dan geometri interaksi melalui metode pembaruan representable; gunakan koordinator saat menjembatani pesan delegate atau target-action. Apple juga mengarahkan pengembang agar membiarkan frame dan bounds dari tampilan root yang direpresentasikan tetap dikendalikan oleh tata letak SwiftUI. Dokumentasi Apple tentang NSViewRepresentable menjelaskan batas siklus hidup dan kepemilikan ini.

Pertahankan peristiwa asli dan pemilik gestur

Lakukan keputusan perutean saat pengujian hit. Hindari menangkap klik lapisan atas lalu memanggil tindakan kontrol di bawahnya secara manual: hal itu akan mengganti interaksi normal kontrol bawah dengan perintah aplikasi. Contoh ini juga tidak memerlukan pengiriman ulang peristiwa, penelusuran tampilan implementasi SwiftUI, atau penggantian penangan gestur lapisan bawah. Setiap lapisan interaktif yang terlihat memiliki perilakunya sendiri.

Cakupan di sini adalah aktivasi tombol utama biasa di dalam hierarki tampilan app. Klik tengah yang berhasil tidak memastikan perilaku untuk menggulir, menyeret, hover, atau menu konteks; uji masing-masing secara terpisah jika antarmuka Anda menambahkannya. Secara khusus, Apple membedakan bentuk konten interaksi dari bentuk pratinjau seret dan pratinjau menu konteks, yang tidak mengubah pengujian hit. Dokumentasi Apple tentang bentuk konten menjelaskan jenis-jenis yang terpisah tersebut.

Untuk tugas desktop ini, pertahankan jalur cincin sebagai definisi bersama bagi pita yang terlihat dan wilayah yang dapat diklik. Dengan begitu, bagian tengah yang transparan memiliki arti yang tepat: lapisan atas menolak titik tersebut, dan kontrol SwiftUI di bawahnya tetap bertanggung jawab untuk merespons melalui sistem interaksi normal.

Sumber