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

SwiftUI技术问题:缩放移动后如何用CGImage裁剪原图?

SwiftUI 图像缩放时同步计算可见区域起始坐标(Offset)

我尝试解决这个问题很久都没成功,现在需要帮助。我要确定屏幕上可见图像区域的起始点坐标(Offset),目前的代码在图像移动后能打印Offset,但缩放时Offset不会相应变化——比如我想从同一位置(比如左边缘)裁剪图片,但不同缩放比例下当前Offset坐标不一致,没法实现这个需求。

原代码:

struct ContentView: View {
    @State private var imageScale: CGFloat = 1.0
    @State private var previousScale: CGFloat = 0
    @State private var imageOffset: CGSize = .zero
    @State private var previousOffset: CGSize = .zero

    var body: some View {
        let dragGesture = DragGesture()
            .onChanged { value in
                self.imageOffset.width = value.translation.width + self.previousOffset.width
                self.imageOffset.height = value.translation.height + self.previousOffset.height
            }
            .onEnded { value in
                self.previousOffset = self.imageOffset

                print("Offset: \(imageOffset)")
            }

        let pinchGesture = MagnificationGesture()
            .onChanged { scale in
                imageScale = scale + previousScale
            }
            .onEnded { _ in
                previousScale = imageScale - 1
            }

        Image(systemName: "person.fill")
            .resizable()
            .scaledToFill()
        .scaleEffect(imageScale)
        .offset(imageOffset)
        .gesture(dragGesture)
        .gesture(pinchGesture)
    }
}

解决方案

核心问题是原手势逻辑没考虑缩放时的坐标变换,缩放会改变图像实际显示尺寸,需要基于缩放中心调整偏移量,同时计算相对于原始图像的可见区域坐标。

修改后的完整代码

import SwiftUI

struct ContentView: View {
    @State private var imageScale: CGFloat = 1.0
    @State private var previousScale: CGFloat = 0
    @State private var imageOffset: CGSize = .zero
    @State private var previousOffset: CGSize = .zero
    // 存储图像原始尺寸
    @State private var originalImageSize: CGSize = .zero
    // 容器尺寸(屏幕可见区域)
    @State private var containerSize: CGSize = .zero

    var body: some View {
        let dragGesture = DragGesture()
            .onChanged { value in
                imageOffset.width = value.translation.width + previousOffset.width
                imageOffset.height = value.translation.height + previousOffset.height
                // 实时计算可见区域坐标
                printVisibleOffset()
            }
            .onEnded { value in
                previousOffset = imageOffset
                printVisibleOffset()
            }

        let pinchGesture = MagnificationGesture()
            .onChanged { scale in
                let currentScale = scale + previousScale
                // 获取捏合手势的中心位置
                let pinchCenter = CGPoint(x: containerSize.width/2 + imageOffset.width, y: containerSize.height/2 + imageOffset.height)
                // 计算缩放带来的偏移补偿,避免缩放时图像偏移混乱
                let offsetAdjustmentX = (pinchCenter.x - containerSize.width/2) * (1 - currentScale/imageScale)
                let offsetAdjustmentY = (pinchCenter.y - containerSize.height/2) * (1 - currentScale/imageScale)
                
                imageScale = currentScale
                imageOffset.width += offsetAdjustmentX
                imageOffset.height += offsetAdjustmentY
                // 实时计算可见区域坐标
                printVisibleOffset()
            }
            .onEnded { _ in
                previousScale = imageScale - 1
                printVisibleOffset()
            }

        Image(systemName: "person.fill")
            .resizable()
            .scaledToFill()
            .onAppear {
                // 获取图像原始尺寸
                let image = UIImage(systemName: "person.fill")!
                originalImageSize = CGSize(width: image.size.width, height: image.size.height)
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(
                // 获取容器尺寸(屏幕可见区域)
                GeometryReader { geo in
                    Color.clear.onAppear {
                        containerSize = geo.size
                    }
                }
            )
            .scaleEffect(imageScale)
            .offset(imageOffset)
            .gesture(dragGesture)
            .gesture(pinchGesture)
    }

    // 计算并打印可见区域坐标
    private func printVisibleOffset() {
        // 图像当前显示尺寸
        let scaledImageWidth = originalImageSize.width * imageScale
        let scaledImageHeight = originalImageSize.height * imageScale
        
        // 屏幕上可见区域的左上角相对于图像左上角的偏移
        let visibleOriginX = -imageOffset.width
        let visibleOriginY = -imageOffset.height
        
        // 转换为相对于原始图像的坐标(缩放前的坐标,用于统一裁剪位置)
        let originalRelativeX = visibleOriginX / imageScale
        let originalRelativeY = visibleOriginY / imageScale
        
        print("可见区域起始坐标(屏幕显示):(\(visibleOriginX), \(visibleOriginY))")
        print("可见区域起始坐标(原始图像相对):(\(originalRelativeX), \(originalRelativeY))")
    }
}

关键逻辑说明

  1. 缩放中心补偿:捏合时根据触摸中心计算偏移调整,确保缩放围绕触摸点进行,同时修正偏移量,避免缩放后图像位置混乱。
  2. 原始图像相对坐标:将屏幕可见起始点除以缩放比例,得到原始图像上的对应坐标——不管缩放多少,同一物理裁剪位置的这个坐标是固定的,满足你固定位置裁剪的需求。
  3. 容器尺寸获取:用GeometryReader拿到图像所在容器的实际尺寸,用于计算触摸中心和可见区域边界。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 11:47:04