Verwende einen SwiftUI-Button, wenn deine benutzerdefinierte Mac-Oberfläche eine bestimmte Aktion ausführt. Verwende eine Click-Geste, wenn die Interaktion Gesteninformationen benötigt, etwa die Position auf einer Zeichenfläche. Diese Anleitung erstellt einen kleinen Formeneditor, um zu zeigen, wofür sich die jeweiligen Ansätze eignen.

Apple empfiehlt ausdrücklich einen Button, wenn ein Element als solcher funktioniert. Ein Button kann jede beliebige View als Beschriftung verwenden. Daher muss das Interaktionsverhalten nicht ersetzt werden, nur weil die Darstellung benutzerdefiniert ist. SwiftUI stellt standardmäßige Button-Verhaltensweisen bereit, darunter Barrierefreiheitsbeschriftungen und Hinweise. Gesten

Welche Interaktion benötigt deine Oberfläche?

Für Entwickler, die eine Mac-App erstellen, lautet die hilfreiche Frage, welche Informationen die Interaktion liefern muss. Eine Kachel mit der Beschriftung Add circle stellt einen Befehl dar. Ein Klick auf eine leere Zeichenfläche, um dort einen Kreis zu platzieren, stellt einen Befehl plus eine Position dar. Diese zusätzliche Eingabe ändert, welche Implementierung geeignet ist.

Die folgende Entscheidungshilfe überträgt Apples dokumentierte Unterscheidungen auf einen gewöhnlichen Formeneditor für den Desktop. Es handelt sich um Implementierungsempfehlungen, die aus den APIs abgeleitet sind, nicht um Regeln für jede denkbare Benutzeroberfläche.

Beabsichtigte Interaktion Ausgangspunkt Grund
Eine benutzerdefinierte Kachel mit der Beschriftung Add circle anklicken Button mit benutzerdefinierter Beschriftung Die Oberfläche löst eine bestimmte Aktion aus.
Der Kachel einen markanten Hintergrund und ein Erscheinungsbild beim Drücken geben ButtonStyle Passt die Darstellung an und behält das standardmäßige Button-Verhalten bei.
Auf eine Zeichenfläche klicken, um an dieser Stelle einen Kreis zu platzieren SpatialTapGesture Das Ergebnis enthält die Position der Interaktion.
Zwei Klicks auf einer Inhalts-View erkennen, ohne Koordinaten zu benötigen onTapGesture(count: 2) Der Modifikator akzeptiert eine erforderliche Klickanzahl.
Bestimmte Maustasten in einer AppKit-Interaktion konfigurieren NSClickGestureRecognizer Bietet Konfiguration für Maustaste und Klickanzahl.

Apple beschreibt einen Button als eine Aktion, die mit einer Beschriftung verbunden ist, und unterscheidet die Anpassung der Darstellung über ButtonStyle von der Anpassung des Interaktionsverhaltens über PrimitiveButtonStyle. Die Gesten-APIs dokumentieren separat Klickanzahlen und räumliche Ergebnisse. Button, onTapGesture(count:perform:), SpatialTapGesture, NSClickGestureRecognizer

Eine benutzerdefinierte Aktionskachel sollte also mit einem Button beginnen. Greife zu einer Geste, wenn du die zusätzliche Eingabe oder Erkennungsanforderung benennen kannst, die die Oberfläche benötigt.

Wie erstellst du einen benutzerdefinierten Button, ohne sein Verhalten neu zu implementieren?

Füge den Befehl in die Aktionsclosure des Buttons und die Grafik in seine Beschriftung ein. Verwende ButtonStyle für die Darstellung. Apple dokumentiert, dass dieses Protokoll das standardmäßige Interaktionsverhalten der Plattform beibehält; PrimitiveButtonStyle ist der separate Erweiterungspunkt, wenn das Interaktionsverhalten bewusst geändert werden soll. ButtonStyle

Das folgende Beispiel enthält zwei Geschwisteroberflächen: einen benutzerdefinierten Button Add circle und eine rechteckige Platzierungsfläche. Beide rufen dieselbe Änderungsfunktion auf. Der Button wählt einen vorhersehbaren Platz; die Zeichenfläche liefert einen lokalen Punkt. So wird der Unterschied sichtbar, ohne Dateien, externe Inhalte oder eine weitere App einzuführen.

