Home > Software design >  SwiftUI animation not continuous after view change
SwiftUI animation not continuous after view change

Time:05-15

Here is the testing code:

import SwiftUI
struct ContentView: View {
  @State private var pad: Bool = false
  @State private var showDot: Bool = true
  var body: some View {
    VStack {
      Button {showDot.toggle()} label: {Text("Toggle Show Dot")}
      Spacer().frame(height: pad ? 100 : 10)
      Circ(showDot: showDot)
      Spacer()
    }.onAppear {
      withAnimation(.linear(duration: 3).repeatForever()) {pad = true}
    }
  }
}

struct Circ: View {
  let showDot: Bool
  var body: some View {
    Circle().stroke().frame(height: 50).overlay {if showDot {Circle().frame(height: 20)}}
  }
}

It happens that after I toggle showDot, the dot circle is not on the center of the stroke circle again! How can I fix this? The Circ View is given, I can't change that view!

CodePudding user response:

Edit

If you can just hide the view, see solution 1, which is preferred. If you need to re-build the view, see solution 2.

Solution 1

Replace the if condition with a .opacity() modifier that reads 1 when showDot is true.

This way, the dot does not disappear completely, it is there but you just can't see it. You will be toggling the visibility, not the view itself.

Like this:

    @State private var pad: Bool = false
    @State private var showDot: Bool = true
    
    var body: some View {
        VStack {
            Button {
                showDot.toggle()
            } label: {
                Text("Toggle Show Dot")
            }
            
            Spacer()
                .frame(height: pad ? 100 : 10)
            
            Circle().stroke()
                .frame(height: 50)
                .overlay {
                    Circle()
                        .frame(height: 20)
                        .opacity(showDot ? 1 : 0)   // <- Here
                }
            
            Spacer()
            
        }
        .onAppear {
            withAnimation(.linear(duration: 3).repeatForever()) {pad = true}
        }
    }

Solution 2

You can replace the animation with a timer. Every time it triggers, it will move the whole view by changing the height of the Spacer().

    
    // These variables will track the position and moving direction of the dot
    @State private var pos: CGFloat = 0
    @State private var movingUp = false
    
    @State private var showDot: Bool = true
    
    // This variable will change the position
    // This is a dummy iniatialization, the .onAppear modifier sets the real timer
    @State private var timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in }
        
    var body: some View {
        VStack {
            Button {
                showDot.toggle()
            } label: {
                Text("Toggle Show Dot")
            }
            
            Spacer()
                .frame(height: pos)

            Circle().stroke()
                .frame(height: 50)
                .overlay {
                    if showDot {
                        Circle().frame(height: 20)
                    }
                }
            
            Spacer()
            
        }
        .onAppear {
            
            // The timer interval will define the speed
            timer = Timer.scheduledTimer(withTimeInterval: 0.02, repeats: true) { _ in
                moveCircle()
            }
        }
    }
    
    private func moveCircle() {
        if movingUp {
            if pos <= 0 {
                pos = 0
                movingUp = false
            } else {
                pos -= 1
            }
        } else {
            if pos >= 100 {
                pos = 100
                movingUp = true
            } else {
                pos  = 1
            }
        }
    }
  • Related