You need to use the animation
modifier that take a physique
closure. That is the way you separate animations into completely different “domains”. Slightly than animating a worth change, it solely animates the ViewModifier
s within the physique
which are Animatable
.
Sadly, body
just isn’t an Animatable
modifier (however scaleEffect
is, so think about using that if you happen to can), so you must make an Animatable
wrapper round it.
struct AnimatableFrameModifier: ViewModifier, Animatable {
var animatableData: CGFloat
let isWidth: Bool
init(width: CGFloat) {
self.animatableData = width
isWidth = true
}
init(peak: CGFloat) {
self.animatableData = peak
isWidth = false
}
func physique(content material: Content material) -> some View {
content material.body(width: isWidth ? animatableData : nil,
peak: isWidth ? nil : animatableData)
}
}
struct TestView: View {
@State var currentWidth: CGFloat = 10
@State var currentHeight: CGFloat = 10
var physique: some View {
VStack {
Coloration.purple
.animation(.spring(length:1.0)) {
$0.modifier(AnimatableFrameModifier(width: currentWidth))
}
.animation(.spring(length:20.0)) {
$0.modifier(AnimatableFrameModifier(peak: currentHeight))
}
}
.onAppear {
currentWidth = 100
currentHeight = 100
}
}
}
The choice that makes use of scaleEffect
, which is likely to be undesirable relying on what you might be doing.
struct TestView: View {
@State var currentWidth: CGFloat = 10
@State var currentHeight: CGFloat = 10
@State var xScale: CGFloat = 1
@State var yScale: CGFloat = 1
var physique: some View {
VStack {
Coloration.purple
.body(width: 10, peak: 10)
.animation(.spring(length:1.0)) {
$0.scaleEffect(x: xScale)
}
.animation(.spring(length:20.0)) {
$0.scaleEffect(y: yScale)
}
.body(width: currentWidth, peak: currentHeight)
}
.onAppear {
currentWidth = 100
currentHeight = 100
xScale = 10
yScale = 10
}
}
}
Earlier than iOS 17, you possibly can obtain an analogous impact by ready for a brief period of time between two withAnimation
calls.
@State var currentWidth: CGFloat = 10
@State var currentHeight: CGFloat = 10
var physique: some View {
VStack {
Coloration.purple
.body(width: currentWidth, peak: currentHeight)
}
.activity {
withAnimation(.linear(length: 1)) {
currentWidth = 100
}
// right here I used Job.yield to attend for the following body
// you can too use Job.sleep, DisptachQueue.predominant.asyncAfter, and so on
await Job.yield()
withAnimation(.linear(length: 2)) {
currentHeight = 400
}
}
}
However this solely works for animations that merge in a fascinating method. The best way spring
animations merge usually are not fascinating for this goal.