Erstelle ein macOS-App-Projekt mit SwiftUI und setze das Deployment-Ziel auf macOS 13 oder neuer, der dokumentierten Mindestversion für SpatialTapGesture. Ersetze die generierten App- und Content-View-Definitionen durch die erste Datei und füge die zweite Datei demselben Target hinzu. Behalte genau eine @main-App-Deklaration bei. Der Code wurde geprüft, aber nicht in einer macOS-SDK-Umgebung kompiliert oder ausgeführt. SpatialTapGesture

Die vollständige Anwendung besteht aus diesen beiden Dateien. Zuerst 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))
    }
}

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

Der Style liest configuration.isPressed, um die Rückmeldung darzustellen; er ruft die Aktion selbst nie auf. Apple definiert diese Eigenschaft als den aktuellen gedrückten Zustand des Buttons. Dadurch bleiben Darstellung und Aktionsausführung voneinander getrennt. isPressed

Die Begrenzung auf sechs Kreise ist eine beispielhafte Anwendungsregel. Beide Einstiegspunkte verwenden addCircle(at:), das vor dem Hinzufügen prüft, ob noch Platz ist. Das deaktivierte Erscheinungsbild macht die Begrenzung sichtbar, während die gemeinsame Prüfung für eine einheitliche Änderungsregel sorgt. Apple dokumentiert disabled(_:) als Modifikator, der Interaktionen mit einer View steuert, darunter Klicks und Gesten. disabled(_:)

Warum verwendet die Zeichenfläche eine räumliche Klickgeste?

Die Zeichenfläche benötigt den Punkt, an dem der Benutzer geklickt hat. Die einfache Überladung von onTapGesture(count:perform:) führt eine Aktion ohne Positionsparameter aus. SpatialTapGesture meldet ausdrücklich eine Position, und sein Initializer verwendet standardmäßig eine Interaktion im lokalen Koordinatenraum. onTapGesture(count:perform:), SpatialTapGesture, init(count:coordinateSpace:)

In diesem Programm gehört die Geste zur Zeichenfläche mit fester Größe, deren lokale Koordinaten die Kreise positionieren. Die Platzierungsfunktion begrenzt jeden Mittelpunkt so, dass ein Abstand von 12 Punkten zum Rand bleibt, passend zum Radius des Kreises. Ein Klick nahe am Rand platziert daher den vollständigen Kreis innerhalb der Zeichenfläche. Überlappende Kreise sind zulässig, und die Anzahl bleibt auch dann sichtbar, wenn sich Formen überlagern.

Der Button und die Zeichenfläche haben außerdem bewusst unterschiedliches Tastaturverhalten. Das Tastenkürzel des Buttons führt dieselbe Aktion wie seine Aktivierung aus und fügt einen Kreis am nächsten Platz hinzu. Es erzeugt keine Zeigerposition. Apple dokumentiert Tastenkürzel als Möglichkeit, die primäre Aktion eines Steuerelements aufzurufen, und weist darauf hin, dass bei doppelten Tastenkürzelbelegungen das erste gefundene passende Steuerelement ausgewählt wird. Weise dieses Tastenkürzel im Beispiel einmal im Fenster zu. keyboardShortcut(_:modifiers:)

Die Beschriftung und die Anzahl der Zeichenfläche beschreiben ihren Inhalt; sie bieten keinen Tastaturablauf für die Platzierung an beliebigen Koordinaten. Der Button ermöglicht es, ohne Zeiger einen Kreis an einem vordefinierten Platz hinzuzufügen. Wenn eine präzise Platzierung auch über Tastatursteuerelemente verfügbar sein muss, füge eine explizite Oberfläche zur Positionsbearbeitung hinzu, statt anzunehmen, dass eine Geste oder eine beschreibende Beschriftung diese Möglichkeit bereitstellt.

Wie sollte die Trefferprüfung zur benutzerdefinierten Oberfläche passen?

