嵌入WaveFormView后VStack高度波动,如何稳定其高度?
解决VStack随WaveFormView相位动画波动高度的问题
问题描述
制作吉他调音器的WaveFormView时遇到以下问题:
- 将WaveFormView嵌入VStack后,VStack高度会随WaveFormView的相位动画周期波动;
- 设备旋转为横屏再切回竖屏后,VStack高度稳定为全屏状态,波动问题消失;
- 仅在嵌入栈视图后出现该行为,单独使用WaveFormView无异常。
相关代码
WaveFormView实现
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 } } } } }
TunerView实现
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) } }
解决方案
问题根源是WaveFormView没有明确的尺寸约束,SwiftUI在相位动画过程中会不断重新计算视图边界,进而带动父VStack高度波动。旋转设备后系统重新布局时会确定稳定尺寸,因此波动消失。可通过以下方式解决:
1. 给所有WaveFormView添加固定尺寸约束
确保两个WaveFormView都有明确高度限制,避免布局计算随动画变化:
ZStack{ // Here are the waves WaveFormView(amplitude: 30, frequency: controller.pitchFrequency, waveColor: controller.isTuned ? .blue : .red, echoes: controller.offsetCents) .frame(height: 400) // 与第二个WaveFormView保持一致高度 WaveFormView(amplitude: 30, frequency: controller.closestOffsetFrequency, waveColor: controller.isTuned ? .blue : .red, echoes: 1) .frame(height: 400) } .frame(height: 400) // 给父ZStack固定高度,进一步稳定布局 .border(.blue)
2. 在WaveFormView内部固定尺寸
直接在WaveFormView的body中添加frame约束,确保所有实例默认有稳定尺寸:
struct WaveFormView: View { // 原有属性不变 var body: some View { ZStack { // 原有内容不变 } .frame(height: 400) // 固定高度 } }
3. 分离动画与布局计算
如果希望Wave绘制区域自适应,但避免动画影响布局,可使用GeometryReader固定绘制范围,让相位动画仅影响路径绘制:
struct Wave: Shape { var amplitude: Double var frequency: Double var phase: Double // 自定义animatableData,仅让phase参与动画 var animatableData: Double { get { phase } set { phase = newValue } } func path(in rect: CGRect) -> Path { var path = Path() let width = rect.width let height = rect.height let midY = height / 2 path.move(to: CGPoint(x: 0, y: midY + sin(phase) * amplitude)) for x in stride(from: 0, to: width, by: 1) { let progress = Double(x) / width let angle = progress * frequency * .pi * 2 + phase let y = midY + sin(angle) * amplitude path.addLine(to: CGPoint(x: x, y: y)) } return path } }
修改后Wave的绘制始终限制在给定rect范围内,不会因动画导致视图尺寸变化,从根源解决VStack高度波动问题。
内容的提问来源于stack exchange,提问作者Licurgen
相关产品推荐
相关产品推荐

