SwiftUI图片拖拽缩放实现及Sheet拖拽时图片仅垂直缩放问题排查
SwiftUI图片拖拽缩放实现及Sheet拖拽时图片仅垂直缩放问题排查
嘿,我明白你现在遇到的困扰——拖拽Sheet向下滑动时,图片虽然能垂直方向变大甚至超出边界,但水平方向却没跟着按比例缩放,看起来像是被“压扁”或者只拉伸了高度对吧?咱们来拆解下问题所在,再给出可行的修复方案。
问题根源分析
先看你现有代码里的图片处理逻辑:
AsyncImage( url: URL(string: "https://images.unsplash.com/photo-1551709645-3f16f608bb80?ixlib"), content: { image in image.resizable() .frame(height: geometryImage.size.height * 0.2 + sheetPosition) .aspectRatio(contentMode: .fit) }, placeholder: {} ) .frame(height: geometryImage.size.height * 0.2 + sheetPosition)
这里的核心问题有两个:
- 你给
AsyncImage和内部图片都设置了固定高度,再加上.aspectRatio(contentMode: .fit)的限制——这个模式会让图片保持原有宽高比,但宽度会被父容器的宽度(也就是屏幕宽度)卡死。所以当你增加高度时,图片宽度最多只能到屏幕宽度,无法继续按比例扩展,看起来就只有垂直方向在变化。 sheetPosition直接取Sheet的minY值,把它直接加到高度上的逻辑太生硬,没有和图片的宽高比例关联起来,导致缩放逻辑不连贯。
修复方案:基于缩放比例的整体缩放
我们换个思路,通过计算Sheet的拖拽偏移量得到整体缩放因子,用scaleEffect让图片按比例整体缩放,同时允许它超出父容器边界。修改后的完整代码如下:
struct SizePreferenceKey: PreferenceKey { typealias Value = CGFloat static var defaultValue: Value = 0 static func reduce(value: inout Value, nextValue: () -> Value) { value = nextValue() } } struct ContentView: View { @State private var selectedPresentationDetent: PresentationDetent = .fraction(0.6) @State private var sheetMinY: CGFloat = 0 // 记录Sheet初始位置,用于计算缩放比例 @State private var initialSheetMinY: CGFloat = 0 var body: some View { GeometryReader { screenGeometry in VStack { VStack(alignment: .center) { Spacer() Text("Hello, world!") .padding() AsyncImage( url: URL(string: "https://images.unsplash.com/photo-1551709645-3f16f608bb80?ixlib"), content: { image in image.resizable() .aspectRatio(contentMode: .fit) // 根据Sheet偏移计算的缩放因子,实现整体缩放 .scaleEffect(scaleFactor) // 允许图片超出父容器,不被裁剪 .allowsHitTesting(false) }, placeholder: {} ) // 给图片设置初始基础尺寸 .frame(height: screenGeometry.size.height * 0.2) Spacer() } .frame(height: screenGeometry.size.height * 0.5 + screenGeometry.safeAreaInsets.top) } } .sheet(isPresented: .constant(true)) { GeometryReader { sheetGeometry in Color.blue .ignoresSafeArea() .interactiveDismissDisabled() .presentationBackgroundInteraction(.enabled) .presentationDetents([ .fraction(0.6), .fraction(0.3) ], selection: $selectedPresentationDetent) .presentationDragIndicator(.visible) .preference(key: SizePreferenceKey.self, value: sheetGeometry.frame(in: .global).minY) .onAppear { // 记录Sheet初始的minY值,作为缩放计算的基准 initialSheetMinY = sheetGeometry.frame(in: .global).minY } } .onPreferenceChange(SizePreferenceKey.self) { newValue in sheetMinY = newValue } } } // 计算平滑的缩放比例:Sheet下拉越远,图片缩放越大 private var scaleFactor: CGFloat { let maxScale: CGFloat = 2.0 // 最大缩放倍数 let minScale: CGFloat = 1.0 // 初始缩放倍数 // 计算拖拽偏移的比例,映射到缩放范围 let offsetRatio = (initialSheetMinY - sheetMinY) / initialSheetMinY return minScale + (maxScale - minScale) * offsetRatio } }
关键修改点说明
- 改用
scaleEffect实现整体缩放:让图片同时按宽高比例放大,从根源解决只拉伸高度的问题。 - 平滑的缩放因子计算:通过Sheet初始位置和当前位置的差值,计算出合理的缩放比例,保证拖拽时图片缩放自然流畅。
- 移除固定高度限制:不再给图片设置动态高度,改用初始高度配合缩放因子,让图片自由扩展。
- 允许图片超出边界:不设置裁剪逻辑,同时添加
.allowsHitTesting(false)避免图片遮挡Sheet的拖拽操作。
这样调整后,拖拽Sheet向下滑动时,图片就会整体按比例放大,同时垂直方向超出边界,完全符合你的需求啦!
备注:内容来源于stack exchange,提问作者ammerzon
相关产品推荐
相关产品推荐

