Gunakan SwiftUI Button saat permukaan Mac kustom Anda melakukan aksi bernama. Gunakan gebrakan klik saat interaksi memerlukan informasi gebrakan, seperti lokasi kanvas. Panduan ini membangun editor bentuk kecil untuk menunjukkan tempat masing-masing.

Apple secara eksplisit merekomendasikan button setiap kali elemen berfungsi sebagai button. Button dapat menggunakan tampilan apa pun sebagai labelnya, sehingga tampilan kustom tidak memerlukan penggantian perilaku interaksinya. SwiftUI menyediakan perilaku button standar, termasuk label dan petunjuk aksesibilitas. Gestur

Interaksi apa yang dibutuhkan permukaan Anda?

Bagi pengembang yang membangun aplikasi Mac, pertanyaan yang berguna adalah informasi apa yang harus disampaikan oleh interaksi. Ubin berlabel Add circle mengekspresikan perintah. Mengklik permukaan gambar kosong untuk menempatkan lingkaran mengekspresikan perintah ditambah lokasi. Masukan tambahan itu mengubah implementasi yang tepat.

Bantu keputusan berikut menerapkan perbedaan yang didokumentasikan Apple ke editor bentuk desktop biasa. Ini adalah rekomendasi implementasi yang berasal dari API, bukan aturan tentang setiap antarmuka yang mungkin.

Interaksi yang dimaksud pembaca Titik awal Alasan
Klik ubin kustom berlabel Add circle Button dengan label kustom Permukaan memulai aksi bernama.
Berikan latar belakang dan tampilan ditekan yang khas pada ubin itu ButtonStyle Ia menyesuaikan tampilan sambil mempertahankan interaksi button standar.
Klik kanvas untuk menempatkan lingkaran di titik itu SpatialTapGesture Hasilnya menyertakan lokasi interaksi.
Kenali dua klik pada tampilan konten tanpa memerlukan koordinat onTapGesture(count: 2) Modifikator menerima jumlah klik yang diperlukan.
Konfigurasi tombol mouse spesifik dalam interaksi AppKit NSClickGestureRecognizer Ia mengekspos konfigurasi tombol mouse dan jumlah klik.

Apple mendokumentasikan button sebagai aksi yang dipasangkan dengan label, dan membedakan penyesuaian tampilan melalui ButtonStyle dari penyesuaian interaksi melalui PrimitiveButtonStyle. API gebrakannya secara terpisah mendokumentasikan jumlah klik dan hasil spasial. Button, onTapGesture(count:perform:), SpatialTapGesture, NSClickGestureRecognizer

Oleh karena itu, ubin aksi kustom dimulai sebagai button. Gunakan gebrakan saat Anda dapat mengidentifikasi kebutuhan masukan atau pengenalan tambahan yang diperlukan permukaan.

Bagaimana membangun button kustom tanpa membangun ulang perilakunya?

Letakkan perintah di closure aksi button dan karya seni di labelnya. Gunakan ButtonStyle untuk presentasi. Apple mendokumentasikan bahwa protokol ini mempertahankan perilaku interaksi standar untuk platform; PrimitiveButtonStyle adalah titik ekstensi terpisah untuk mengubah perilaku interaksi secara sengaja. ButtonStyle

Contoh di bawah berisi dua permukaan saudara: button Add circle kustom dan kanvas penempatan persegi panjang. Keduanya memanggil fungsi mutasi yang sama. Button memilih slot yang dapat diprediksi; kanvas menyediakan titik lokal. Hal ini membuat perbedaan terlihat tanpa memperkenalkan file, konten eksternal, atau aplikasi lain.

Buat proyek macOS App menggunakan SwiftUI dan tetapkan target deployment ke macOS 13 atau lebih baru, minimum tercatat untuk SpatialTapGesture. Ganti definisi app dan content-view yang dihasilkan dengan file pertama, dan tambahkan file kedua ke target yang sama. Pertahankan tepat satu deklarasi app @main. Kode telah diperiksa tetapi belum dikompilasi atau dijalankan di lingkungan macOS SDK. SpatialTapGesture

Aplikasi lengkap terdiri dari dua file ini. Pertama, ShapeSurfaceApp.swift:

import SwiftUI
import Foundation

