You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中KFImage缩放后拖拽异常、仅能中心缩放的修复咨询

问题解决:SwiftUI图片全屏缩放拖拽优化

问题背景

开发的App支持点击图片全屏展示,全屏状态下用户可缩放图片并拖拽查看不同区域,但存在两个问题:

  • 图片缩放后拖拽时会自动回正,拖拽体验差
  • 仅能对图片中心进行缩放

需要修改代码实现:缩放后平滑拖拽任意区域,且支持对图片任意位置进行缩放。

修改后的完整代码

import SwiftUI
import Kingfisher

struct ContentView: View {
    @State private var myPhoto = "https://firebasestorage.googleapis.com:443/v0/b/hustle-85b6c.appspot.com/o/messages%2F7AC5914A-6239-41CF-85EF-E1C0F25C0A84?alt=media&token=8720789b-7cdd-410c-9532-143a2bcf3f3b"
    @State private var currentScale: CGFloat = 1.0
    @State private var baseScale: CGFloat = 1.0
    @State private var currentOffset = CGSize.zero
    @State private var baseOffset = CGSize.zero
    @State private var imageSize = CGSize.zero
    @State private var containerSize = CGSize.zero

    var body: some View {
        ZStack {
            Color.black
            GeometryReader { containerGeo in
                KFImage(URL(string: myPhoto))
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .onSizeChange { size in
                        imageSize = size
                        containerSize = containerGeo.size
                    }
                    .scaleEffect(currentScale)
                    .offset(currentOffset)
                    .gesture(
                        SimultaneousGesture(
                            DragGesture()
                                .onChanged { value in
                                    // 计算拖拽偏移,基于当前缩放比例调整
                                    let newOffset = CGSize(
                                        width: baseOffset.width + value.translation.width / currentScale,
                                        height: baseOffset.height + value.translation.height / currentScale
                                    )
                                    // 限制偏移,确保图片不会超出容器过多
                                    currentOffset = clampedOffset(for: newOffset)
                                }
                                .onEnded { _ in
                                    baseOffset = currentOffset
                                    // 缩放小于1时重置位置
                                    if currentScale < 1.0 {
                                        withAnimation(.easeInOut) {
                                            currentScale = 1.0
                                            currentOffset = .zero
                                            baseOffset = .zero
                                        }
                                    }
                                },
                            MagnificationGesture()
                                .onChanged { value in
                                    let newScale = baseScale * value
                                    currentScale = max(min(newScale, 3.5), 1.0)
                                    
                                    // 计算锚点偏移,实现触摸位置缩放
                                    let anchor = value.startLocation
                                    let scaledImageSize = CGSize(
                                        width: imageSize.width * currentScale,
                                        height: imageSize.height * currentScale
                                    )
                                    let containerCenter = CGPoint(
                                        x: containerSize.width / 2,
                                        y: containerSize.height / 2
                                    )
                                    
                                    // 调整偏移,让缩放围绕触摸点进行
                                    let offsetX = baseOffset.width + (anchor.x - containerCenter.x) * (1 - currentScale / baseScale)
                                    let offsetY = baseOffset.height + (anchor.y - containerCenter.y) * (1 - currentScale / baseScale)
                                    
                                    currentOffset = clampedOffset(for: CGSize(width: offsetX, height: offsetY))
                                }
                                .onEnded { value in
                                    baseScale = currentScale
                                    baseOffset = currentOffset
                                }
                        )
                    )
            }
        }
        .ignoresSafeArea()
    }
    
    // 计算合法的偏移量,防止图片空白区域过大
    private func clampedOffset(for offset: CGSize) -> CGSize {
        let scaledWidth = imageSize.width * currentScale
        let scaledHeight = imageSize.height * currentScale
        
        let maxOffsetX = max(0, (scaledWidth - containerSize.width) / 2)
        let maxOffsetY = max(0, (scaledHeight - containerSize.height) / 2)
        
        return CGSize(
            width: max(-maxOffsetX, min(offset.width, maxOffsetX)),
            height: max(-maxOffsetY, min(offset.height, maxOffsetY))
        )
    }
}

// 扩展View获取尺寸变化
extension View {
    func onSizeChange(perform action: @escaping (CGSize) -> Void) -> some View {
        background(
            GeometryReader { proxy in
                Color.clear
                    .onAppear {
                        action(proxy.size)
                    }
                    .onChange(of: proxy.size) { newValue in
                        action(newValue)
                    }
            }
        )
    }
}

关键修改说明

  • 修复拖拽自动回正问题:移除原代码硬编码的边界限制,改用clampedOffset方法动态计算合法偏移范围,避免强行拉回图片;优化拖拽偏移计算逻辑,用baseOffset记录拖拽起始偏移,确保拖拽过程平滑无跳变。
  • 支持任意位置缩放:在MagnificationGesture中获取触摸锚点,计算锚点相对容器中心的偏移,缩放时同步调整图片偏移量,实现围绕触摸点缩放的效果;用baseScale记录缩放基准比例,保证缩放计算准确。
  • 其他体验优化:新增onSizeChange扩展统一处理尺寸获取,简化代码逻辑;设置缩放范围为1.0~3.5,缩放小于1时自动重置位置,保证基础交互一致性。

内容的提问来源于stack exchange,提问作者Ahmed Zaidan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 14:23:09