如何在SwiftUI中实现拖动调整HStack内相邻视图的尺寸?
SwiftUI拖动调整视图尺寸导致CPU占用100%问题解决
我想实现这样的效果:拖动中间的绿色视图,左侧视图宽度随拖动距离增加而变宽,右侧视图宽度随拖动距离增加而变窄,松开拖动后视图保持调整后的尺寸。
但我写的代码会导致CPU占用率直接拉满到100%,代码如下:
import SwiftUI struct ContentView: View { @State private var xMovement: CGFloat = 0.0 @State private var completed: Bool = false var body: some View { HStack(spacing: 20) { ReactanceView(width: 100 + xMovement, color: .blue) DraggingView(xMovement: $xMovement) .gesture(DragGesture() .onChanged { value in // 记录拖动时的X轴偏移 xMovement = value.translation.width completed = false } .onEnded { _ in // 拖动结束时重置X轴偏移(注:当前已注释) //xMovement = 0.0 completed = false } ) ReactanceView(width: 100 - xMovement, color: .blue) } .padding() } } struct ReactanceView: View { var width: CGFloat var color: Color var body: some View { Text("Reactance") .padding() .frame(width: width, height: 100) // 取消注释这行就会引发无限循环 .background(color) .foregroundColor(.white) } } struct DraggingView: View { @Binding var xMovement: CGFloat var body: some View { Text("Drag me!") .padding() .frame(width: 100, height: 100) .background(Color.green) .foregroundColor(.white) .offset(x: xMovement, y: 0) .animation(.easeInOut) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
问题原因
核心问题在于**DragGesture的translation是相对于手势起始位置的实时偏移**。当左右两侧的ReactanceView宽度变化时,整个HStack的布局会重新计算,导致中间DraggingView的位置发生变化,进而让DragGesture的起始位置不断更新,translation的值也跟着波动。这会触发xMovement反复更新,再次引发布局重算,形成无限循环,直接把CPU跑满。
解决方案
我们需要记录拖动的累计偏移量,而不是依赖实时的translation。具体做法是:
- 新增一个状态变量存储拖动开始时的基准偏移量
- 拖动开始时,把当前的
xMovement赋值给基准偏移量 - 拖动过程中,用基准偏移量加上实时的
translation.width来更新xMovement
修改后的代码如下:
import SwiftUI struct ContentView: View { @State private var xMovement: CGFloat = 0.0 @State private var baseOffset: CGFloat = 0.0 // 新增基准偏移量 var body: some View { HStack(spacing: 20) { ReactanceView(width: max(20, 100 + xMovement), color: .blue) // 限制最小宽度避免视图消失 DraggingView(xMovement: $xMovement) .gesture(DragGesture() .onChanged { value in // 用基准偏移量 + 实时拖动距离计算最终偏移 xMovement = baseOffset + value.translation.width } .onEnded { _ in // 拖动结束后更新基准偏移量,保存当前状态 baseOffset = xMovement } ) ReactanceView(width: max(20, 100 - xMovement), color: .blue) // 限制最小宽度 } .padding() } } struct ReactanceView: View { var width: CGFloat var color: Color var body: some View { Text("Reactance") .padding() .frame(width: width, height: 100) .background(color) .foregroundColor(.white) } } struct DraggingView: View { @Binding var xMovement: CGFloat var body: some View { Text("Drag me!") .padding() .frame(width: 100, height: 100) .background(Color.green) .foregroundColor(.white) .offset(x: xMovement, y: 0) .animation(.easeInOut, value: xMovement) // 仅在xMovement变化时触发动画 } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
额外优化点
- 给左右视图添加
max(20, ...)限制最小宽度,避免拖动时视图被压缩到消失 - 把
DraggingView的动画改为animation(_:value:),仅当xMovement变化时才触发动画,减少不必要的视图刷新
内容的提问来源于stack exchange,提问作者János
相关产品推荐
相关产品推荐

