SwiftUI中如何用MagnificationGesture动态修改LazyVGrid的GridItems
实现苹果相册式缩放手势切换网格列数(1/3/5列)
核心修正思路
当前代码直接将手势缩放倍数转为列数,不符合苹果相册的交互逻辑——苹果相册是先通过缩放手势改变视觉大小,达到阈值后切换列数,同时图片大小恢复对应列的标准尺寸。需要重构手势逻辑,通过阈值触发列数切换,同时处理缩放状态的重置与过渡动画。
完整实现代码
import SwiftUI struct ContentView: View { let colors: [Color] = [.red, .purple, .yellow, .green, .blue, .mint, .orange] // 当前列数,仅允许1/3/5三种状态 @State private var columnCount = 1 // 对应当前列数的基础缩放比例 @State private var baseScale: CGFloat = 1.0 // 手势临时缩放比例,手势结束后自动重置为1 @GestureState private var gestureScale: CGFloat = 1.0 // 列数切换阈值:放大/缩小设置不同阈值避免频繁切换 private let scaleThresholds = [ 1: (min: 0, max: 1.4), 3: (min: 1.6, max: 3.2), 5: (min: 3.4, max: CGFloat.infinity) ] var body: some View { NavigationView { ScrollView { LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: columnCount)) { ForEach(1..<101) { number in colors[number % colors.count] .overlay(Text("\(number)").font(.title2.bold()).foregroundColor(.white)) .aspectRatio(1, contentMode: .fill) // 保持正方形布局,贴合相册样式 .clipped() } } .scaleEffect(baseScale * gestureScale) .gesture(magnificationGesture) .navigationTitle("🧨 相册网格") .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button { toggleColumnCount() } label: { Image(systemName: "square.grid.3x3") .font(.title2) .foregroundColor(.primary) } } } } } } // 缩放手势处理 private var magnificationGesture: some Gesture { MagnificationGesture() .updating($gestureScale) { currentMagnification, gestureScale, transaction in gestureScale = currentMagnification } .onEnded { finalMagnification in let totalScale = baseScale * finalMagnification updateColumnCount(from: totalScale) } } // 根据总缩放值更新列数 private func updateColumnCount(from totalScale: CGFloat) { var targetColumn = columnCount // 按阈值区间确定目标列数 switch totalScale { case ..<scaleThresholds[3]!.min: targetColumn = 1 case scaleThresholds[3]!.min..<scaleThresholds[5]!.min: targetColumn = 3 case scaleThresholds[5]!.min...: targetColumn = 5 default: break } // 列数变化时,重置基础缩放并添加过渡动画 if targetColumn != columnCount { withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) { columnCount = targetColumn baseScale = 1.0 // 切换列数后恢复标准尺寸 } } else { // 列数不变,限制缩放比例在当前列的阈值范围内 let threshold = scaleThresholds[targetColumn]! baseScale = min(max(totalScale, threshold.min), threshold.max) } } // 工具栏按钮循环切换列数 private func toggleColumnCount() { withAnimation(.spring(response: 0.8)) { switch columnCount { case 1: columnCount = 3 case 3: columnCount = 5 case 5: columnCount = 1 default: columnCount = 1 } baseScale = 1.0 } } }
关键改动说明
- 状态拆分:将缩放拆分为
baseScale(列对应的标准缩放)和gestureScale(手势临时缩放),用GestureState自动管理手势结束后的临时缩放重置,避免状态混乱。 - 阈值防抖动:为每列设置缩放阈值区间,放大/缩小阈值留间隔(如1→3用1.6,3→1用1.4),防止手势抖动导致列数频繁切换。
- 相册布局适配:添加
.aspectRatio(1, contentMode: .fill)保持图片正方形,贴合苹果相册视觉效果。 - 动画优化:切换列数用弹簧动画还原流畅过渡,手势缩放实时响应,切换后自动重置到标准尺寸。
- 工具栏逻辑修正:按钮按1→3→5→1顺序循环切换,同时重置缩放比例。
内容的提问来源于stack exchange,提问作者ViOS
相关产品推荐
相关产品推荐

