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

如何在Swift中实现UIImage的自由裁剪功能?

iOS 原生UIImage自定义裁剪功能实现方案

你需要实现的裁剪效果如下:
示例图

以下完全基于iOS系统API实现,无需引入任何第三方库,分为UI交互层实现和核心裁剪逻辑两部分:

一、裁剪交互UI实现

  • 首先创建全屏半透明蒙层,使用CAShapeLayer实现中间镂空效果,设置fillRule = .evenOdd,绘制路径时先绘制蒙层全屏区域,再叠加中间裁剪框区域,系统会自动保留裁剪框位置的透明效果
  • 为裁剪框添加平移手势支持移动位置,添加捏合手势或四角拖动手势支持调整裁剪框大小,可根据需求设置是否固定裁剪比例
  • 底层添加UIImageView展示待裁剪原图,可根据需求添加手势支持用户缩放、拖动原图调整展示内容

二、核心裁剪逻辑实现

UIImage裁剪需要注意处理图片方向属性,否则会出现裁剪后内容旋转、错位的问题,以下是兼容图片方向的裁剪扩展:

extension UIImage {
    /// 裁剪图片到指定区域,自动适配图片原始方向
    func cropped(to rect: CGRect) -> UIImage? {
        guard let cgImage = self.cgImage else { return nil }
        let scale = self.scale
        
        // 处理图片方向转换
        var transform = CGAffineTransform.identity
        switch self.imageOrientation {
        case .down, .downMirrored:
            transform = transform.translatedBy(x: size.width, y: size.height).rotated(by: .pi)
        case .left, .leftMirrored:
            transform = transform.translatedBy(x: size.width, y: 0).rotated(by: .pi / 2)
        case .right, .rightMirrored:
            transform = transform.translatedBy(x: 0, y: size.height).rotated(by: -.pi / 2)
        default: break
        }
        
        // 处理镜像翻转
        switch self.imageOrientation {
        case .upMirrored, .downMirrored:
            transform = transform.translatedBy(x: size.width, y: 0).scaledBy(x: -1, y: 1)
        case .leftMirrored, .rightMirrored:
            transform = transform.translatedBy(x: size.height, y: 0).scaledBy(x: -1, y: 1)
        default: break
        }
        
        // 创建裁剪上下文
        guard let context = CGContext(
            data: nil,
            width: Int(rect.width * scale),
            height: Int(rect.height * scale),
            bitsPerComponent: cgImage.bitsPerComponent,
            bytesPerRow: 0,
            space: cgImage.colorSpace ?? CGColorSpaceCreateDeviceRGB(),
            bitmapInfo: cgImage.bitmapInfo.rawValue
        ) else { return nil }
        
        // 应用坐标转换后裁剪
        context.concatenate(transform)
        context.draw(cgImage, in: CGRect(x: -rect.minX * scale, y: -rect.minY * scale, width: size.width * scale, height: size.height * scale))
        
        guard let croppedCGImage = context.makeImage() else { return nil }
        return UIImage(cgImage: croppedCGImage, scale: scale, orientation: .up)
    }
}

三、坐标转换注意事项

你在UI层拿到的裁剪框坐标是相对于UIImageView的坐标,需要先转换为图片本身的实际坐标再调用裁剪方法,避免因为UIImageView的contentMode缩放导致裁剪区域错位,以下是scaleAspectFit模式下的坐标转换示例:

/// 将UI层裁剪框坐标转换为图片本身的坐标
/// - Parameters:
///   - cropRect: 裁剪框相对于UIImageView的坐标
///   - imageView: 展示原图的UIImageView
///   - image: 待裁剪的原图
func convertCropRect(cropRect: CGRect, for imageView: UIImageView, image: UIImage) -> CGRect {
    let viewSize = imageView.bounds.size
    let imageSize = image.size
    // 计算scaleAspectFit模式下图片的实际缩放比例和偏移量
    let scale = min(viewSize.width / imageSize.width, viewSize.height / imageSize.height)
    let displaySize = CGSize(width: imageSize.width * scale, height: imageSize.height * scale)
    let offsetX = (viewSize.width - displaySize.width) / 2
    let offsetY = (viewSize.height - displaySize.height) / 2
    
    // 转换为图片内部坐标
    let actualX = (cropRect.minX - offsetX) / scale
    let actualY = (cropRect.minY - offsetY) / scale
    let actualWidth = cropRect.width / scale
    let actualHeight = cropRect.height / scale
    
    return CGRect(x: actualX, y: actualY, width: actualWidth, height: actualHeight)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 07:30:00