@main
struct ShapeSurfaceApp: App {
    var body: some Scene {
        WindowGroup {
            ShapeSurfaceView()
        }
    }
}

private struct PlacedCircle: Identifiable {
    let id = UUID()
    let center: CGPoint
}

struct ShapeSurfaceView: View {
    @State private var circles: [PlacedCircle] = []
    private let limit = 6

    private var isFull: Bool {
        circles.count >= limit
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Button {
                let x = 35 + CGFloat(circles.count) * 50
                addCircle(at: CGPoint(x: x, y: 100))
            } label: {
                HStack(spacing: 12) {
                    Circle()
                        .fill(Color.blue)
                        .frame(width: 24, height: 24)
                    Text("Add circle")
                }
                .frame(width: 280, height: 64)
                .contentShape(RoundedRectangle(cornerRadius: 12))
            }
            .buttonStyle(SurfaceButtonStyle())
            .keyboardShortcut("a", modifiers: [.command, .shift])
            .disabled(isFull)
            .opacity(isFull ? 0.4 : 1)

            Text("Click the canvas to place a circle. Maximum: 6.")

            ZStack(alignment: .topLeading) {
                Color.gray.opacity(0.1)

                ForEach(circles) { circle in
                    Circle()
                        .fill(Color.blue)
                        .frame(width: 24, height: 24)
                        .position(circle.center)
                }
            }
            .frame(width: 320, height: 200)
            .contentShape(Rectangle())
            .gesture(
                SpatialTapGesture()
                    .onEnded { value in
                        addCircle(at: value.location)
                    }
            )
            .disabled(isFull)
            .border(Color.gray)
            .accessibilityElement(children: .ignore)
            .accessibilityLabel("Circle canvas")
            .accessibilityValue("\(circles.count) of \(limit) circles")

            Text(isFull
                 ? "Six circles placed. Clear to start again."
                 : "\(circles.count) of \(limit) circles placed")

            Button("Clear circles") {
                circles.removeAll()
            }
            .disabled(circles.isEmpty)
        }
        .padding(24)
    }

    private func addCircle(at point: CGPoint) {
        guard !isFull else { return }

        // Keep each 24-point circle inside the 320-by-200 canvas.
        let center = CGPoint(
            x: min(max(point.x, 12), 308),
            y: min(max(point.y, 12), 188)
        )
        circles.append(PlacedCircle(center: center))
    }
}

Kedua, SurfaceButtonStyle.swift:

import SwiftUI

struct SurfaceButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .background(
                RoundedRectangle(cornerRadius: 12)
                    .fill(Color.blue.opacity(
                        configuration.isPressed ? 0.25 : 0.1
                    ))
            )
    }
}

Style membaca configuration.isPressed untuk menggambar umpan balik; ia tidak pernah memanggil aksi itu sendiri. Apple mendefinisikan properti itu sebagai status ditekan button saat ini. Ini menjaga rendering dan eksekusi aksi di tempat terpisah. isPressed

Batas enam lingkaran adalah aturan aplikasi ilustratif. Kedua titik masuk menggunakan addCircle(at:), yang memeriksa kapasitas sebelum menambahkan. Tampilan dinonaktifkan mengomunikasikan batas, sementara penjaga bersama menjaga aturan mutasi tetap konsisten. Apple mendokumentasikan disabled(_:) sebagai mengontrol interaksi dengan tampilan, termasuk klik dan gebrakan. disabled(_:)

Mengapa kanvas menggunakan gebrakan klik spasial?

Kanvas memerlukan titik tempat pengguna mengklik. Overload onTapGesture(count:perform:) dasar menyediakan aksi tanpa parameter lokasi. SpatialTapGesture secara eksplisit melaporkan lokasi, dan inisialisasi default-nya ke satu interaksi di ruang koordinat lokal. onTapGesture(count:perform:), SpatialTapGesture, init(count:coordinateSpace:)

Dalam program ini, gebrakan milik kanvas berukuran tetap yang koordinat lokalnya memposisikan lingkaran. Fungsi penempatan memaksa setiap pusat agar menyisakan margin 12 poin, sesuai jari-jari lingkaran. Mengklik dekat tepi oleh karena itu menempatkan lingkaran lengkap di dalam kanvas. Lingkaran tumpang tindih diizinkan, dan jumlah tetap terlihat bahkan saat bentuk tumpang tindih.

