VStack height varying with embedded WaveFormView, how to stabilize it?

44 Views Asked by At

Trying to make a WaveFormView for the Guitar Tuner I am designing.

struct WaveFormView: View {
  @State private var phase = 0.0
  let amplitude: Double
  let frequency: Double
  let waveColor: Color
  let echoes: Double
  
  var body: some View {
    ZStack {
      ForEach(0..<5) { i in
        Wave(amplitude: amplitude, frequency: frequency, phase: self.phase)
          .stroke(waveColor.opacity((Double(i)/abs(echoes))), lineWidth: 2)
          .offset(y: -CGFloat(i) * CGFloat(echoes))
      }
      .mask {
        LinearGradient(gradient: Gradient(colors: [.clear, .white, . clear]), startPoint: .leading, endPoint: .trailing)
      }
      .onAppear {
        withAnimation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) {
          self.phase = .pi * 2
        }
      }
    }
  }
}

When I put this inside a VStack, this will force the height of the VStack to be variable. It pulses with the same period of the phase animation in the WaveFormView.

Here's the ContentView:

struct TunerView: View {
  @StateObject var controller = PitchData()
  var pitchEngine = PitchEngine()
  
  var body: some View {
    VStack {
      Spacer()
      Text(controller.pitchString)     // Note letter
        .font(.system(size: 100))
      Spacer()

      ZStack{                                // Here are the waves 
        WaveFormView(amplitude: 30,
                     frequency: controller.pitchFrequency,
                     waveColor: controller.isTuned ? .blue : .red,
                     echoes: controller.offsetCents)
        WaveFormView(amplitude: 30,
                     frequency: controller.closestOffsetFrequency,
                     waveColor: controller.isTuned ? .blue : .red,
                     echoes: 1)                  
        .frame(height: 400)
        .border(.blue)
        .onAppear { startPitchEngine() }
      } 
    }
    .border(.red)
  }

BUT.... The most baffling thing is this: if I rotate the device landscape, then back portrait, the height of the VStack becomes stabilises to the value I want (full screen). No more bug.

Have posted a short video on my GitHub, please feel free to take a look. https://github.com/Salubrejoe/Tuner/blob/main/RPReplay_Final1685114259.MOV

Thanks in advance for any help!

I thought it might be the WFView itself, but the behaviour appears only after I embed it in a Stack.

If I do that and then use the spacer to push down the view, the graph gets indeed push down, but any other content overlayed on the original Stack keeps animating.

1

There are 1 best solutions below

0
Licurgen On

From the comments, by Yrb:

"The issue is most likely starting your animation in .onAppear(). The issue with that is .onAppear() kicks off before the view is on the screen. At the time .onAppear() fires, the sizes of the views are not set, so the animation does not have the size it needs to properly animate. If I am correct, the solution is simple: don't start the animation in .onAppear or start it with a delay with DispatchQueue.main.asyncAfter(deadline:). I would test it, but you didn't post a Minimal, Reproducible Example (MRE)."