Pour une forme déplacée dans votre app Mac, mettez directement à jour son décalage pendant le geste, puis animez ce même décalage avec un ressort au relâchement. Sous macOS 14 et versions ultérieures, SwiftUI peut automatiquement transmettre au ressort la vitesse suivie.
Ce tutoriel crée une petite interaction de bureau : faites glisser une tuile bleue synthétique dans n’importe quelle direction, relâchez-la et laissez-la revenir à sa position de départ avec un effet de ressort. Tout l’état reste en mémoire et le geste est associé à la tuile dans la propre fenêtre de l’app.
Comment le ressort hérite-t-il de la vitesse finale du glissement ?
Transaction.tracksVelocity de SwiftUI enregistre la vitesse lorsque les propriétés animables changent. Apple indique que les rappels onChanged et updating des gestes activent automatiquement ce suivi. À la fin de l’interaction, une animation peut utiliser la vitesse accumulée. Vous n’avez donc pas besoin de calculer ni de transmettre une vitesse initiale pour cette interaction SwiftUI ordinaire. La documentation d’Apple sur le suivi de la vitesse décrit explicitement ce transfert.
Le lien essentiel concerne la propriété animée. Pendant le glissement, affectez la translation du geste au décalage de la tuile. Au relâchement, modifiez ce même décalage pour lui donner sa destination dans withAnimation. Le fait de conserver la vue et son état permet à SwiftUI de suivre une propriété de façon continue.
Dans cette implémentation, ne faites pas d’animation pendant le glissement. Apple explique que le suivi de la vitesse par une transaction et une animation qui gère sa propre vitesse sont mutuellement exclusifs. Un ressort interactif pendant le glissement est une autre technique prise en charge, mais elle modifie la façon dont la tuile suit le pointeur ; elle n’est pas nécessaire pour l’interaction où la tuile suit directement le pointeur, présentée ici. Suivi de la vitesse par transaction
La destination du ressort et son mouvement initial remplissent des rôles différents. La destination indique où la tuile doit se stabiliser. La vitesse héritée indique son mouvement au moment du relâchement. Une tuile relâchée alors qu’elle s’éloigne de son point de départ peut d’abord poursuivre son mouvement vers l’extérieur avant que le ressort ne la ramène. Apple montre des ressorts qui prolongent le mouvement selon la vitesse du geste dans Animer avec des ressorts.
Créer un exemple complet de glissement et de relâchement pour macOS
Dans Xcode, créez une app macOS avec SwiftUI, définissez sa cible de déploiement sur macOS 14.0 ou une version ultérieure, puis remplacez le fichier d’entrée d’app généré par le code suivant. Ne conservez qu’une seule déclaration d’app @main dans la cible. L’exemple utilise l’API de macOS 14 pour la fin des animations. La documentation d’Apple sur la fin des animations
Il s’agit d’une application complète dans un seul fichier, sans ressources ni dépendances de package. Les signatures d’API ont été vérifiées par rapport à la documentation ; l’application de bureau n’a pas été compilée ni exécutée dans cet environnement de recherche.
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
}
}
}
Le geste est associé à la tuile mobile, tandis que son espace de coordonnées nommé appartient au canevas fixe. Le choix du système de coordonnées de l’interaction est ainsi explicite. La documentation d’Apple sur DragGesture explique comment associer un glissement configuré à l’aide du modificateur de geste.
value.translation correspond au déplacement total depuis le début du glissement. Affectez-le à offset ; ne l’ajoutez pas à répétition au décalage précédent. Une addition répétée accumulerait plusieurs fois le même mouvement. Dans cet exemple, chaque glissement accepté commence au point de départ, aucun décalage initial distinct n’est donc nécessaire. Définition de la translation par Apple
Au relâchement réussi, appelez immédiatement returnHome(). Ne réinitialisez pas le décalage auparavant et ne programmez pas le ressort pour plus tard. Les changements suivis pendant le glissement et l’animation de relâchement restent ainsi liés par la même propriété d’état.
Pourquoi conserver des états distincts pour le glissement, la position et le retour ?
Les trois propriétés répondent à des questions différentes : offset stocke le déplacement demandé, isDragging suit un geste actif et isReturning indique la phase de retour de l’exemple. Le propriétaire de la tuile conserve ces valeurs localement. Apple recommande de déclarer comme privés les états possédés par une vue et de partager des liaisons uniquement lorsqu’une autre vue doit les modifier. Documentation sur l’état
La syntaxe de l’enveloppe de propriété a également un rôle précis. Les enveloppes de propriétés Swift gèrent l’accès à une valeur enveloppée et peuvent exposer une valeur projetée au moyen du préfixe dollar. Ici, $isDragging fournit la projection d’état du geste utilisée par updating ; il ne s’agit pas d’un second indicateur géré indépendamment. Le langage de programmation Swift : propriétés
L’annulation nécessite un traitement distinct, car onEnded n’est appelé que lorsqu’un geste se termine avec succès. Apple recommande @GestureState pour les états qui doivent être réinitialisés quelle que soit la façon dont un geste se termine. L’exemple observe cette réinitialisation et remet la tuile synthétique à sa place si aucun retour normal n’est déjà en cours. Cette solution de repli la ramène instantanément au point de départ ; elle ne prétend pas disposer d’une vitesse finale pour un geste annulé. Comportement à la fin d’un geste, GestureState
L’exemple n’accepte volontairement un nouveau glissement qu’une fois l’animation de retour terminée. Affecter zéro au décalage du modèle lance l’animation ; zéro est donc déjà la destination demandée pendant que la tuile continue visiblement à se déplacer. Supprimer simplement cette restriction nécessiterait de définir comment un nouveau glissement prend le relais depuis la position actuellement affichée.
allowsHitTesting(!isReturning) détermine si la tuile participe à la détection des interactions. Le critère d’achèvement .removed lève la restriction lorsque l’animation entière est terminée. On évite ainsi de deviner un délai à partir du paramètre de durée du ressort. Contrôle de la détection des interactions, Fin d’animation avec suppression
Comment régler et vérifier le relâchement ?
Commencez par définir une destination et des paramètres de ressort fixes. Modifiez la façon dont vous relâchez la tuile avant de changer les paramètres de l’animation. Vous distinguez ainsi l’héritage de la vitesse du réglage du ressort.
Effectuez les vérifications suivantes dans la fenêtre. Il s’agit d’une séquence de diagnostic déduite du transfert documenté, et non de résultats mesurés pour cet exemple.
| Vérification | Action | Élément à observer |
|---|---|---|
| Relâchement presque à l’arrêt | Éloignez la tuile de son point de départ, faites une pause, puis relâchez-la. | Le retour devrait commencer avec peu de mouvement transmis. |
| Relâchement vers l’extérieur | Éloignez la tuile de son point de départ et relâchez-la alors qu’elle continue de s’éloigner. | Le ressort devrait prolonger le mouvement vers l’extérieur avant de l’inverser. |
| Relâchement vers l’intérieur | Éloignez la tuile, inversez le mouvement vers le point de départ, puis relâchez-la. | Le retour devrait prolonger le mouvement vers l’intérieur. |
| Relâchement diagonal | Relâchez la tuile alors qu’elle se déplace sur les deux axes. | Vérifiez que le transfert conserve la direction et ne produit pas un retour uniquement horizontal. |
| Interaction suivante | Attendez la fin du retour, puis faites à nouveau glisser la tuile. | Le nouveau glissement devrait commencer à la position de départ. |
Réglez duration pour contrôler le rythme et bounce pour ajuster le caractère du mouvement. Les valeurs 0.45 et 0.2 de l’exemple sont des points de départ illustratifs, et non des réglages prescrits. Apple définit la durée comme une durée perceptuelle et décrit un rebond nul comme un amortissement critique ; les valeurs de rebond positives produisent des ressorts de plus en plus rebondissants. Configuration des ressorts
La durée perceptuelle ne garantit pas exactement le moment où chaque mouvement visible prend fin. Apple la distingue de la durée de stabilisation, qui dépend du mouvement du ressort. Pour la phase de retour de cet exemple, utilisez le rappel de fin au lieu de traiter 0.45 comme un minuteur. Animer avec des ressorts
Si le relâchement semble ignorer le mouvement, vérifiez d’abord le transfert : le geste modifie-t-il le même décalage que celui animé par le ressort ? Un autre code écrase-t-il ce décalage avant le relâchement ? La vue est-elle remplacée ? Un modificateur d’animation anime-t-il les mises à jour pendant le glissement ? Ces vérifications découlent directement de la nécessité documentée de suivre les changements de propriétés animables avant de les transmettre à une animation.
Quand lire explicitement la vitesse ou utiliser AppKit ?
DragGesture.Value.velocity expose la vitesse actuelle du glissement, et onEnded fournit la valeur finale du geste après une fin réussie. La lecture de cette vitesse est utile lorsqu’elle influe sur une décision de l’application, par exemple pour choisir entre deux points d’ancrage synthétiques. Pour la destination de retour fixe de cet exemple, le suivi automatique assure déjà le transfert à l’animation. Vitesse du glissement, Valeur finale du geste
Si vous choisissez une API qui prend explicitement une vitesse initiale pour le ressort, vérifiez sa représentation avant de lui transmettre une mesure du geste. Dans sa démonstration d’animation pilotée par un geste, Apple convertit la vitesse en vitesse unitaire en la divisant par la distance restante jusqu’à la destination. Selon son calcul illustratif, une vitesse de 300 points par seconde sur une distance restante de 150 points devient 2 distances d’animation par seconde. Cette conversion exige également de définir une règle lorsque la distance restante est nulle. Dans ce cas, le chemin automatique de SwiftUI évite ce calcul. Améliorer les animations et les transitions de votre interface utilisateur
Utilisez AppKit lorsqu’une vue ou un recognizer AppKit appartenant à l’app doit gérer l’interaction existante. NSPanGestureRecognizer.velocity(in:) renvoie les vitesses horizontale et verticale en points par seconde par rapport à la vue fournie. Bien que sa signature Swift accepte une vue facultative, Apple précise que cet argument ne doit pas être nil. Vitesse d’un panoramique dans AppKit
À partir de macOS 26, NSGestureRecognizerRepresentable est la méthode documentée pour intégrer un recognizer de geste AppKit dans une hiérarchie SwiftUI. SwiftUI gère sa création et ses mises à jour, et fournit une prise en charge pour la conversion des coordonnées. Apple avertit que la géométrie de la vue sous-jacente au recognizer peut différer de celle de la vue SwiftUI ; utilisez donc le convertisseur de la représentation pour passer d’un système à l’autre. Intégration des gestes AppKit
Pour cette interaction avec la tuile, gardez le geste SwiftUI comme unique responsable des mises à jour du glissement et laissez l’animation de relâchement agir sur le décalage existant. Une fois ce transfert fonctionnel, ajustez la destination et les paramètres du ressort tout en conservant les mêmes états et limites de coordonnées.
Sources
tracksVelocity — Apple Developer
Animer avec des ressorts — 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
Améliorer les animations et les transitions de votre interface utilisateur — Apple Developer
velocity(in:) — Apple Developer
NSGestureRecognizerRepresentable — Apple Developer