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 }
关键细节解释
坐标转换逻辑:
- 先算出图片在界面上的显示尺寸(因为用了
scaledToFit,所以要根据屏幕比例和图片比例计算) - 结合拖拽偏移量,得到图片在ZStack中的实际位置
- 将裁剪框的界面坐标转换为原始图片的相对坐标,这样才能正确定位到要裁剪的区域
- 先算出图片在界面上的显示尺寸(因为用了
边界检查:
- 裁剪时添加了
max(0, ...)和min(...)的判断,避免裁剪区域超出原始图片的范围,防止出现裁剪失败的情况
- 裁剪时添加了
Retina屏适配:
- 考虑了图片的
scale属性,将裁剪矩形转换为CGImage的像素坐标,保证裁剪后的图片在高分辨率屏幕上不会模糊
- 考虑了图片的
预览功能:
- 添加了
croppedImage状态变量,裁剪完成后会在按钮下方显示裁剪后的图片,方便你直观看到结果
- 添加了
内容的提问来源于stack exchange,提问作者Asim Roy
相关产品推荐
相关产品推荐

