För en form som dras i din Mac-app uppdaterar du dess förskjutning direkt under gesten och animerar sedan samma förskjutning med en fjäder när du släpper. På macOS 14 och senare kan SwiftUI automatiskt föra över den registrerade draghastigheten till fjädern.
Den här handledningen bygger en liten interaktion för datorn: dra en syntetisk blå ruta i valfri riktning, släpp den och låt den fjädra tillbaka till startpositionen. Allt tillstånd finns kvar i minnet, och gesten hör till rutan i appens eget fönster.
Hur ärver fjädern dragets sluthastighet?
SwiftUIs Transaction.tracksVelocity registrerar hastighet när animerbara egenskaper ändras. Apple dokumenterar att återanropen onChanged och updating för gester aktiverar den här registreringen automatiskt. När interaktionen avslutas kan en animering använda den ackumulerade hastigheten. Du behöver alltså inte beräkna och skicka med en initial hastighet för den här vanliga SwiftUI-interaktionen. Apples dokumentation om hastighetsregistrering beskriver uttryckligen den här överföringen.
Den viktiga kopplingen är den animerade egenskapen. Skriv in gestens förflyttning i rutans förskjutning medan den dras. När du släpper ändrar du samma förskjutning till dess destination inuti withAnimation. Om vyn och dess tillstånd finns kvar får SwiftUI en sammanhängande egenskap att följa.
Låt draguppdateringarna vara o animerade i den här implementationen. Apple förklarar att transaktionens hastighetsregistrering och en animering som hanterar sin egen hastighet utesluter varandra. En interaktiv fjäder under dragningen är en annan teknik som stöds, men den ändrar hur rutan följer pekaren. Den behövs inte för interaktionen där rutan följer pekaren direkt, som beskrivs här. Transaktionens hastighetsregistrering
Fjäderns destination och dess initiala rörelse har olika funktioner. Destinationen anger var rutan ska stanna. Den ärvda hastigheten anger hur den rör sig när den släpps. En ruta som släpps medan den rör sig bort från utgångsläget kan först fortsätta utåt innan fjädern för den tillbaka. Apple visar hur fjädrar fortsätter rörelsen utifrån gestens hastighet i Animera med fjädrar.
Bygg ett komplett macOS-exempel med dragning och återgång
Skapa en macOS-app i Xcode med SwiftUI, ange macOS 14.0 eller senare som distributionsmål och ersätt den genererade appens startfil med följande kod. Ha bara en @main-appdeklaration i målet. Exemplet använder macOS 14:s API för animeringsslutförande. Apples dokumentation om animeringsslutförande
Det här är ett komplett program i en enda fil utan resurser eller paketberoenden. API-signaturerna har kontrollerats mot dokumentationen; skrivbordsappen kompilerades eller kördes inte i den här forskningsmiljön.
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
}
}
}
Gesten är kopplad till rutan som rör sig, medan dess namngivna koordinatsystem tillhör den stillastående ritytan. Då blir interaktionens koordinatval tydligt. Apples dokumentation om DragGesture beskriver hur en konfigurerad draggest kopplas med gestmodifieraren.
value.translation är den totala förflyttningen sedan dragningen började. Tilldela den till offset; lägg inte upprepade gånger till den i den tidigare förskjutningen. Upprepad addition skulle ackumulera samma rörelse flera gånger. I det här exemplet börjar varje accepterad dragning från utgångsläget, så någon separat startförskjutning behövs inte. Apples definition av translation
När dragningen avslutas som den ska anropas returnHome() direkt. Förskjutningen återställs inte i förväg och fjädern schemaläggs inte till ett senare tillfälle. På så sätt förblir de registrerade ändringarna under dragningen och återgångsanimeringen kopplade genom samma tillståndsegenskap.
Varför hålla isär tillstånd för dragning, position och återgång?
De tre egenskaperna besvarar olika frågor: offset lagrar den begärda förskjutningen, isDragging håller reda på en aktiv gest och isReturning registrerar exempelappens återgångsfas. Vyn som äger rutan lagrar dessa värden lokalt. Apple rekommenderar att tillstånd som ägs av en vy deklareras som privat och att bindningar delas endast när en annan vy behöver ändra tillståndet. Dokumentation om State
Omslagssyntaxen har också ett särskilt syfte. Swift-egenskapomslag hanterar åtkomst till ett omslutet värde och kan exponera ett projicerat värde med dollarteckenprefixet. Här tillhandahåller $isDragging projektionen för gesttillstånd som används av updating; det är inte en andra, separat flagga som underhålls oberoende. The Swift Programming Language: Egenskaper
Avbrytning måste hanteras separat eftersom onEnded körs endast när en gest slutförs utan avbrott. Apple rekommenderar @GestureState för tillstånd som måste återställas oavsett hur en gest avslutas. Exemplet observerar återställningen och återställer den syntetiska rutan om en vanlig återgång inte redan pågår. Den här reservlösningen flyttar rutan direkt till utgångsläget; den utger sig inte för att ha en slutlig släpphastighet för en avbruten gest. Gesters slutförandebeteende, GestureState
Exemplet accepterar också medvetet en ny dragning först när återgångsanimeringen är klar. När modellens förskjutning ställs in på noll startar animeringen, så noll är redan den begärda destinationen medan rutan fortfarande synligt rör sig. Om begränsningen bara togs bort skulle det krävas en utformning av hur en ny dragning tar över från den position som för närvarande visas.
allowsHitTesting(!isReturning) styr om rutan deltar i träfftestningen. Slutförandekriteriet .removed tar bort begränsningen när hela animeringen är klar. Då slipper man gissa en fördröjning utifrån fjäderns varaktighetsparameter. Styrning av träfftestning, Slutförande när animeringen tas bort
Hur justerar och kontrollerar du återgången?
Börja med en fast destination och fasta fjäderinställningar. Ändra hur du släpper rutan innan du ändrar animeringsparametrarna. Då kan du skilja hastighetsöverföringen från justeringen av fjädern.
Använd följande kontroller i fönstret. De är en diagnostisk sekvens som härletts från den dokumenterade överföringen, inte uppmätta resultat från det här exemplet.
| Kontroll | Åtgärd | Vad du ska titta efter |
|---|---|---|
| Släpp nästan utan rörelse | Dra bort från utgångsläget, pausa och släpp sedan. | Återgången bör börja med liten medförd rörelse. |
| Släpp i riktning utåt | Dra bort från utgångsläget och släpp medan rutan fortfarande rör sig utåt. | Fjädern bör bära rörelsen utåt vidare innan den vänder tillbaka. |
| Släpp i riktning inåt | Dra bort från utgångsläget, vänd tillbaka mot det och släpp sedan. | Återgången bör fortsätta rörelsen inåt. |
| Släpp diagonalt | Släpp medan rutan rör sig längs båda axlarna. | Kontrollera att överföringen behåller riktningen och inte beter sig som en återgång enbart horisontellt. |
| Nästa interaktion | Vänta tills återgången är klar och dra sedan igen. | Den nya dragningen bör börja från utgångsläget. |
Justera duration för tempot och bounce för karaktären. Exempelvärdena 0.45 och 0.2 är illustrativa startvärden, inte rekommenderade inställningar. Apple definierar varaktighet som upplevd varaktighet och beskriver noll studs som kritiskt dämpad; positiva studsvärden ger fjädrar med allt mer studs. Fjäderkonfiguration
Upplevd varaktighet är inget exakt löfte om när varje synlig rörelse tar slut. Apple skiljer den från avklingningsvaraktighet, som beror på fjäderns rörelse. Använd återanropet för slutförande för exempelappens återgångsfas i stället för att behandla 0.45 som en timer. Animera med fjädrar
Om återgången verkar ignorera rörelsen bör du först kontrollera överföringen: ändrar gesten samma förskjutning som fjädern animerar? Skriver någon annan kod över den förskjutningen före släppet? Ersätts vyn? Animerar en animationsmodifierare draguppdateringarna? De här kontrollerna följer direkt av det dokumenterade kravet på att registrera ändrade animerbara egenskaper innan de överlämnas till en animering.
När bör du läsa av hastigheten direkt eller använda AppKit?
DragGesture.Value.velocity visar den aktuella draghastigheten, och onEnded ger gestens slutvärde när den slutförs utan avbrott. Det är användbart att läsa av hastigheten när den påverkar ett beslut i appen, till exempel valet mellan två syntetiska snäppunkter. För den fasta återgångsdestinationen i det här exemplet sköter den automatiska registreringen redan överföringen till animeringen. Draghastighet, Gestens slutvärde
Om du väljer ett uttryckligt API för fjäderns initialhastighet bör du kontrollera hur hastigheten representeras innan du skickar in ett gestmått. I Apples demonstration av geststyrd animering konverteras hastigheten till enhetshastighet genom att den divideras med den återstående förflyttningssträckan. Med demonstrationens exempelberäkning skulle 300 punkter per sekund över 150 punkters förflyttning bli 2 animeringslängder per sekund. Den konverteringen kräver också en genomtänkt hantering när den återstående sträckan är noll. Den automatiska SwiftUI-lösningen slipper den här beräkningen. Förbättra dina UI-animeringar och övergångar
Använd AppKit när en befintlig AppKit-vy eller igenkännare som ägs av appen ska hantera interaktionen. NSPanGestureRecognizer.velocity(in:) returnerar horisontell och vertikal hastighet i punkter per sekund relativt den angivna vyn. Även om metodens Swift-signatur godtar en valfri vy säger Apple uttryckligen att argumentet inte får vara nil. AppKits panninghastighet
För macOS 26 och senare är NSGestureRecognizerRepresentable det dokumenterade sättet att integrera en AppKit-gestigenkännare i en SwiftUI-hierarki. SwiftUI hanterar dess skapande och uppdateringar och tillhandahåller stöd för koordinatkonvertering. Apple påpekar att den underliggande vyn för igenkännaren kan ha en annan geometri än SwiftUI-vyn, så använd representerbarens konverterare när du passerar den gränsen. Integrering av AppKit-gester
För rutinteraktionen låter du SwiftUI-gesten ensam hantera draguppdateringarna och låter återgångsfjädern animera den befintliga förskjutningen. När överföringen fungerar justerar du destinationen och fjäderparametrarna samtidigt som samma tillstånd och koordinatgränser behålls.
Källor
tracksVelocity — Apple Developer
Animera med fjädrar — 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
Förbättra dina UI-animeringar och övergångar — Apple Developer
velocity(in:) — Apple Developer
NSGestureRecognizerRepresentable — Apple Developer