Use um Button do SwiftUI quando sua superfície personalizada no Mac executar uma ação nomeada. Use um gesto de clique quando a interação precisar de informações do gesto, como a localização em uma tela de desenho. Este guia cria um pequeno editor de formas para mostrar onde cada opção se encaixa.
A Apple recomenda explicitamente um botão sempre que um elemento funcionar como tal. Um botão pode usar qualquer view como rótulo, então uma aparência personalizada não exige substituir seu comportamento de interação. O SwiftUI fornece comportamentos padrão de botão, incluindo rótulos e dicas de acessibilidade. Gestures
Qual interação sua superfície precisa?
Para quem desenvolve um app para Mac, a pergunta útil é quais informações a interação precisa fornecer. Um bloco identificado como Add circle expressa um comando. Clicar em uma área vazia da superfície de desenho para colocar um círculo expressa um comando mais uma localização. Essa entrada adicional muda a implementação adequada.
O guia de decisão a seguir aplica as distinções documentadas pela Apple a um editor de formas comum para desktop. Estas são recomendações de implementação derivadas das APIs, não regras para toda interface possível.
| Interação pretendida pelo leitor | Ponto de partida | Motivo |
|---|---|---|
Clicar em um bloco personalizado identificado como Add circle |
Button com um rótulo personalizado |
A superfície inicia uma ação nomeada. |
| Dar ao bloco um plano de fundo distinto e uma aparência de pressionado | ButtonStyle |
Personaliza a aparência preservando a interação padrão de botão. |
| Clicar em uma tela de desenho para colocar um círculo naquele ponto | SpatialTapGesture |
O resultado inclui a localização da interação. |
| Reconhecer dois cliques em uma view de conteúdo sem precisar das coordenadas | onTapGesture(count: 2) |
O modificador aceita uma contagem de cliques obrigatória. |
| Configurar botões específicos do mouse em uma interação com AppKit | NSClickGestureRecognizer |
Ele expõe a configuração do botão do mouse e da contagem de cliques. |
A Apple documenta um botão como uma ação associada a um rótulo e distingue a personalização da aparência por meio de ButtonStyle da personalização da interação por meio de PrimitiveButtonStyle. As APIs de gestos documentam separadamente contagens de cliques e resultados espaciais. Button, onTapGesture(count:perform:), SpatialTapGesture, NSClickGestureRecognizer
Assim, um bloco de ação personalizado começa como um botão. Recorra a um gesto quando puder identificar a entrada adicional ou o requisito de reconhecimento de que a superfície precisa.
Como criar um botão personalizado sem reconstruir seu comportamento?
Coloque o comando no fechamento de ação do botão e a arte no rótulo. Use ButtonStyle para a apresentação. A Apple documenta que esse protocolo mantém o comportamento de interação padrão da plataforma; PrimitiveButtonStyle é o ponto de extensão separado para alterar intencionalmente o comportamento da interação. ButtonStyle
O exemplo abaixo contém duas superfícies irmãs: um botão personalizado Add circle e uma tela retangular para posicionamento. Ambos chamam a mesma função de mutação. O botão escolhe uma posição previsível; a tela fornece um ponto local. Isso torna a diferença visível sem introduzir arquivos, conteúdo externo ou outro app.
Crie um projeto de app para macOS usando SwiftUI e defina como destino de implantação o macOS 13 ou posterior, mínimo documentado para SpatialTapGesture. Substitua as definições geradas do app e da view de conteúdo pelo primeiro arquivo e adicione o segundo arquivo ao mesmo destino. Mantenha exatamente uma declaração de app @main. O código foi inspecionado, mas não foi compilado nem executado em um ambiente com SDK do macOS. SpatialTapGesture
O aplicativo completo consiste nestes dois arquivos. Primeiro, 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))
}
}
Segundo, 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
))
)
}
}
O estilo lê configuration.isPressed para desenhar o feedback; ele nunca chama a ação por conta própria. A Apple define essa propriedade como o estado atual de pressionamento do botão. Isso mantém a renderização e a execução da ação em lugares separados. isPressed
O limite de seis círculos é uma regra ilustrativa do aplicativo. Os dois pontos de entrada usam addCircle(at:), que verifica a capacidade antes de adicionar um círculo. A aparência desativada comunica o limite, enquanto a verificação compartilhada mantém consistente a regra de mutação. A Apple documenta disabled(_:) como um controle da interação com uma view, incluindo cliques e gestos. disabled(_:)
Por que a tela usa um gesto de clique espacial?
A tela precisa do ponto em que o usuário clicou. A sobrecarga básica de onTapGesture(count:perform:) fornece uma ação sem um parâmetro de localização. SpatialTapGesture informa explicitamente uma localização, e seu inicializador usa por padrão uma interação no espaço de coordenadas local. onTapGesture(count:perform:), SpatialTapGesture, init(count:coordinateSpace:)
Neste programa, o gesto pertence à tela de tamanho fixo, cujas coordenadas locais posicionam os círculos. A função de posicionamento limita cada centro para manter uma margem de 12 pontos, igual ao raio do círculo. Assim, um clique perto da borda posiciona o círculo inteiro dentro da tela. É permitido sobrepor círculos, e a contagem continua visível mesmo quando as formas se sobrepõem.
O botão e a tela também têm comportamentos de teclado intencionalmente diferentes. O atalho do botão executa a mesma ação que ativá-lo, adicionando um círculo na próxima posição. Ele não cria uma localização de ponteiro. A Apple documenta que os atalhos de teclado invocam a ação principal de um controle e observa que, em caso de atribuições duplicadas, o atalho é associado ao primeiro controle correspondente encontrado. Atribua esse atalho uma única vez na janela de exemplo. keyboardShortcut(_:modifiers:)
O rótulo de acessibilidade e a contagem da tela descrevem seu conteúdo; não oferecem um fluxo de teclado para posicionar formas em coordenadas arbitrárias. O botão oferece uma alternativa sem ponteiro para adicionar um círculo em uma posição predefinida. Se o posicionamento exato também precisar estar disponível pelos controles do teclado, adicione uma interface explícita de edição da posição, em vez de presumir que um gesto ou rótulo descritivo ofereça essa funcionalidade.
Como fazer o teste de acerto corresponder à superfície personalizada?
Defina a área de interação pretendida depois de estabelecer o tamanho do rótulo. No exemplo, o rótulo do botão tem um frame de 280 por 64 seguido de um contentShape retangular com cantos arredondados. A tela estabelece seu frame de 320 por 200 e, em seguida, declara uma forma de conteúdo retangular. A Apple define contentShape(_:eoFill:) como a forma usada para o teste de acerto. contentShape(_:eoFill:)
Isso dá às superfícies visíveis limites de interação explícitos. Se, ao reformular o design, você mover o frame para fora do rótulo do botão, inspecione o resultado novamente: no exemplo, o dimensionamento e a forma de conteúdo ficam juntos dentro do rótulo. Mantenha intencionais as geometrias de desenho, layout e interação, em vez de corrigir uma margem sem resposta adicionando outro manipulador de ação.
Quando uma superfície deixa de responder, verifique estas condições nesta ordem:
- Verifique a geometria. Clique no texto, no gráfico e no interior vazio do rótulo. Confirme que a forma de conteúdo descreve os limites pretendidos.
- Verifique os ancestrais desativados. Um contêiner desativado prevalece sobre um descendente ativado, então um
.disabled(false)local talvez não restaure a interação. disabled(_:) - Verifique as sobreposições decorativas. Uma view pode ser excluída do teste de acerto com
.allowsHitTesting(false)quando for puramente visual. Aplique-o à decoração, não à superfície interativa. allowsHitTesting(_:) - Verifique os manipuladores concorrentes. Identifique qual view é responsável pela ação antes de alterar a precedência dos gestos.
Como evitar manipuladores concorrentes e erros de responsabilidade?
Use um manipulador para cada caminho de ativação pretendido. No exemplo, o botão personalizado é responsável pelo comando, e a tela é responsável pelo posicionamento baseado na localização. Nenhum dos dois está dentro de um pai com outro gesto de posicionamento. Com essa estrutura, é fácil inspecionar o efeito pretendido de um único clique.
Evite anexar um segundo manipulador de toque ao botão apenas para chamar a mesma função de mutação. Da mesma forma, não adicione um gesto ao pai que engloba vários controles sem decidir se as interações do pai e do filho devem ocorrer ambas. O exemplo da Apple com simultaneousGesture demonstra que os manipuladores do pai e do filho podem ser executados ambos. Já highPriorityGesture dá maior precedência ao gesto anexado. Esses modificadores expressam uma política de interação; não são soluções intercambiáveis. simultaneousGesture(_:including:), highPriorityGesture(_:including:)
Mantenha o estado do aplicativo na view responsável por ele. Aqui, ShapeSurfaceView armazena os círculos sintéticos, e o estilo apenas renderiza o rótulo fornecido. A Apple recomenda manter o estado privado na view de nível mais alto que precise acessá-lo, passando valores ou vínculos aos descendentes conforme necessário. State
Se um adaptador AppKit futuro armazenar um callback, examine separadamente suas relações de captura. Fechamentos Swift podem capturar valores do contexto ao redor; uma classe que armazena um fechamento que captura essa mesma instância pode criar um ciclo de referência forte. Escolha a lista de captura conforme o ciclo de vida real do objeto. Transformar um botão em gesto não resolve a responsabilidade pela propriedade do callback. Closures
Quando o AppKit é o próximo passo adequado?
Considere o AppKit quando sua própria superfície precisar da configuração específica do mouse documentada. NSClickGestureRecognizer permite definir uma contagem de cliques obrigatória e uma máscara de botões do mouse. A Apple especifica que o bit zero representa o botão principal e o bit um, o secundário; o reconhecimento exige os cliques configurados sem arrastar. Ele também ativa automaticamente os atrasos de eventos para os botões do mouse configurados. NSClickGestureRecognizer, buttonMask
Escolha o caminho de integração de acordo com o destino de implantação e o componente sob sua responsabilidade:
- No macOS 26 ou posterior,
NSGestureRecognizerRepresentableintegra diretamente um reconhecedor AppKit a uma hierarquia SwiftUI por meio de.gesture(...). O SwiftUI instala o destino da ação; implemente o manipulador de ação do representable e use o conversor de contexto para converter coordenadas. A Apple alerta que a view associada ao reconhecedor pode ter uma geometria diferente da view SwiftUI. NSGestureRecognizerRepresentable - Para uma view AppKit hospedada no SwiftUI,
NSViewRepresentablegerencia a criação, as atualizações e a desmontagem da sua view. Use o coordenador para callbacks e deixe o SwiftUI controlar o layout da view representada. Esta API está disponível desde o macOS 10.15. NSViewRepresentable
Preserve o limite dos eventos ao adicionar qualquer um dos adaptadores. A documentação do AppKit especifica que um reconhecedor atua na view à qual está anexado e nos descendentes dela, recebe os eventos relevantes antes da view e pode atrasar a entrega desses eventos. Também declara explicitamente que não há suporte para transferir o rastreamento de eventos a outros mecanismos, como arrastar e soltar ou menus pop-up. Crie essas interações com os mecanismos compatíveis, em vez de tratar um reconhecedor de cliques como um retransmissor universal de eventos. NSGestureRecognizer
O que verificar na interação pronta para Mac?
Compile o exemplo no Xcode e verifique o comportamento observável antes de adaptá-lo a uma hierarquia de views maior:
- Clique no texto e no interior vazio do botão personalizado. Cada ativação deve adicionar um círculo e atualizar a contagem uma vez.
- Pressione
Command–Shift–Ana janela ativa. Isso deve executar a ação do botão baseada em posições. - Clique em diferentes posições da tela, inclusive perto das bordas. Os círculos devem seguir as coordenadas locais, respeitando a margem documentada no código.
- Chegue a seis círculos. Os dois caminhos de posicionamento devem parar de adicionar, enquanto
Clear circlescontinua disponível. - Limpe a tela. O posicionamento deve ficar disponível novamente.
- Inspecione o botão com o VoiceOver e teste a navegação por teclado com as configurações atuais do seu Mac. Verifique se a ação continua compreensível depois da estilização.
Para uma superfície de ação personalizada no Mac, comece pelo significado da interação: um comando nomeado pertence a um Button, a personalização visual pertence ao rótulo e ao estilo, e o posicionamento dependente de localização pertence ao gesto da tela. Mantenha esses caminhos conectados por uma única função que altere o estado e, em seguida, introduza o AppKit apenas quando sua configuração adicional atender a um requisito concreto.
Fontes
Button — Documentação para Desenvolvedores da Apple.
Gestures — Documentação para Desenvolvedores da Apple.
ButtonStyle — Documentação para Desenvolvedores da Apple.
isPressed — Documentação para Desenvolvedores da Apple.
onTapGesture(count:perform:) — Documentação para Desenvolvedores da Apple.
SpatialTapGesture — Documentação para Desenvolvedores da Apple.
init(count:coordinateSpace:) — Documentação para Desenvolvedores da Apple.
contentShape(_:eoFill:) — Documentação para Desenvolvedores da Apple.
disabled(_:) — Documentação para Desenvolvedores da Apple.
allowsHitTesting(_:) — Documentação para Desenvolvedores da Apple.
keyboardShortcut(_:modifiers:) — Documentação para Desenvolvedores da Apple.
simultaneousGesture(_:including:) — Documentação para Desenvolvedores da Apple.
highPriorityGesture(_:including:) — Documentação para Desenvolvedores da Apple.
State — Documentação para Desenvolvedores da Apple.
Closures — Swift.org, The Swift Programming Language.
NSClickGestureRecognizer — Documentação para Desenvolvedores da Apple.
buttonMask — Documentação para Desenvolvedores da Apple.
NSGestureRecognizer — Documentação para Desenvolvedores da Apple.
NSGestureRecognizerRepresentable — Documentação para Desenvolvedores da Apple.
NSViewRepresentable — Documentação para Desenvolvedores da Apple.