Usa un Button di SwiftUI quando la tua superficie personalizzata per Mac esegue un’azione con un nome preciso. Usa un gesto di clic quando l’interazione richiede informazioni sul gesto, come la posizione sul canvas. Questa guida costruisce un piccolo editor di forme per mostrare a cosa serve ciascuna opzione.

Apple raccomanda esplicitamente di usare un pulsante ogni volta che un elemento svolge quella funzione. Un pulsante può usare qualsiasi vista come etichetta, quindi un aspetto personalizzato non richiede di sostituirne il comportamento d’interazione. SwiftUI fornisce comportamenti standard per i pulsanti, incluse le etichette e i suggerimenti di accessibilità. Gesti

Di quale interazione ha bisogno la tua superficie?

Per chi sviluppa un’app per Mac, la domanda utile è quali informazioni deve fornire l’interazione. Un riquadro con l’etichetta Aggiungi cerchio esprime un comando. Fare clic su una superficie di disegno vuota per posizionare un cerchio esprime un comando più una posizione. Questo input aggiuntivo cambia l’implementazione appropriata.

Il seguente strumento decisionale applica le distinzioni documentate da Apple a un normale editor di forme per desktop. Si tratta di consigli d’implementazione basati sulle API, non di regole valide per ogni possibile interfaccia.

Interazione prevista dall’utente Punto di partenza Motivo
Fare clic su un riquadro personalizzato con l’etichetta Aggiungi cerchio Button con etichetta personalizzata La superficie avvia un’azione con un nome preciso.
Dare al riquadro uno sfondo distintivo e un aspetto specifico quando viene premuto ButtonStyle Personalizza l’aspetto mantenendo il comportamento d’interazione standard del pulsante.
Fare clic sul canvas per posizionare un cerchio in quel punto SpatialTapGesture Il risultato include la posizione dell’interazione.
Rilevare due clic su una vista di contenuto senza aver bisogno delle coordinate onTapGesture(count: 2) Il modificatore accetta un numero di clic richiesto.
Configurare pulsanti specifici del mouse in un’interazione AppKit NSClickGestureRecognizer Espone la configurazione del pulsante del mouse e del numero di clic.

Apple documenta il pulsante come un’azione abbinata a un’etichetta e distingue la personalizzazione dell’aspetto tramite ButtonStyle dalla personalizzazione del comportamento d’interazione tramite PrimitiveButtonStyle. Le API per i gesti documentano separatamente il numero di clic e i risultati con posizione. Button, onTapGesture(count:perform:), SpatialTapGesture, NSClickGestureRecognizer

Un riquadro per un’azione personalizzata parte quindi da un pulsante. Scegli un gesto quando puoi identificare l’input aggiuntivo o il requisito di riconoscimento di cui la superficie ha bisogno.

Come si crea un pulsante personalizzato senza ricostruirne il comportamento?

Inserisci il comando nella closure d’azione del pulsante e l’immagine nella sua etichetta. Usa ButtonStyle per la presentazione. Apple documenta che questo protocollo mantiene il comportamento d’interazione standard della piattaforma; PrimitiveButtonStyle è il punto d’estensione distinto per modificare intenzionalmente il comportamento d’interazione. ButtonStyle

L’esempio seguente contiene due superfici affiancate: un pulsante personalizzato Aggiungi cerchio e un canvas rettangolare per il posizionamento. Entrambi chiamano la stessa funzione di modifica. Il pulsante sceglie uno slot prevedibile; il canvas fornisce un punto locale. Questo rende visibile la differenza senza introdurre file, contenuti esterni o un’altra app.

Crea un progetto App macOS usando SwiftUI e imposta la destinazione di distribuzione su macOS 13 o successivo, il requisito minimo documentato per SpatialTapGesture. Sostituisci le definizioni generate dell’app e della vista di contenuto con il primo file e aggiungi il secondo file allo stesso target. Mantieni una sola dichiarazione @main. Il codice è stato esaminato, ma non è stato compilato né eseguito in un ambiente con SDK macOS. SpatialTapGesture

L’applicazione completa è composta da questi due file. Per prima cosa, 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))
    }
}

Secondo, 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
                    ))
            )
    }
}

Lo stile legge configuration.isPressed per disegnare il feedback; non chiama mai direttamente l’azione. Apple definisce questa proprietà come lo stato attuale di pressione del pulsante. In questo modo il rendering e l’esecuzione dell’azione restano separati. isPressed

Il limite di sei cerchi è una regola applicativa a scopo illustrativo. Entrambi i punti d’accesso usano addCircle(at:), che verifica la capacità prima di aggiungere un elemento. L’aspetto disabilitato comunica il limite, mentre la guardia condivisa mantiene coerente la regola di modifica. Apple documenta disabled(_:) come un controllo dell’interazione con una vista, inclusi clic e gesti. disabled(_:)