Lege den gewünschten Interaktionsbereich fest, nachdem du die Größe der Beschriftung bestimmt hast. Im Beispiel hat die Button-Beschriftung einen Frame von 280 mal 64 Punkten, gefolgt von einer abgerundeten rechteckigen contentShape. Die Zeichenfläche legt ihren Frame von 320 mal 200 Punkten fest und definiert anschließend eine rechteckige Inhaltsform. Apple definiert contentShape(_:eoFill:) als die für die Trefferprüfung verwendete Form. contentShape(_:eoFill:)

So erhalten die sichtbaren Oberflächen klar definierte Interaktionsgrenzen. Wenn du bei einer Neugestaltung den Frame außerhalb der Button-Beschriftung verschiebst, prüfe das Ergebnis erneut: Die Größenangabe und die Inhaltsform des Beispiels gehören zusammen in die Beschriftung. Sorge dafür, dass Zeichnung, Layout und Interaktionsgeometrie bewusst festgelegt sind, statt einen nicht reagierenden Rand durch einen weiteren Aktionshandler zu beheben.

Wenn eine Oberfläche nicht mehr reagiert, prüfe die folgenden Punkte der Reihe nach:

  1. Geometrie prüfen. Klicke auf den Text, die Grafik und den leeren Innenbereich der Beschriftung. Vergewissere dich, dass die Inhaltsform die gewünschten Grenzen beschreibt.
  2. Deaktivierte übergeordnete Views prüfen. Ein deaktivierter Container setzt sich gegenüber einer aktivierten untergeordneten View durch. Ein lokales .disabled(false) stellt die Interaktion daher möglicherweise nicht wieder her. disabled(_:)
  3. Dekorative Overlays prüfen. Eine View, die nur der Darstellung dient, lässt sich mit .allowsHitTesting(false) von der Trefferprüfung ausschließen. Wende dies auf die Dekoration an, nicht auf die interaktive Oberfläche. allowsHitTesting(_:)
  4. Konkurrierende Handler prüfen. Ermittle, welche View für die Aktion zuständig ist, bevor du die Gestenpriorität änderst.

Wie verhinderst du konkurrierende Handler und Fehler bei der Zuständigkeit?

Verwende für jeden beabsichtigten Aktivierungspfad einen Handler. Im Beispiel ist der benutzerdefinierte Button für seinen Befehl zuständig, die Zeichenfläche für die positionsabhängige Platzierung. Keiner von beiden befindet sich in einem übergeordneten Element mit einer weiteren Platzierungsgeste. So lässt sich leicht erkennen, welche Wirkung ein einzelner Klick haben soll.

Füge dem Button keinen zweiten Tap-Handler hinzu, nur um dieselbe Änderungsfunktion aufzurufen. Füge ebenso wenig eine übergeordnete Geste um mehrere Steuerelemente herum hinzu, ohne zu entscheiden, ob die Interaktionen von Eltern- und untergeordnetem Element beide stattfinden sollen. Apples Beispiel zu simultaneousGesture zeigt, dass Handler für Eltern- und untergeordnete Elemente beide ausgeführt werden können. highPriorityGesture gibt der angehängten Geste stattdessen eine höhere Priorität. Diese Modifikatoren legen die Interaktionsregeln fest; sie sind keine austauschbaren Reparaturen. simultaneousGesture(_:including:), highPriorityGesture(_:including:)

Behalte den Anwendungszustand bei der View, die dafür zuständig ist. Hier speichert ShapeSurfaceView die beispielhaften Kreise, und der Style stellt lediglich die übergebene Beschriftung dar. Apple empfiehlt, privaten Zustand in der höchsten View zu speichern, die darauf zugreifen muss, und Werte oder Bindings bei Bedarf an untergeordnete Views weiterzugeben. State

Wenn ein späterer AppKit-Adapter einen Callback speichert, untersuche seine Erfassungsbeziehungen separat. Swift-Closures können umgebende Werte erfassen. Eine Klasse, die eine Closure speichert, welche dieselbe Instanz erfasst, kann dadurch einen starken Referenzzyklus erzeugen. Wähle eine Capture-Liste passend zur tatsächlichen Lebensdauer des Objekts. Ein Button wird nicht dadurch zu einer Geste, dass die Zuständigkeit für Callbacks geklärt wird. Closures

