2023 SwiftUI & UI Frameworks
WWDC23 · 30 min · SwiftUI & UI Frameworks
Explore SwiftUI animation
Explore SwiftUI’s powerful animation capabilities and find out how these features work together to produce impressive visual effects. Learn how SwiftUI refreshes the rendering of a view, determines what to animate, interpolates values over time, and propagates context for the current transaction.
Watch at developer.apple.com ↗Chapters
Code shown on screen · 12 snippets
Pet Avatar - Unanimated
struct Avatar: View {
var pet: Pet
private var selected: Bool = false
var body: some View {
Image(pet.type)
.scaleEffect(selected ? 1.5 : 1.0)
.onTapGesture {
selected.toggle()
}
}
} Pet Avatar - Animated
struct Avatar: View {
var pet: Pet
private var selected: Bool = false
var body: some View {
Image(pet.type)
.scaleEffect(selected ? 1.5 : 1.0)
.onTapGesture {
withAnimation {
selected.toggle()
}
}
}
} Pet Avatar - Explicit Animation
struct Avatar: View {
var pet: Pet
private var selected: Bool = false
var body: some View {
Image(pet.type)
.scaleEffect(selected ? 1.5 : 1.0)
.onTapGesture {
withAnimation(.bouncy) {
selected.toggle()
}
}
}
} UnitCurve Model
let curve = UnitCurve(
startControlPoint: UnitPoint(x: 0.25, y: 0.1),
endControlPoint: UnitPoint(x: 0.25, y: 1))
curve.value(at: 0.25)
curve.velocity(at: 0.25) Spring Model
let spring = Spring(duration: 1.0, bounce: 0)
spring.value(target: 1, time: 0.25)
spring.velocity(target: 1, time: 0.25) MyLinearAnimation
struct MyLinearAnimation: CustomAnimation {
var duration: TimeInterval
func animate<V: VectorArithmetic>(
value: V,
time: TimeInterval,
context: inout AnimationContext<V>
) -> V? {
if time <= duration {
value.scaled(by: time / duration)
} else {
nil // animation has finished
}
}
} MyLinearAnimation with Velocity
struct MyLinearAnimation: CustomAnimation {
var duration: TimeInterval
func animate<V: VectorArithmetic>(
value: V, time: TimeInterval, context: inout AnimationContext<V>
) -> V? {
if time <= duration {
value.scaled(by: time / duration)
} else {
nil // animation has finished
}
}
func velocity<V: VectorArithmetic>(
value: V, time: TimeInterval, context: AnimationContext<V>
) -> V? {
value.scaled(by: 1.0 / duration)
}
} Pet Avatar - Animation Modifier
struct Avatar: View {
var pet: Pet
var selected: Bool
var body: some View {
Image(pet.type)
.scaleEffect(selected ? 1.5 : 1.0)
.animation(.bouncy, value: selected)
.onTapGesture {
selected.toggle()
}
}
} Pet Avatar - Multiple Animation Modifiers
struct Avatar: View {
var pet: Pet
var selected: Bool
var body: some View {
Image(pet.type)
.shadow(radius: selected ? 12 : 8)
.animation(.smooth, value: selected)
.scaleEffect(selected ? 1.5 : 1.0)
.animation(.bouncy, value: selected)
.onTapGesture {
selected.toggle()
}
}
} Generic Avatar - Scoped Animation Modifiers
struct Avatar<Content: View>: View {
var content: Content
var selected: Bool
var body: some View {
content
.animation(.smooth) {
$0.shadow(radius: selected ? 12 : 8)
}
.animation(.bouncy) {
$0.scaleEffect(selected ? 1.5 : 1.0)
}
.onTapGesture {
selected.toggle()
}
}
} Pet Avatar - Transaction Modifier
struct Avatar: View {
var pet: Pet
var selected: Bool
var body: some View {
Image(pet.type)
.scaleEffect(selected ? 1.5 : 1.0)
.transaction(value: selected) {
$0.animation = $0.avatarTapped
? .bouncy : .smooth
}
.onTapGesture {
withTransaction(\.avatarTapped, true) {
selected.toggle()
}
}
}
}
private struct AvatarTappedKey: TransactionKey {
static let defaultValue = false
}
extension Transaction {
var avatarTapped: Bool {
get { self[AvatarTappedKey.self] }
set { self[AvatarTappedKey.self] = newValue }
}
} Generic Avatar - Scoped Transaction Modifier
struct Avatar<Content: View>: View {
var content: Content
var selected: Bool
var body: some View {
content
.transaction {
$0.animation = $0.avatarTapped
? .bouncy : .smooth
} body: {
$0.scaleEffect(selected ? 1.5 : 1.0)
}
.onTapGesture {
withTransaction(\.avatarTapped, true) {
selected.toggle()
}
}
}
}
private struct AvatarTappedKey: TransactionKey {
static let defaultValue = false
}
extension Transaction {
var avatarTapped: Bool {
get { self[AvatarTappedKey.self] }
set { self[AvatarTappedKey.self] = newValue }
}
} Related sessions
-
27 min -
34 min -
18 min -
23 min -
22 min