13.7 C
New York
Tuesday, April 1, 2025

ios – Animate a peak and width change individually in a SwiftUI view


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 ViewModifiers 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.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles