Para uma forma arrastada dentro do seu app para Mac, atualize seu deslocamento diretamente durante o gesto e, em seguida, anime esse mesmo deslocamento com uma mola ao soltar. No macOS 14 e posteriores, o SwiftUI pode transferir automaticamente para a mola a velocidade rastreada durante o arrasto.
Este tutorial cria uma pequena interação para desktop: arraste um bloco azul sintético em qualquer direção, solte-o e deixe que ele volte à posição inicial como uma mola. Todo o estado fica na memória, e o gesto pertence ao bloco na própria janela do app.
Como a mola herda a velocidade final do arrasto?
Transaction.tracksVelocity do SwiftUI registra a velocidade conforme as propriedades animáveis mudam. A Apple documenta que os callbacks de gesto onChanged e updating ativam esse rastreamento automaticamente. Quando a interação termina, uma animação pode usar a velocidade acumulada. Portanto, você não precisa calcular e passar uma velocidade inicial para essa interação comum do SwiftUI. A documentação da Apple sobre rastreamento de velocidade descreve explicitamente essa transferência.
A conexão importante está na propriedade animada. Durante o arrasto, grave a translação do gesto no deslocamento do bloco. Ao soltar, altere esse mesmo deslocamento para o destino dentro de withAnimation. Manter a view e seu estado ativos dá ao SwiftUI uma propriedade contínua para rastrear.
Nesta implementação, mantenha as atualizações do arrasto sem animação. A Apple explica que o rastreamento de velocidade da transação e uma animação que gerencia a própria velocidade são mutuamente exclusivos. Uma mola interativa durante o arrasto é outra técnica compatível, mas altera como o bloco acompanha o ponteiro; ela não é necessária para a interação de acompanhamento direto apresentada aqui. Rastreamento de velocidade da transação
O destino da mola e seu movimento inicial têm funções diferentes. O destino indica onde o bloco deve parar. A velocidade herdada indica como ele está se movendo no momento em que é solto. Um bloco solto enquanto se afasta da posição inicial pode continuar se movendo para fora por um instante antes de a mola trazê-lo de volta. A Apple demonstra molas que dão continuidade ao movimento com base na velocidade do gesto em Animar com molas.
Crie um exemplo completo de arrastar e soltar no macOS
Crie um app para macOS no Xcode usando SwiftUI, defina o destino de implantação como macOS 14.0 ou posterior e substitua o arquivo de entrada do app gerado pelo código a seguir. Mantenha apenas uma declaração @main no destino. O exemplo usa a API de conclusão de animação do macOS 14. Documentação da Apple sobre conclusão de animações
Este é um app completo em um único arquivo, sem recursos ou dependências de pacotes. As assinaturas da API foram verificadas na documentação; o app para desktop não foi compilado nem executado neste ambiente de pesquisa.
import SwiftUI
@main
struct SpringReleaseDemoApp: App {
var body: some Scene {
WindowGroup {
SpringReleaseView()
}
}
}
struct SpringReleaseView: View {
@State private var offset: CGSize = .zero
@State private var isReturning = false
@GestureState private var isDragging = false
var body: some View {
VStack(spacing: 16) {
Text("Drag the tile, then release")
.font(.headline)
ZStack {
RoundedRectangle(cornerRadius: 20)
.stroke(.gray.opacity(0.4), lineWidth: 2)
.frame(width: 112, height: 112)
RoundedRectangle(cornerRadius: 20)
.fill(.blue)
.frame(width: 112, height: 112)
.contentShape(Rectangle())
.offset(offset)
.gesture(tileDrag)
.allowsHitTesting(!isReturning)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.coordinateSpace(name: "canvas")
}
.padding(24)
.frame(width: 640, height: 440)
.onChange(of: isDragging) { _, active in
guard !active, !isReturning else { return }
// Cancellation cleanup has no successful release value.
withTransaction(Transaction(animation: nil)) {
offset = .zero
}
}
}
private var tileDrag: some Gesture {
DragGesture(
minimumDistance: 3,
coordinateSpace: .named("canvas")
)
.updating($isDragging) { _, active, _ in
active = true
}
.onChanged { value in
// Gesture callbacks automatically track velocity.
offset = value.translation
}
.onEnded { _ in
returnHome()
}
}
private func returnHome() {
guard !isReturning else { return }
isReturning = true
withAnimation(
.spring(duration: 0.45, bounce: 0.2),
completionCriteria: .removed
) {
offset = .zero
} completion: {
isReturning = false
}
}
}
O gesto é anexado ao bloco em movimento, enquanto seu espaço de coordenadas nomeado pertence à tela fixa. Isso deixa explícita a escolha do sistema de coordenadas da interação. A documentação da Apple sobre DragGesture descreve como anexar um arrasto configurado com o modificador de gesto.
value.translation é o deslocamento total desde o início do arrasto. Atribua-o a offset; não o some repetidamente ao deslocamento anterior. A soma repetida acumularia várias vezes o mesmo movimento. Neste exemplo, todo arrasto aceito começa na posição inicial, então não é necessário um deslocamento inicial separado. Definição de translação da Apple
Ao soltar com sucesso, chama-se returnHome() imediatamente. O deslocamento não é redefinido antes, nem a mola é agendada para depois. Assim, as mudanças rastreadas durante o arrasto e a animação ao soltar permanecem conectadas pela mesma propriedade de estado.
Por que manter estados separados para o arrasto, a posição e o retorno?
As três propriedades respondem a perguntas diferentes: offset armazena o deslocamento solicitado, isDragging acompanha um gesto ativo e isReturning registra a fase de retorno do exemplo. O proprietário do bloco mantém esses valores localmente. A Apple recomenda declarar como privadas as propriedades de estado pertencentes à view e compartilhar bindings apenas quando outra view precisar alterá-las. Documentação sobre State
A sintaxe do wrapper também tem uma função específica. Os wrappers de propriedade do Swift gerenciam o acesso a um valor encapsulado e podem expor um valor projetado com o prefixo de cifrão. Aqui, $isDragging fornece a projeção do estado do gesto usada por updating; não é uma segunda flag mantida de forma independente. A linguagem de programação Swift: propriedades
O cancelamento precisa de um tratamento separado porque onEnded só é executado quando um gesto termina com sucesso. A Apple recomenda @GestureState para estados que devem ser redefinidos independentemente de como o gesto termina. O exemplo observa essa redefinição e restaura o bloco sintético se nenhum retorno normal estiver em andamento. Essa alternativa faz o bloco voltar à posição inicial instantaneamente; ela não afirma que exista uma velocidade final de soltura para um gesto cancelado. Comportamento ao concluir gestos, GestureState
O exemplo também aceita deliberadamente um novo arrasto somente depois que a animação de retorno termina. Definir o deslocamento do modelo como zero inicia a animação, então zero já é o destino solicitado enquanto o bloco ainda está visivelmente em movimento. Remover essa restrição exigiria projetar como um novo arrasto assumiria o controle a partir da posição exibida naquele momento.
allowsHitTesting(!isReturning) controla se o bloco participa do teste de acerto. O critério de conclusão .removed libera a restrição quando a animação inteira termina. Isso evita adivinhar um atraso com base no parâmetro de duração da mola. Controle do teste de acerto, Conclusão por remoção da animação
Como ajustar e verificar o retorno?
Comece com um destino fixo e configurações fixas para a mola. Mude a maneira como solta o bloco antes de alterar os parâmetros da animação. Isso permite distinguir a herança de velocidade do ajuste da mola.
Faça as verificações a seguir dentro da janela. Esta é uma sequência de diagnóstico baseada na transferência documentada, não em resultados medidos neste exemplo.
| Verificação | Ação | O que observar |
|---|---|---|
| Soltura quase sem movimento | Arraste para longe da posição inicial, faça uma pausa e solte. | O retorno deve começar com pouco movimento herdado. |
| Soltura para fora | Arraste para longe da posição inicial e solte enquanto ainda se move para fora. | A mola deve manter o movimento para fora antes de inverter a direção. |
| Soltura para dentro | Arraste para longe, inverta a direção rumo à posição inicial e solte. | O retorno deve dar continuidade ao movimento para dentro. |
| Soltura diagonal | Solte enquanto se move pelos dois eixos. | Verifique se a transferência mantém a direção, em vez de se comportar como um retorno apenas horizontal. |
| Próxima interação | Aguarde o fim do retorno e arraste novamente. | O novo arrasto deve começar na posição inicial. |
Ajuste duration para controlar o ritmo e bounce para definir o caráter do movimento. 0.45 e 0.2 do exemplo são valores iniciais ilustrativos, não configurações obrigatórias. A Apple define a duração como duração perceptual e descreve o valor zero de elasticidade como criticamente amortecido; valores positivos de elasticidade produzem molas progressivamente mais oscilantes. Configuração da mola
A duração perceptual não garante exatamente quando todo movimento visível termina. A Apple a distingue da duração de acomodação, que depende do movimento da mola. Para a fase de retorno deste exemplo, use o callback de conclusão em vez de tratar 0.45 como um temporizador. Animar com molas
Se parecer que o movimento é ignorado ao soltar, inspecione primeiro a transferência: o gesto está alterando o mesmo deslocamento que a mola anima? Algum outro código sobrescreve esse deslocamento antes da soltura? A view está sendo substituída? Um modificador de animação está animando as atualizações do arrasto? Essas verificações decorrem diretamente do requisito documentado de rastrear propriedades animáveis em mudança antes de passá-las a uma animação.
Quando você deve ler a velocidade explicitamente ou usar AppKit?
DragGesture.Value.velocity expõe a velocidade atual do arrasto, e onEnded fornece o valor final do gesto após uma conclusão bem-sucedida. Ler esse valor é útil quando a velocidade influencia uma decisão do app, como escolher entre dois pontos de encaixe sintéticos. Para o destino fixo de retorno deste exemplo, o rastreamento automático já fornece a transferência para a animação. Velocidade do arrasto, Valor final do gesto
Se você escolher uma API explícita de velocidade inicial para a mola, confira sua representação antes de passar uma medição do gesto. A demonstração da Apple de animação controlada por gesto converte a velocidade em velocidade unitária, dividindo-a pela distância restante até o destino. Nesse cálculo ilustrativo, 300 pontos por segundo ao longo de 150 pontos de deslocamento se convertem em 2 distâncias de animação por segundo. Essa conversão também exige uma política deliberada quando a distância restante é zero. Neste caso, o caminho automático do SwiftUI evita esse cálculo. Aprimore suas animações e transições de UI
Use AppKit quando uma view ou um reconhecedor existente, pertencente ao app e baseado em AppKit, precisar controlar a interação. NSPanGestureRecognizer.velocity(in:) retorna as velocidades horizontal e vertical em pontos por segundo em relação à view fornecida. Embora a assinatura Swift aceite uma view opcional, a Apple diz explicitamente que esse argumento não pode ser nil. Velocidade de pan do AppKit
No macOS 26 e posteriores, NSGestureRecognizerRepresentable é a forma documentada de integrar um reconhecedor de gestos do AppKit a uma hierarquia SwiftUI. O SwiftUI gerencia sua criação e suas atualizações, além de fornecer suporte à conversão de coordenadas. A Apple alerta que a geometria da view subjacente ao reconhecedor pode diferir da geometria da view SwiftUI; ao cruzar esse limite, use o conversor do representable. Integração de gestos do AppKit
Para a interação com o bloco, mantenha o gesto SwiftUI como o único responsável pelas atualizações do arrasto e deixe que a mola de retorno anime o deslocamento existente. Quando essa transferência funcionar, ajuste o destino e os parâmetros da mola, preservando o mesmo estado e os mesmos limites do sistema de coordenadas.
Fontes
tracksVelocity — Apple Developer
Animar com molas — Apple Developer
withAnimation(:completionCriteria::completion:) — Apple Developer
DragGesture — Apple Developer
translation — Apple Developer
State — Apple Developer
Properties — Swift.org
onEnded(_:) — Apple Developer
GestureState — Apple Developer
allowsHitTesting(_:) — Apple Developer
removed — Apple Developer
spring(duration:bounce:blendDuration:) — Apple Developer
velocity — Apple Developer
Aprimore suas animações e transições de UI — Apple Developer
velocity(in:) — Apple Developer
NSGestureRecognizerRepresentable — Apple Developer