Button dan kanvas juga memiliki perilaku keyboard yang sengaja berbeda. Pintasan button melakukan aksi yang sama dengan mengaktifkan button itu, menambahkan lingkaran di slot berikutnya. Ia tidak memproduksi lokasi pointer. Apple mendokumentasikan pintasan keyboard sebagai memanggil aksi primer kontrol dan mencatat bahwa penugasan pintasan duplikat diselesaikan ke kontrol pertama yang cocok. Tetapkan pintasan ini sekali di jendela contoh. keyboardShortcut(_:modifiers:)

Label aksesibilitas dan jumlah kanvas mendeskripsikan isinya; mereka tidak menyediakan alur kerja keyboard untuk penempatan koordinat sewenang-wenang. Button menawarkan jalur non-pointer untuk menambahkan lingkaran di slot yang ditentukan sebelumnya. Jika penempatan tepat juga harus tersedia melalui kontrol keyboard, tambahkan antarmuka pengeditan posisi eksplisit daripada mengasumsikan gebrakan atau label deskriptif menyediakannya.

Bagaimana pengujian tabrakan harus cocok dengan permukaan kustom?

Tentukan area interaksi yang dimaksud setelah menetapkan ukuran label. Dalam contoh, label button memiliki frame 280x64 diikuti oleh contentShape persegi panjang bulat. Kanvas menetapkan frame 320x200-nya dan kemudian mendeklarasikan bentuk konten persegi panjang. Apple mendefinisikan contentShape(_:eoFill:) sebagai bentuk yang digunakan untuk pengujian tabrakan. contentShape(_:eoFill:)

Ini memberikan batas interaksi eksplisit pada permukaan terlihat. Jika Anda memindahkan frame ke luar label button selama redesign, periksa hasilnya lagi: ukuran dan bentuk konten contoh berada bersama di dalam label. Jaga gambar, tata letak, dan geometri interaksi agar sengaja, bukan memperbaiki margin yang tidak responsif dengan menambahkan handler aksi lain.

Saat permukaan berhenti merespons, periksa kondisi ini secara berurutan:

  1. Periksa geometri. Klik teks, grafis, dan interior kosong label. Konfirmasi bentuk konten mendeskripsikan batas yang dimaksud.
  2. Periksa nenek moyang dinonaktifkan. Kontainer dinonaktifkan mengesampingkan turunan yang diaktifkan, sehingga .disabled(false) lokal mungkin tidak memulihkan interaksi. disabled(_:)
  3. Periksa overlay dekoratif. Tampilan dapat dikecualikan dari pengujian tabrakan dengan .allowsHitTesting(false) ketika ia murni visual. Terapkan itu pada dekorasi, bukan permukaan interaktif. allowsHitTesting(_:)
  4. Periksa handler bersaing. Identifikasi tampilan mana yang memiliki aksi sebelum mengubah presedensi gebrakan.

Bagaimana mencegah handler bersaing dan kesalahan kepemilikan?

Gunakan satu handler untuk setiap jalur aktivasi yang dimaksud. Dalam contoh, button kustom memiliki perintahnya dan kanvas memiliki penempatan berbasis lokasi. Keduanya tidak berada di dalam induk dengan gebrakan penempatan lain. Struktur ini membuat efek yang dimaksud dari satu klik mudah diperiksa.

Hindari menempelkan handler tap kedua ke button hanya untuk mencapai fungsi mutasi yang sama. Demikian pula, jangan menambahkan gebrakan induk di sekitar beberapa kontrol tanpa memutuskan apakah interaksi induk dan anak keduanya harus terjadi. Contoh simultaneousGesture Apple menunjukkan bahwa handler induk dan anak keduanya dapat berjalan. highPriorityGesture sebaliknya memberikan presedensi lebih tinggi pada gebrakan yang dilampirkan. Modifikator ini mengekspresikan kebijakan interaksi; mereka bukan perbaikan yang dapat dipertukarkan. simultaneousGesture(_:including:), highPriorityGesture(_:including:)

Simpan status aplikasi dengan tampilan yang memilikinya. Di sini, ShapeSurfaceView menyimpan lingkaran sintetis, dan style hanya merender label yang disediakan. Apple merekomendasikan status privat di tampilan tertinggi yang memerlukan akses, dengan nilai atau binding diteruskan ke turunan sesuai kebutuhan. State

Jika adapter AppKit nanti menyimpan callback, periksa hubungan tangkapannya secara terpisah. Closure Swift dapat menangkap nilai sekitar; kelas yang menyimpan closure yang menangkap instance yang sama dapat membuat siklus referensi kuat. Pilih daftar tangkapan sesuai dengan masa hidup objek sebenarnya. Mengubah button menjadi gebrakan tidak menyelesaikan kepemilikan callback. Closures

Kapan AppKit menjadi langkah selanjutnya yang tepat?

Pertimbangkan AppKit ketika permukaan Anda sendiri memerlukan konfigurasi khusus mouse yang terdokumentasi. NSClickGestureRecognizer mendukung jumlah klik yang diperlukan dan masker tombol mouse. Apple menentukan bahwa bit nol mewakili tombol primer dan bit satu tombol sekunder; pengenalan memerlukan klik yang dikonfigurasi tanpa menyeret. Ia juga secara otomatis mengaktifkan penundaan acara untuk tombol mouse yang dikonfigurasi. NSClickGestureRecognizer, buttonMask

Pilih jalur integrasi sesuai target deployment dan komponen yang Anda miliki:

  • Di macOS 26 atau lebih baru, NSGestureRecognizerRepresentable mengintegrasikan pengenal AppKit langsung ke hierarki SwiftUI melalui .gesture(...). SwiftUI memasang target aksi; implementasikan handler aksi representable dan gunakan konverter konteksnya untuk konversi koordinat. Apple memperingatkan bahwa tampilan terkait pengenal mungkin memiliki geometri berbeda dari tampilan SwiftUI. NSGestureRecognizerRepresentable
  • Untuk tampilan AppKit yang di-host di SwiftUI, NSViewRepresentable mengelola pembuatan, pembaruan, dan penghancuran tampilan Anda. Gunakan koordinatornya untuk callback, dan biarkan SwiftUI mengontrol tata letak tampilan yang diwakili. API ini tersedia sejak macOS 10.15. NSViewRepresentable

Jaga batas acara saat menambahkan adapter mana pun. AppKit mendokumentasikan bahwa pengenal beroperasi pada tampilan terlampir dan turunannya, menerima acara relevan sebelum tampilan, dan mungkin menunda pengiriman mereka. Ia juga secara eksplisit tidak mendukung penyerahan pelacakan acara ke mekanisme lain seperti drag and drop atau menu pop-up. Desain interaksi tersebut melalui mekanisme yang didukung daripada memperlakukan pengenal klik sebagai relai acara universal. NSGestureRecognizer

Apa yang harus diverifikasi dalam interaksi Mac yang jadi?

Bangun contoh di Xcode dan periksa perilaku yang dapat diamati sebelum mengadaptasinya ke hierarki tampilan yang lebih besar:

  • Klik teks dan interior kosong button kustom. Setiap aktivasi seharusnya menambah satu lingkaran dan memperbarui jumlah sekali.
  • Tekan Command–Shift–A di jendela aktif. Seharusnya melakukan aksi berbasis slot button.
  • Klik posisi kanvas yang berbeda, termasuk dekat tepinya. Lingkaran harus mengikuti koordinat lokal dengan margin yang terdokumentasikan dalam kode.
  • Capai enam lingkaran. Kedua jalur penempatan harus berhenti menambah, sementara Clear circles tetap tersedia.
  • Bersihkan kanvas. Penempatan harus menjadi tersedia lagi.
  • Periksa button dengan VoiceOver dan uji navigasi keyboard di bawah pengaturan Mac Anda saat ini. Verifikasi bahwa aksinya tetap dapat dipahami setelah styling.

Untuk permukaan aksi Mac kustom, mulailah dengan makna interaksi: perintah bernama ada di Button, penyesuaian visual ada di label dan style-nya, dan penempatan bergantung lokasi ada di gebrakan kanvas. Jaga jalur-jalur itu terhubung melalui satu fungsi pengubah status, lalu kenalkan AppKit hanya ketika konfigurasinya tambahan menjawab kebutuhan konkret.

Sumber