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

SwiftUI图片编辑器使用Mantis库相机选图时因内存问题崩溃

iOS SwiftUI图片编辑器相机选图崩溃(内存不足)

我正在开发一款基于SwiftUI的iOS图片编辑器,使用Mantis库实现图片裁剪功能。目前遇到一个问题:从系统图库选择图片时所有功能正常,但通过相机拍摄选择图片后,应用会直接崩溃,报错信息如下:

Message from debugger: Terminated due to memory issue

以下是我的代码:

HomePage 视图

import SwiftUI
import Mantis

struct HomePage: View {
    
    @State var bgImage :UIImage?
    @State var camIsClicked = false
    @State private var isImagePickerDisplay = false
    @State var sourceType: UIImagePickerController.SourceType?
    @State var showCropper = false
    
    @State var gotoImageEdit = false
    @State private var cropShapeType: Mantis.CropShapeType = .rect
    @State private var presetFixedRatioType: Mantis.PresetFixedRatioType = .canUseMultiplePresetFixedRatio()
    
    var camAlertView: some View{
        VStack {
            VStack{
                VStack(spacing: Constants.device == .pad ? 20:10){
                    Text("选择来源!")
                        .foregroundColor(Color.red)
                        .fontWeight(.bold)
                        .font(Constants.device == .pad ? .largeTitle:.title2)
                    Divider()
                        .frame(width: Constants.width*0.65 , height: Constants.device == .pad ? 3.5:2)
                        .background(Color.red)
                }
                .padding(.top)
                Spacer()
                Button {
                    sourceType = .photoLibrary
                    isImagePickerDisplay.toggle()
                } label: {
                    Text("图库")
                        .fontWeight(.bold)
                        .font(Constants.device == .pad ? .title:.title3)
                        .frame(width: Constants.width*0.34, height: Constants.device == .pad ? 70:40)
                        .background(Color.red)
                        .cornerRadius(Constants.device == .pad ? 35:20)
                }
                Spacer()
                Button {
                    sourceType = .camera
                    isImagePickerDisplay.toggle()
                } label: {
                    Text("相机")
                        .fontWeight(.bold)
                        .font(Constants.device == .pad ? .title:.title3)
                        .frame(width: Constants.width*0.34, height: Constants.device == .pad ? 70:40)
                        .background(Color.red)
                        .cornerRadius(Constants.device == .pad ? 35:20)
                    
                }
                Spacer()
            }
            .frame(width: Constants.width*0.65, height: Constants.height*0.39)
            .background(Color.white)
            .cornerRadius(20)
            .foregroundColor(.white)
        }
        .frame(width: Constants.width, height: Constants.height)
        .background(Color.black
                        .opacity(0.8)
                        .ignoresSafeArea()
                        .onTapGesture {
            camIsClicked = false
        })
    }
    
    var body: some View {
        ZStack{
            NavigationLink( destination: ImageEditPage(bgImage: $bgImage), isActive: $gotoImageEdit) {
                EmptyView()
            }
            VStack{
                Button {
                    camIsClicked.toggle()
                } label: {
                    Text("选择图片")
                        .padding()
                        .background(Color.green)
                        .cornerRadius(30)
                }
            }
            if camIsClicked{
                camAlertView
            }
            if isImagePickerDisplay{
                if sourceType == .photoLibrary{
                    SUImagePickerView(sourceType: .photoLibrary, image: $bgImage, isPresented: $isImagePickerDisplay, camIsClicked: $camIsClicked, bgImageIsSelected: $showCropper)
                }else{
                    SUImagePickerView(sourceType: .camera, image: $bgImage, isPresented: $isImagePickerDisplay, camIsClicked: $camIsClicked, bgImageIsSelected: $showCropper)
                }
            }
        }
        .navigationBarHidden(true)
        .fullScreenCover(isPresented: $showCropper, content: {
            ImageCropper(gotoImageEdit: $gotoImageEdit, image: $bgImage,cropShapeType: $cropShapeType,presetFixedRatioType: $presetFixedRatioType)
                .ignoresSafeArea()
        })
    }
}

struct HomePage_Previews: PreviewProvider {
    static var previews: some View {
        HomePage()
    }
}

ImageCropper 结构体(UIViewControllerRepresentable)

import SwiftUI
import Mantis

struct ImageCropper: UIViewControllerRepresentable {
    @Binding var gotoImageEdit : Bool
    @Binding var image: UIImage?
    @Binding var cropShapeType: Mantis.CropShapeType
    @Binding var presetFixedRatioType: Mantis.PresetFixedRatioType
    @Environment(\.presentationMode) var presentationMode

    class Coordinator: CropViewControllerDelegate {
        func cropViewControllerDidImageTransformed(_ cropViewController: CropViewController) {

        }

        var parent: ImageCropper

        init(_ parent: ImageCropper) {
            self.parent = parent
        }

        func cropViewControllerDidCrop(_ cropViewController: CropViewController, cropped: UIImage, transformation: Transformation, cropInfo: CropInfo) {
            parent.image = cropped
            print("transformation is \(transformation)")
            parent.gotoImageEdit = true
            parent.presentationMode.wrappedValue.dismiss()
        }

        func cropViewControllerDidCancel(_ cropViewController: CropViewController, original: UIImage) {
            parent.presentationMode.wrappedValue.dismiss()
        }

        func cropViewControllerDidFailToCrop(_ cropViewController: CropViewController, original: UIImage) {
        }

        func cropViewControllerDidBeginResize(_ cropViewController: CropViewController) {
        }

        func cropViewControllerDidEndResize(_ cropViewController: CropViewController, original: UIImage, cropInfo: CropInfo) {
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIViewController(context: Context) -> CropViewController {
        var config = Mantis.Config()
        config.cropViewConfig.cropShapeType = cropShapeType
        config.presetFixedRatioType = presetFixedRatioType
        let cropViewController = Mantis.cropViewController(image: image!,
                                                           config: config)
        cropViewController.delegate = context.coordinator
        return cropViewController
    }

    func updateUIViewController(_ uiViewController: CropViewController, context: Context) {

    }
}

问题分析与解决方案

相机拍摄的图片分辨率通常远高于图库中压缩后的图片,直接传入Mantis裁剪控制器会导致内存占用过高崩溃。可以从以下几个方面优化:

  1. 压缩相机拍摄的图片
    在相机选图完成后,先对UIImage进行压缩处理,降低图片分辨率和体积,再传入裁剪控制器。例如添加一个图片压缩方法:
func compressImage(_ image: UIImage, targetSize: CGSize = CGSize(width: 1080, height: 1920)) -> UIImage? {
    let widthRatio = targetSize.width / image.size.width
    let heightRatio = targetSize.height / image.size.height
    let scaleRatio = min(widthRatio, heightRatio)
    
    let newSize = CGSize(width: image.size.width * scaleRatio, height: image.size.height * scaleRatio)
    
    UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
    image.draw(in: CGRect(origin: .zero, size: newSize))
    let newImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    
    return newImage
}

在SUImagePickerView获取到相机图片后,调用该方法压缩,再赋值给bgImage。

  1. 避免强制解包image!
    在ImageCropper的makeUIViewController方法中,直接使用image!会存在崩溃风险(如果图片处理失败导致image为nil),应该添加可选绑定判断:
guard let image = image else {
    // 处理图片为空的情况,比如返回一个空的控制器或者提示用户
    return CropViewController()
}
let cropViewController = Mantis.cropViewController(image: image, config: config)
  1. 检查Mantis库版本
    确保使用的是Mantis库的最新版本,新版本可能已经修复了相机图片内存占用过高的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 13:05:21