Untuk daftar item Mac, hubungkan pilihan ke SwiftUI List dan sediakan tindakan utama melalui menu konteks yang memperhatikan pilihan. Satu klik memilih baris; klik ganda membukanya. Pisahkan status pilihan dari status presentasi.
Tutorial ini menerapkan interaksi tersebut di aplikasi macOS Anda sendiri menggunakan tiga item sintetis di memori. Tutorial ini juga menjelaskan batasan untuk permukaan SwiftUI khusus dan tampilan AppKit, sehingga Anda dapat memilih pendekatan tanpa membuat beberapa lapisan berebut menangani klik yang sama.
Gunakan tindakan utama milik kontainer yang mendukung pilihan
Apple secara eksplisit mendokumentasikan pemilihan dengan satu klik dan tindakan utama dengan klik ganda untuk kontainer yang mendukung pilihan di macOS. Titik masuk SwiftUI yang didukung adalah contextMenu(forSelectionType:menu:primaryAction:), tersedia mulai macOS 13. Pasang pada kontainer yang mendukung pilihan, seperti List atau Table. Dokumentasi Apple tentang menu konteks yang memperhatikan pilihan.
Ada tiga tanggung jawab yang terpisah: model mengidentifikasi setiap item, daftar mengelola pilihan melalui binding, dan penangan tindakan utama Anda menentukan arti membuka item. Dalam contoh ini, membuka item menampilkan sheet detail item. Memilih item hanya mengubah baris yang disorot dan label pilihan.
Pilih implementasi berdasarkan tampilan yang menangani interaksi:
| Antarmuka Anda | Titik awal yang sesuai | Tanggung jawab yang tetap Anda tangani |
|---|---|---|
| Daftar atau tabel item SwiftUI di macOS 13 atau lebih baru | Binding pilihan ditambah tindakan utama yang memperhatikan pilihan | Menyelesaikan ID tindakan dan menampilkan konten yang sesuai |
| Tile atau bentuk SwiftUI khusus | Komposisi gesture eksplisit | Tampilan pilihan, kebijakan aktivasi, dan jalur input lainnya |
| Tabel AppKit yang disematkan di SwiftUI | Pilihan NSTableView dan doubleAction |
Menjembatani pilihan dan aktivasi ke status SwiftUI |
| Permukaan gambar AppKit khusus | Penanganan peristiwa mouse miliknya sendiri | Hit testing, identitas item, dan kebijakan urutan klik |
Rute pertama secara langsung sesuai dengan tugas ini. Rute khusus berguna ketika antarmuka memang memerlukan permukaan yang berbeda; rute tersebut juga mengharuskan Anda menerapkan lebih banyak perilaku interaksi. Apple mendokumentasikan alternatif yang mendasarinya dalam komposisi gesture, tindakan klik ganda tabel AppKit, dan penanganan mouse-down.
Buat contoh lengkap dengan pilihan tunggal
Buat proyek aplikasi macOS di Xcode dengan antarmuka SwiftUI dan tetapkan target deployment ke macOS 13 atau lebih baru. Ganti deklarasi aplikasi dan tampilan konten yang dibuat otomatis dengan satu file berikut. Pastikan hanya ada satu deklarasi aplikasi @main di target.
Program ini menggunakan ID integer tetap, tiga lingkaran berwarna, dan status lokal. Program ini tidak membaca file atau membuka sumber daya eksternal. Tombol Open bergaya toolbar dan perintah kontekstual Open memanggil fungsi yang sama dengan aktivasi klik ganda.
import SwiftUI
struct SampleItem: Identifiable {
let id: Int
let name: String
let tint: Color
}
@main
struct ClickSelectionDemoApp: App {
var body: some Scene {
WindowGroup {
ItemBrowser()
}
}
}
struct ItemBrowser: View {
private let items = [
SampleItem(id: 1, name: "Circle A", tint: .blue),
SampleItem(id: 2, name: "Circle B", tint: .orange),
SampleItem(id: 3, name: "Circle C", tint: .purple)
]
@State private var selectedID: SampleItem.ID? = nil
@State private var openedItem: SampleItem? = nil
@State private var message = "Select an item."
private var selectedItem: SampleItem? {
items.first { $0.id == selectedID }
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
Text(selectedItem.map {
"Selected: \($0.name)"
} ?? "No selection")
Spacer()
Button("Open") {
if let id = selectedID {
openItems([id])
}
}
.disabled(selectedItem == nil)
}
List(items, selection: $selectedID) { item in
HStack(spacing: 10) {
Circle()
.fill(item.tint)
.frame(width: 18, height: 18)
Text(item.name)
}
.padding(.vertical, 4)
}
.contextMenu(forSelectionType: SampleItem.ID.self) { ids in
if ids.count == 1 {
Button("Open") {
openItems(ids)
}
}
} primaryAction: { ids in
openItems(ids)
}
Text(message)
.font(.caption)
}
.padding()
.frame(minWidth: 360, minHeight: 280)
.sheet(item: $openedItem) { item in
VStack(spacing: 16) {
Circle()
.fill(item.tint)
.frame(width: 80, height: 80)
Text(item.name)
.font(.title2)
Button("Close") {
openedItem = nil
}
}
.padding(32)
.frame(minWidth: 260, minHeight: 220)
}
}
private func openItems(_ ids: Set<SampleItem.ID>) {
guard ids.count == 1, let id = ids.first else {
message = "Choose exactly one item to open."
return
}
guard let item = items.first(where: { $0.id == id }) else {
message = "That item is no longer available."
return
}
openedItem = item
message = "Opened: \(item.name)"
}
}
Deklarasi dan referensi telah diperiksa, tetapi contoh ini belum dikompilasi atau dijalankan di sini: lingkungan yang tersedia tidak memiliki toolchain Swift atau GUI macOS. Build di Xcode dan lakukan pemeriksaan interaksi di bawah.
Pisahkan identitas, pilihan, dan pembukaan
SampleItem mematuhi protokol Identifiable milik Swift. id yang tetap mewakili item yang sama di antara pembaruan tampilan. Apple mendefinisikan protokol ini berdasarkan identitas yang stabil; nama tampilan adalah properti yang terpisah. Dalam aplikasi yang lebih besar, pertahankan ID item ketika nama atau tampilannya berubah. Dokumentasi Apple tentang Identifiable.
selectedID opsional membuat daftar dengan pilihan tunggal. Set ID akan mengaktifkan pilihan multipel. Dokumentasi daftar SwiftUI menjelaskan dua bentuk binding ini dan tampilan pilihan yang disediakan oleh kontainer. Dokumentasi Apple tentang List.
openedItem mengendalikan presentasi secara terpisah. Menetapkannya ke suatu item akan menampilkan sheet; menetapkannya ke nil akan menutupnya. Ini mengikuti API sheet berbasis item yang didokumentasikan. Karena memilih baris tidak pernah menetapkan openedItem, pilihan saja tidak dapat memicu sheet dalam implementasi ini. Dokumentasi Apple tentang sheet berbasis item.
Selesaikan ID yang diteruskan ke tindakan
API menu konteks menggunakan tipe pengenal kontainer, dan tindakannya menerima sekumpulan ID. Karena itu, contoh ini menggunakan SampleItem.ID.self, yang sesuai dengan pilihan daftar. Contoh ini menyelesaikan argumen callback, alih-alih menggantinya dengan nilai pilihan yang dibaca secara terpisah. Dokumentasi Apple tentang parameter menu konteks.
Penangan ini sengaja hanya menerima tepat satu ID. Set kosong menghasilkan pesan, dan ID yang tidak ditemukan menghasilkan pesan lain. Jika nanti Anda mengaktifkan pilihan multipel, tentukan kebijakan membuka item sebelum mengubah kondisi ini: tampilkan tampilan detail grup, buka jendela terpisah, atau wajibkan satu pilihan. Mengambil anggota pertama secara sembarang akan menyamarkan kebijakan yang belum ditentukan.
Mengapa dua penangan ketukan memerlukan keputusan desain berbeda
Tile khusus mungkin memerlukan gesturenya sendiri. SwiftUI onTapGesture(count:perform:) menerima jumlah klik atau ketukan yang diwajibkan. Namun, dokumentasinya tidak menyatakan bahwa memasang dua modifier terpisah pada tampilan akan menciptakan kontrak eksklusif untuk klik tunggal dan klik ganda. Dokumentasi Apple tentang tindakan ketukan.
Pertama, tentukan arti pilihan saat terjadi klik ganda. Untuk penjelajah item biasa, memilih item sebelum membukanya berguna dan konsisten dengan solusi berbasis kontainer. Persyaratan terpisah—menjalankan tindakan klik tunggal hanya ketika urutannya tidak berubah menjadi klik ganda—memerlukan arbitrase.
Untuk permukaan khusus, SwiftUI menyediakan exclusively(before:): hanya satu gesture komponen yang berhasil, dengan prioritas diberikan kepada gesture pertama. Berikut ini adalah fragmen ekspresi gesture, bukan pengganti lengkap untuk contoh daftar: Dokumentasi Apple tentang gesture eksklusif.
TapGesture(count: 2)
.exclusively(before: TapGesture(count: 1))
Ini menempatkan pengenalan klik ganda lebih dulu. Pasang gesture gabungan pada item khusus dan tangani hasilnya sesuai kebijakan pilihan dan pembukaan Anda. TapGesture mendokumentasikan pengenalan berdasarkan jumlah ketukan yang diwajibkan. Dokumentasi Apple tentang TapGesture.
Kebijakan itu memiliki konsekuensi terkait waktu: untuk memastikan urutan hanya merupakan klik tunggal, Anda harus memastikan urutan tersebut bukan klik ganda. Ini adalah konsekuensi dari persyaratan interaksi, bukan penundaan tetap SwiftUI yang didokumentasikan. Hindari menjanjikan bahwa klik tunggal akan selesai seketika sekaligus mensyaratkan agar klik tersebut tidak pernah terjadi selama rangkaian klik ganda.
Pada tile khusus, tentukan juga area yang dapat diklik. contentShape(Rectangle()) dapat membuat area persegi panjang ber-padding ikut serta dalam hit testing; ini tidak menentukan prioritas gesture. Pisahkan kedua hal tersebut saat mendiagnosis klik yang tidak terdeteksi. Dokumentasi Apple tentang bentuk konten.
Pertahankan kepemilikan peristiwa saat AppKit digunakan
Jika tabel AppKit yang sudah ada menangani baris, gunakan mekanisme aktivasi yang didukung oleh tabel tersebut. NSTableView.doubleAction mengirimkan selector ke targetnya saat klik ganda. Apple juga mendokumentasikan clickedRow dan clickedColumn untuk menentukan lokasi aktivasi, termasuk judul kolom. Pastikan lokasi tersebut mengidentifikasi item sebelum membukanya. Dokumentasi Apple tentang doubleAction.
Untuk permukaan AppKit khusus, mouseDown(with:) menerima peristiwa mouse dengan tombol kiri ditekan, dan NSEvent.clickCount melaporkan jumlah klik yang terkait dengan peristiwa mouse-down atau mouse-up. Membaca properti tersebut pada peristiwa yang tidak terkait berada di luar kontrak terdokumentasinya dan dapat menimbulkan pengecualian. Lakukan interpretasi tersebut hanya di dalam tampilan yang menangani peristiwa mouse yang sesuai. Dokumentasi Apple tentang mouse-down, dokumentasi Apple tentang jumlah klik.
Gunakan NSViewRepresentable saat menyematkan tampilan AppKit yang dikelola tersebut di SwiftUI. Buat tampilan di makeNSView, sinkronkan konfigurasi terkini di updateNSView, dan gunakan koordinator jika komunikasi target-action atau delegate memerlukan jembatan. Apple mendokumentasikan pembuatan, pembaruan, koordinasi, dan penghentian sebagai bagian dari integrasi ini. Dokumentasi Apple tentang NSViewRepresentable.
Overlay yang menerima peristiwa mouse dapat mengubah tampilan mana yang menangani klik. AppKit menggunakan hit testing untuk menentukan penerima peristiwa mouse-down, jadi overlay yang menangkap peristiwa tidak boleh dianggap sebagai pengamat tak terlihat. Untuk tugas ini, lakukan pengenalan pada kontainer yang mendukung pilihan atau permukaan khusus yang benar-benar menangani interaksi item. Dokumentasi Apple tentang hit testing.
Verifikasi pilihan dan pembukaan sebagai hasil yang terpisah
Lakukan urutan berikut setelah mem-build contoh:
- Klik
Circle Asatu kali. Barisnya harus terpilih, label pilihan harus menampilkan namanya, dan sheet tidak boleh muncul. - Klik ganda
Circle B. Pastikan sheet detail menampilkanCircle B. - Tutup sheet dan klik
Circle Csatu kali. Pastikan pilihan berubah tanpa membuka sheet lain. - Aktifkan tombol
Openyang terlihat. Pastikan tombol tersebut membuka item terpilih melalui penangan yang sama. - Jalankan perintah kontekstual
Openpada sebuah baris dan pastikan item yang dibukanya. - Klik dan klik ganda area kosong di daftar. Pastikan tindakan tersebut tidak membuka item yang tidak terkait.
- Jika Anda menambahkan kontrol tertanam, seret, atau overlay, ulangi pemeriksaan pada area tersebut.
Jika pilihan tanpa sengaja membuka detail, periksa di mana status presentasi ditetapkan. Jika aktivasi membuka item yang salah, periksa ID callback dan pencarian model. Jika klik melewatkan sebagian tile khusus, periksa tata letak dan bentuk hit testing sebelum mengubah prioritas gesture. Pemeriksaan ini mengisolasi status, penargetan tindakan, dan pengiriman peristiwa alih-alih mengubah ketiganya sekaligus.
Untuk daftar item Mac konvensional, pusatkan implementasi pada binding pilihan dan tindakan utama kontainer. Tambahkan pengenalan khusus hanya jika permukaan atau interaksinya memerlukannya, dan pastikan setiap jalur pembukaan menyelesaikan item yang dimaksud melalui satu penangan eksplisit.
Sumber
- contextMenu(forSelectionType:menu:primaryAction:) — Apple
- List — Apple
- Identifiable — Apple
- sheet(item:onDismiss:content:) — Apple
- onTapGesture(count:perform:) — Apple
- exclusively(before:) — Apple
- TapGesture — Apple
- contentShape(_:eoFill:) — Apple
- doubleAction — Apple
- mouseDown(with:) — Apple
- clickCount — Apple
- NSViewRepresentable — Apple
- hitTest(_:) — Apple