Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI - How to limit the scope of animation to only the onAppear Transition

I'm new to SwiftUI and working through some sample projects to get the hang of it and I'm getting stuck on limiting the scope of the animation I set for the .transition for an AnimationModifier so it only impacts the animation of the transition and nothing else in the view.

While the separate transitions are respected for onAppear() and another for onDisappear(). The animation in the AnimatableModifier is overriding the removal over the item from the grid even when explicitly declared

I've tried explcicitly setting the Animation to the .offset transition in both the AnimatableModifier and for the CardView in the GameView, and when I do, no animation is triggered at all:

.transition(AnyTransition.offset(CGSize.init(width: randomXLocation, height: -offset.height-50)).animation(Animation.easeInOut(duration: 1.25).delay(delay)))
                    

So, there's gotta be a way to limit the scope or explicitly declare the animation for transition or two separate Animations in the Animation Modifier, but I'm not finding any resources on how to move forward.

GameView.swift

struct GameView: View {
    @ObservedObject var viewModel: SetGameViewModel
    @State var delay: Double = 0.1
    
    var body: some View {
        GeometryReader { geometry in
            VStack {
                Grid(newItems: self.viewModel.newCards,
                    items: self.viewModel.cards.itemsAtWithIds(ids: self.viewModel.idOfCardsToDisplay)) { card in
                        CardView(card: card, bodyGeoProxy: geometry, delay: self.delay).onTapGesture {
                            self.viewModel.choose(card: card)
                            }
                    .transition(AnyTransition.offset(CGSize.init(width: randomXLocation, height: -offset.height-50)))
                    .animation(Animation.easeInOut(duration: 1.25).delay(delay))
                        .onAppear() {
                            let maxDelay: Double = Double(self.viewModel.cards.itemsAtWithIds(ids: self.viewModel.idOfCardsToDisplay).count)*0.2 + 0.2
                            if self.delay < 2.5 {
                                self.delay = self.delay + 0.2
                            } else if self.delay >= maxDelay {
                                self.delay = 0.1
                            }
                        }
                    
                }
                
                HStack{
                    Button(action: {
                        self.viewModel.dealThreeCards()
                    }) {
                        Text("Hit Me")
                    }
                    Spacer()
                    Text("Score: \(self.viewModel.score)")
                    Spacer()
                    Button(action: {
                        self.viewModel.dealThreeCards()
                    }) {
                        Text("New Game")
                    }
                }
            }
        }
        
        
        
        
        
    }
} 

GameView.swift

struct CardView: View{
    var card: SetGame<SoloSetCardContent>.Card
    var bodyGeoProxy: GeometryProxy
    var delay: Double
    
    var body: some View {
        GeometryReader { geometry in
            self.body(for: geometry)
        }
        
    }
    
    init(card: SetGame<SoloSetCardContent>.Card, bodyGeoProxy: GeometryProxy, delay: Double) {
        self.card = card
        self.bodyGeoProxy = bodyGeoProxy
        self.delay = delay
    }
    
    
    @ViewBuilder
    func body(for geometryProxy: GeometryProxy) -> some View {
        
            ZStack {
                if card.isSelected {
                    RoundedRectangle(cornerRadius: 5)
                        .fill(Color.gray)
                        .frame(width: geometryProxy.size.width-4, height: geometryProxy.size.height-4, alignment: .center)
                        .border(Color.blue, width: 2)
                        .animation(nil)
                } else {
                    RoundedRectangle(cornerRadius: 5)
                        .fill(Color.gray)
                        .frame(width: geometryProxy.size.width-4, height: geometryProxy.size.height-4, alignment: .center)
                        .border(Color.red, width: 2)
                        .animation(nil)
                }
                VStack {
                    ForEach(0..<self.card.content.deckShapes.count) { index in
                        VStack {
                            Spacer(minLength: 5)
                            ShapeView(setShape: self.card.content.deckShapes[index])
                                .frame(width: (geometryProxy.size.width-geometryProxy.size.width/5), height: geometryProxy.size.height/5, alignment: .center)
                            Spacer(minLength: 5)
                        }
                    }
                }
            }
            .deal(delay: self.delay, offset: bodyGeoProxy.size)
        }
}

Dealer.Swift - AnimatableModifier

struct Dealer: AnimatableModifier {
    
    
    @State var show: Bool = false
    var delay: Double
    var offset: CGSize
    var randomXLocation: CGFloat {
        CGFloat.random(in: -offset.width ..< offset.width)
    }
    
    func body(content: Content) -> some View {
        ZStack {
            if show {
                content
                    .transition(AnyTransition.offset(CGSize.init(width: randomXLocation, height: -offset.height-450)))
                    .animation(Animation.easeInOut(duration: 1.25).delay(delay))
                    
            }
        }
            
        .onAppear {
            withAnimation {
                self.show = true
            }
        }
        .onDisappear {
            withAnimation {
                self.show = false
            }
        }

    }

}

extension View {
    func deal(delay: Double, offset: CGSize) -> some View {
        self.modifier(Dealer(delay: delay, offset: offset))
    }
}

like image 802
got2jam Avatar asked Nov 29 '25 08:11

got2jam


1 Answers

I was able to resolve this by removing the Animation from the body content (and elsewhere) and adding to withAnimation portion of the .onAppear method in body function of the AnimationModifier

func body(content: Content) -> some View {
        ZStack {
            if show {
                content
                    .transition(.asymmetric(insertion: .offset(CGSize.init(width: randomXLocation, height: -offset.height-50)),
                                            removal: .offset(CGSize.init(width: randomXLocation, height: offset.height+50))))
            }
        }
        .onDisappear {
            withAnimation (Animation.easeInOut(duration: 1.25).delay(0))  {
                self.show = false
            }
        }
        .onAppear {
            withAnimation (Animation.easeInOut(duration: 1.25).delay(self.delay)) {
                self.show = true
            }
        }
    }
like image 135
got2jam Avatar answered Dec 02 '25 05:12

got2jam



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!