Perché il canvas usa un gesto di clic spaziale?

Il canvas ha bisogno del punto in cui l’utente ha fatto clic. L’overload di base onTapGesture(count:perform:) fornisce un’azione senza un parametro per la posizione. SpatialTapGesture riporta esplicitamente una posizione e il suo inizializzatore usa come valore predefinito un’interazione nello spazio di coordinate locali. onTapGesture(count:perform:), SpatialTapGesture, init(count:coordinateSpace:)

In questo programma, il gesto è associato al canvas di dimensioni fisse, le cui coordinate locali determinano la posizione dei cerchi. La funzione di posizionamento limita il centro per lasciare un margine di 12 punti, pari al raggio del cerchio. Fare clic vicino a un bordo posiziona quindi il cerchio interamente all’interno del canvas. I cerchi possono sovrapporsi e il conteggio resta visibile anche quando le forme si sovrappongono.

Anche il comportamento da tastiera del pulsante e del canvas è volutamente diverso. La scorciatoia del pulsante esegue la stessa azione dell’attivazione del pulsante, aggiungendo un cerchio nello slot successivo. Non genera una posizione del puntatore. Apple documenta le scorciatoie da tastiera come invocazioni dell’azione principale di un controllo e segnala che, se a più controlli viene assegnata la stessa scorciatoia, viene scelto il primo controllo corrispondente trovato. Assegna questa scorciatoia una sola volta nella finestra dell’esempio. keyboardShortcut(_:modifiers:)

L’etichetta di accessibilità e il conteggio del canvas ne descrivono il contenuto, ma non forniscono un flusso di lavoro da tastiera per posizionare le forme in coordinate arbitrarie. Il pulsante offre un modo senza puntatore per aggiungere un cerchio in uno slot predefinito. Se è necessario poter eseguire posizionamenti precisi anche tramite i controlli da tastiera, aggiungi un’interfaccia esplicita per modificare la posizione, invece di presumere che un gesto o un’etichetta descrittiva la forniscano.

Come si dovrebbe far corrispondere l’hit testing alla superficie personalizzata?

Definisci l’area d’interazione prevista dopo aver stabilito le dimensioni dell’etichetta. Nell’esempio, l’etichetta del pulsante ha un frame di 280 per 64, seguito da una contentShape rettangolare arrotondata. Il canvas imposta il proprio frame di 320 per 200 e poi dichiara una forma di contenuto rettangolare. Apple definisce contentShape(_:eoFill:) come la forma usata per l’hit testing. contentShape(_:eoFill:)

In questo modo le superfici visibili hanno limiti d’interazione espliciti. Se durante una riprogettazione sposti il frame fuori dall’etichetta del pulsante, ricontrolla il risultato: nell’esempio, dimensionamento e forma di contenuto vanno tenuti insieme all’interno dell’etichetta. Mantieni intenzionali il disegno, il layout e la geometria d’interazione, anziché correggere un margine che non risponde aggiungendo un altro gestore di azioni.

Quando una superficie smette di rispondere, controlla queste condizioni nell’ordine:

  1. Controlla la geometria. Fai clic sul testo dell’etichetta, sulla grafica e sul suo interno vuoto. Verifica che la forma di contenuto descriva i limiti previsti.
  2. Controlla gli antenati disabilitati. Un contenitore disabilitato prevale su un discendente abilitato, quindi un .disabled(false) locale potrebbe non ripristinare l’interazione. disabled(_:)
  3. Controlla le sovrapposizioni decorative. Se una vista ha solo una funzione visiva, può essere esclusa dall’hit testing con .allowsHitTesting(false). Applica il modificatore alla decorazione, non alla superficie interattiva. allowsHitTesting(_:)
  4. Controlla i gestori in conflitto. Individua quale vista possiede l’azione prima di modificare la precedenza dei gesti.

Come si evitano gestori in conflitto ed errori di proprietà?

Usa un solo gestore per ogni percorso di attivazione previsto. Nell’esempio, il pulsante personalizzato possiede il suo comando e il canvas gestisce il posizionamento basato sulla posizione. Nessuno dei due si trova all’interno di un genitore con un altro gesto di posizionamento. Questa struttura rende facile esaminare l’effetto previsto di un singolo clic.