Wann ist AppKit der passende nächste Schritt?

Ziehe AppKit in Betracht, wenn deine eigene Oberfläche die dokumentierte mausspezifische Konfiguration benötigt. NSClickGestureRecognizer unterstützt eine erforderliche Klickanzahl und eine Maske für Maustasten. Apple legt fest, dass Bit null die primäre Taste und Bit eins die sekundäre Taste darstellt. Die Erkennung erfordert die konfigurierte Anzahl von Klicks ohne Ziehen. Außerdem aktiviert der Recognizer für die konfigurierten Maustasten automatisch Ereignisverzögerungen. NSClickGestureRecognizer, buttonMask

Wähle den Integrationsweg entsprechend deinem Deployment-Ziel und der Komponente, für die du zuständig bist:

  • Unter macOS 26 oder neuer integriert NSGestureRecognizerRepresentable über .gesture(...) einen AppKit-Recognizer direkt in eine SwiftUI-Hierarchie. SwiftUI richtet das Aktionsziel ein. Implementiere den Aktionshandler des Representable und verwende dessen Kontextkonverter für die Koordinatenumrechnung. Apple weist darauf hin, dass die Geometrie der dem Recognizer zugeordneten View von der Geometrie der SwiftUI-View abweichen kann. NSGestureRecognizerRepresentable
  • Für eine in SwiftUI eingebettete AppKit-View verwaltet NSViewRepresentable das Erstellen, Aktualisieren und Entfernen deiner View. Verwende dessen Coordinator für Callbacks und überlasse SwiftUI das Layout der repräsentierten View. Diese API ist ab macOS 10.15 verfügbar. NSViewRepresentable

Behalte beim Hinzufügen eines der beiden Adapter die Ereignisgrenze bei. Apple dokumentiert, dass ein Recognizer auf der View, an die er angehängt ist, und deren Nachfahren arbeitet, relevante Ereignisse vor der View erhält und deren Zustellung verzögern kann. Außerdem wird die Übergabe der Ereignisverarbeitung an andere Mechanismen wie Drag-and-drop oder Pop-up-Menüs ausdrücklich nicht unterstützt. Entwickle solche Interaktionen stattdessen mit den jeweils unterstützten Mechanismen, anstatt einen Klick-Recognizer als universelle Ereignisweiterleitung zu behandeln. NSGestureRecognizer

Was solltest du bei der fertigen Mac-Interaktion überprüfen?

Baue das Beispiel in Xcode und prüfe das beobachtbare Verhalten, bevor du es an eine größere View-Hierarchie anpasst:

  • Klicke auf den Text und den leeren Innenbereich des benutzerdefinierten Buttons. Bei jeder Aktivierung sollte ein Kreis hinzukommen und die Anzahl einmal aktualisiert werden.
  • Drücke Command–Shift–A im aktiven Fenster. Dadurch sollte die Aktion des Buttons ausgeführt werden, die einen Kreis am nächsten Platz hinzufügt.
  • Klicke an verschiedenen Stellen der Zeichenfläche, auch nahe an den Rändern. Die Kreise sollten den lokalen Koordinaten folgen und den im Code festgelegten Randabstand einhalten.
  • Füge sechs Kreise hinzu. Beide Platzierungswege sollten keine weiteren Kreise mehr hinzufügen, während Clear circles weiterhin verfügbar bleibt.
  • Leere die Zeichenfläche. Danach sollte die Platzierung wieder möglich sein.
  • Prüfe den Button mit VoiceOver und teste die Tastaturnavigation mit den aktuellen Einstellungen deines Mac. Vergewissere dich, dass seine Aktion auch nach der Gestaltung verständlich bleibt.

Beginne bei einer benutzerdefinierten Aktionsfläche für den Mac mit der Bedeutung der Interaktion: Ein bestimmter Befehl gehört in einen Button, die visuelle Anpassung in seine Beschriftung und seinen Style, und eine positionsabhängige Platzierung in die Geste der Zeichenfläche. Verknüpfe diese Pfade über eine gemeinsame Funktion, die den Zustand ändert. Führe AppKit erst dann ein, wenn seine zusätzliche Konfiguration eine konkrete Anforderung erfüllt.

Quellen