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

SwiftUI中实现按矩形区域裁剪可拖拽图片的方法

实现矩形区域内的图片裁剪功能

好的,我来帮你搞定这个裁剪需求!结合你已经实现的拖拽功能,我们需要通过坐标转换和Core Graphics来完成精确裁剪,下面是完整的解决方案:

核心思路

要完成裁剪,我们需要依次解决这几个问题:

  • 获取原始图片的真实尺寸(SwiftUI的Image是经过缩放适配的,需要拿到底层的UIImage)
  • 计算拖拽后图片在界面中的实际位置,以及裁剪框相对于原始图片的坐标比例
  • 使用Core Graphics API对原始图片进行精确裁剪

修改后的完整代码

import SwiftUI
import CoreGraphics

struct CropImage: View {
    @State private var currentPosition: CGSize = .zero
    @State private var newPosition: CGSize = .zero
    @State private var croppedImage: UIImage?
    
    // 裁剪框尺寸,抽成变量便于维护
    private let cropFrameSize = CGSize(
        width: UIScreen.screenWidth * 0.7,
        height: UIScreen.screenHeight / 5
    )
    
    var body: some View {
        VStack {
            GeometryReader { geo in
                ZStack {
                    Image("test_pic")
                        .resizable()
                        .scaledToFit()
                        .offset(x: currentPosition.width, y: currentPosition.height)
                    
                    Rectangle()
                        .fill(Color.black.opacity(0.3))
                        .frame(width: cropFrameSize.width, height: cropFrameSize.height)
                        .overlay(Rectangle().stroke(Color.white, lineWidth: 3))
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity)
            }
            .gesture(DragGesture()
                .onChanged { value in
                    currentPosition = CGSize(
                        width: value.translation.width + newPosition.width,
                        height: value.translation.height + newPosition.height
                    )
                }
                .onEnded { value in
                    currentPosition = CGSize(
                        width: value.translation.width + newPosition.width,
                        height: value.translation.height + newPosition.height
                    )
                    newPosition = currentPosition
                })
            
            Button(action: {
                cropImageFromFrame()
            }) {
                Text("Crop Image")
                    .padding(.all, 10)
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .shadow(color: .gray, radius: 1)
                    .padding(.top, 50)
            }
            
            // 预览裁剪后的图片
            if let croppedImage = croppedImage {
                Image(uiImage: croppedImage)
                    .resizable()
                    .scaledToFit()
                    .padding(.top, 20)
                    .frame(height: 200)
            }
        }
    }
    
    // 获取原始UIImage
    private func getOriginalImage() -> UIImage? {
        return UIImage(named: "test_pic")
    }
    
    // 计算裁剪区域并执行裁剪
    private func cropImageFromFrame() {
        guard let originalImage = getOriginalImage() else {
            print("无法加载原始图片")
            return
        }
        
        let originalSize = originalImage.size
        let aspectRatio = originalSize.width / originalSize.height
        let zStackSize = UIScreen.main.bounds.size
        
        // 计算图片scaledToFit后的显示尺寸
        var imageDisplaySize: CGSize
        if zStackSize.width / zStackSize.height > aspectRatio {
            imageDisplaySize = CGSize(width: zStackSize.height * aspectRatio, height: zStackSize.height)
        } else {
            imageDisplaySize = CGSize(width: zStackSize.width, height: zStackSize.width / aspectRatio)
        }
        
        // 裁剪框在ZStack中的原点(居中定位)
        let cropFrameOrigin = CGPoint(
            x: (zStackSize.width - cropFrameSize.width) / 2,
            y: (zStackSize.height - cropFrameSize.height) / 2
        )
        
        // 图片在ZStack中的原点(默认居中 + 拖拽偏移)
        let imageOrigin = CGPoint(
            x: (zStackSize.width - imageDisplaySize.width) / 2 + currentPosition.width,
            y: (zStackSize.height - imageDisplaySize.height) / 2 + currentPosition.height
        )
        
        // 转换为原始图片的相对坐标
        let relativeX = (cropFrameOrigin.x - imageOrigin.x) / imageDisplaySize.width * originalSize.width
        let relativeY = (cropFrameOrigin.y - imageOrigin.y) / imageDisplaySize.height * originalSize.height
        let relativeWidth = cropFrameSize.width / imageDisplaySize.width * originalSize.width
        let relativeHeight = cropFrameSize.height / imageDisplaySize.height * originalSize.height
        
        // 确保裁剪区域不超出图片边界
        let cropRect = CGRect(
            x: max(0, relativeX),
            y: max(0, relativeY),
            width: min(relativeWidth, originalSize.width - relativeX),
            height: min(relativeHeight, originalSize.height - relativeY)
        )
        
        croppedImage = cropImage(to: cropRect)
    }
    
    // 使用Core Graphics执行实际裁剪
    private func cropImage(to rect: CGRect) -> UIImage? {
        guard let originalImage = getOriginalImage(), let cgImage = originalImage.cgImage else {
            return nil
        }
        
        // 适配图片的scale(避免Retina屏裁剪模糊)
        let scale = originalImage.scale
        let scaledRect = CGRect(
            x: rect.origin.x * scale,
            y: rect.origin.y * scale,
            width: rect.size.width * scale,
            height: rect.size.height * scale
        )
        
        guard let croppedCGImage = cgImage.cropping(to: scaledRect) else {
            print("裁剪操作失败")
            return nil
        }
        
        return UIImage(
            cgImage: croppedCGImage,
            scale: originalImage.scale,
            orientation: originalImage.imageOrientation
        )
    }
}

// 你的自定义屏幕尺寸扩展(如果之前没有的话)
extension UIScreen {
    static let screenWidth = UIScreen.main.bounds.width
    static let screenHeight = UIScreen.main.bounds.height
}

关键细节解释

  1. 坐标转换逻辑:

    • 先算出图片在界面上的显示尺寸(因为用了scaledToFit,所以要根据屏幕比例和图片比例计算)
    • 结合拖拽偏移量,得到图片在ZStack中的实际位置
    • 将裁剪框的界面坐标转换为原始图片的相对坐标,这样才能正确定位到要裁剪的区域
  2. 边界检查:

    • 裁剪时添加了max(0, ...)和min(...)的判断,避免裁剪区域超出原始图片的范围,防止出现裁剪失败的情况
  3. Retina屏适配:

    • 考虑了图片的scale属性,将裁剪矩形转换为CGImage的像素坐标,保证裁剪后的图片在高分辨率屏幕上不会模糊
  4. 预览功能:

    • 添加了croppedImage状态变量,裁剪完成后会在按钮下方显示裁剪后的图片,方便你直观看到结果

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 10:57:48