Swink
Swink

Reputation: 457

Animating background gradient on view

Attempting to fool around with animating a background gradient. When animation is toggled, it would change the end radius. It is creating weird behavior whenever I incorporate the animation (gif below) where it moves the whole VStack.

I tried to put the stack in a ZStack thinking that would be a solution, but end result still the same.

enter image description here

Curious what exactly is causing the behavior

struct LandingPage: View {
    @AppStorage("signedIn") var signedIn = false
    @Environment (\.dismiss) var dismiss
    
    @StateObject var vm = DashboardLogic()
    
    @State private var animateGradient = false

    
    @ViewBuilder
    var body: some View {
        if(signedIn){
           // Text("Random Page")
        }
        else{
            NavigationView{
                VStack{
                        Image("bodybuilding-1") // << main image
                            .resizable()
                            .scaledToFit()
                            .frame(width:150, height:150)
                            //.renderingMode(.template)
                            .foregroundColor(.black)
                            .padding(.top, 200)
                    
                        Text("Welcome to Meal Journal")
                            .font(.title)
                            .padding()
                    
                    .offset(y:-25) // << adjusts title
                    
                    VStack{
                        NavigationLink(destination:dummyPage() .navigationBarHidden(true),
                       label:{
                            Text("Get Started").fontWeight(.bold)
                                .frame(minWidth: 0, maxWidth: 200)
                                .padding(10)
                                .foregroundColor(.white)
                            //draw rectange around buttons
                                .background(
                                    RoundedRectangle(cornerRadius: 20)
                                        .fill(
                                            LinearGradient(
                                                colors: [.orange, .yellow],
                                                startPoint: .topLeading,
                                                endPoint: .bottomTrailing
                                            )))
                                        })
                        
                        NavigationLink(destination: DummyPage().navigationBarHidden(true), label: {
                            Text("Login").fontWeight(.semibold)
                                .frame(minWidth:0, maxWidth: 200)
                                .padding(10)
                                .foregroundColor(.black)
                                .overlay( RoundedRectangle(cornerRadius: 25)
                                            .stroke(Color.gray, lineWidth: 3)
                                    )
                        })
                            .padding()
                    }
                    Rectangle()
                        .frame(height: 0)
                        .frame(maxWidth: .infinity, maxHeight: .infinity)
                        .ignoresSafeArea()
                }
                //.background(Color.purple)
                .background(RadialGradient(gradient: Gradient(colors: [.yellow, .green]), center: .center, startRadius: 312, endRadius: animateGradient ? 100 : 450))
                 .onAppear {
                DispatchQueue.main.async {  
                    withAnimation(.linear(duration: 2.0).repeatForever(autoreverses: true)) {
                        animateGradient.toggle()
                    }
                }
            }
                
            }
            
        }
    }
}

Upvotes: 1

Views: 324

Answers (2)

Satish
Satish

Reputation: 23

You should animate into task {} insterad of onAppear. With this animation will work as well instead of whole view. We have call this asynchronously

@State var animateGradient = false

LinearGradient(colors: [.purple, .yellow], startPoint: animateGradient ? .topTrailing : .bottomTrailing, endPoint: animateGradient ? .bottomLeading : .topTrailing)
        .ignoresSafeArea()
        .frame(width: 200, height: 200)
        .task {
            withAnimation(.linear(duration: 5.0).repeatForever(autoreverses: true)) {
                animateGradient.toggle()
            }
        }

Upvotes: 1

timbre timbre
timbre timbre

Reputation: 14004

I didn't carefully check your entire code, but one thing that may help is remembering that background is a View on its own, so you can (and often have to) apply modifiers to the background view itself. So instead of .onAppear on the parent view, add it to a background view, i.e.:

var backgroundView: some View {
        
    RadialGradient(gradient: Gradient(colors: [.yellow, .green]), center: .center, startRadius: 312, endRadius: animateGradient ? 100 : 450)
        .onAppear {
            withAnimation(.linear(duration: 2.0).repeatForever(autoreverses: true)) {
                animateGradient.toggle()
            }
        }
    }
}

and then parent view simply uses background view:

VStack {
    Image("bodybuilding-1")
    ...
}
.background(backgroundView)

Playground example:

struct AnimatedBackgroundView: View {
    
    @State private var animateGradient = false
    
    @State var scale = 1.0

    var body: some View {
        
        VStack {
            Text("Hello")
            Button("Do it", action: {})
        }
        .frame(
            maxWidth: .infinity,
            maxHeight: .infinity
        )
        .scaleEffect(scale)
        .background(backgroundView)
     }
    
    var backgroundView: some View {
        
        RadialGradient(gradient: Gradient(colors: [.yellow, .green]), center: .center, startRadius: 312, endRadius: animateGradient ? 100 : 450)
            .onAppear {
                withAnimation(.linear(duration: 2.0).repeatForever(autoreverses: true)) {
                    animateGradient.toggle()
                }
        }
    }
}

struct ContentView: View {

    var body: some View {
        
        AnimatedBackgroundView()
            .frame(
                width: 500,
                height: 600
            )
     }
}
    
PlaygroundPage.current.setLiveView(ContentView())

But like I said: I didn't investigate your entire code, so maybe some other issues prevent it from working

Upvotes: 3

Related Questions