Evita di aggiungere un secondo gestore di tocco al pulsante solo per richiamare la stessa funzione di modifica. Analogamente, non aggiungere un gesto sul genitore che avvolge più controlli senza decidere se le interazioni del genitore e del figlio debbano avvenire entrambe. L’esempio di Apple con simultaneousGesture dimostra che i gestori del genitore e del figlio possono essere eseguiti entrambi. highPriorityGesture assegna invece una precedenza maggiore al gesto associato. Questi modificatori esprimono una politica d’interazione; non sono soluzioni intercambiabili. simultaneousGesture(_:including:), highPriorityGesture(_:including:)

Mantieni lo stato dell’app con la vista che ne è proprietaria. Qui ShapeSurfaceView memorizza i cerchi sintetici e lo stile si limita a renderizzare l’etichetta fornita. Apple raccomanda di mantenere lo stato privato nella vista più in alto che ne ha bisogno, passando valori o binding alle viste discendenti secondo necessità. State

Se in seguito un adattatore AppKit memorizza una callback, esamina separatamente le sue relazioni di cattura. Le closure Swift possono catturare valori circostanti; una classe che memorizza una closure che cattura la stessa istanza può creare un ciclo di riferimenti forti. Scegli una lista di cattura in base alla durata effettiva degli oggetti. Trasformare un pulsante in un gesto non risolve la proprietà della callback. Closures

Quando AppKit è il passo successivo appropriato?

Valuta AppKit quando la tua superficie richiede la configurazione specifica per il mouse che documenta. NSClickGestureRecognizer supporta un numero di clic richiesto e una maschera per i pulsanti del mouse. Apple specifica che il bit zero rappresenta il pulsante primario e il bit uno quello secondario; il riconoscimento richiede i clic configurati senza trascinamento. Inoltre abilita automaticamente il ritardo degli eventi per i pulsanti del mouse configurati. NSClickGestureRecognizer, buttonMask

Scegli il percorso d’integrazione in base alla destinazione di distribuzione e al componente di cui sei responsabile:

  • Su macOS 26 o successivo, NSGestureRecognizerRepresentable integra direttamente un riconoscitore AppKit in una gerarchia SwiftUI tramite .gesture(...). SwiftUI installa il target dell’azione; implementa il gestore d’azione del rappresentabile e usa il convertitore del contesto per convertire le coordinate. Apple avverte che la vista associata al riconoscitore può avere una geometria diversa da quella della vista SwiftUI. NSGestureRecognizerRepresentable
  • Per una vista AppKit ospitata in SwiftUI, NSViewRepresentable gestisce la creazione, gli aggiornamenti e la rimozione della vista. Usa il suo coordinatore per le callback e lascia che sia SwiftUI a controllare il layout della vista rappresentata. Questa API è disponibile da macOS 10.15. NSViewRepresentable

Quando aggiungi uno dei due adattatori, mantieni i limiti degli eventi. Apple documenta che un riconoscitore opera sulla vista a cui è collegato e sui suoi discendenti, riceve gli eventi pertinenti prima della vista e può ritardarne la consegna. Specifica inoltre che non supporta il trasferimento del tracciamento degli eventi ad altri meccanismi, come il drag and drop o i menu a comparsa. Progetta queste interazioni usando i meccanismi supportati, invece di trattare un riconoscitore di clic come un relè universale degli eventi. NSGestureRecognizer

Che cosa dovresti verificare nell’interazione Mac completata?

Compila l’esempio in Xcode e controlla il comportamento osservabile prima di adattarlo a una gerarchia di viste più ampia:

  • Fai clic sul testo e sull’interno vuoto del pulsante personalizzato. Ogni attivazione dovrebbe aggiungere un cerchio e aggiornare il conteggio una sola volta.
  • Premi Command–Shift–A nella finestra attiva. Dovrebbe eseguire l’azione del pulsante basata sugli slot.
  • Fai clic in diverse posizioni del canvas, anche vicino ai bordi. I cerchi dovrebbero seguire le coordinate locali rispettando il margine indicato nel codice.
  • Raggiungi sei cerchi. Entrambi i metodi di posizionamento dovrebbero smettere di aggiungerne, mentre Cancella cerchi dovrebbe restare disponibile.
  • Svuota il canvas. Il posizionamento dovrebbe essere di nuovo disponibile.
  • Esamina il pulsante con VoiceOver e prova la navigazione da tastiera con le impostazioni attuali del tuo Mac. Verifica che la sua azione resti comprensibile dopo aver applicato lo stile.

Per una superficie d’azione personalizzata su Mac, parti dal significato dell’interazione: un comando con un nome preciso appartiene a un Button, la personalizzazione visiva appartiene alla sua etichetta e al suo stile, mentre il posizionamento dipendente dalla posizione appartiene al gesto del canvas. Collega questi percorsi tramite un’unica funzione che modifica lo stato e introduci AppKit solo quando la sua configurazione aggiuntiva soddisfa un requisito concreto.

Fonti