视图更改后 SwiftUI 动画不连续

SwiftUI animation not continuous after view change

测试代码如下:

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)}}
  }
}

碰巧我切换showDot后,点圈又不在笔划圈的中心了!我怎样才能解决这个问题? Circ 视图已给出,我无法更改该视图!

编辑

如果只能隐藏视图,请参考方案一,推荐。如果您需要 re-build 视图,请参见解决方案 2。

解决方案 1

if 条件替换为当 showDottrue 时读取 1 的 .opacity() 修饰符。

这样,点就不会完全消失,它就在那里,只是你看不见而已。您将切换可见性,而不是视图本身。

像这样:

    @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}
        }
    }

解决方案 2

您可以用计时器替换动画。每次触发时,它都会通过改变 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
            }
        }
    }