这是在 SwiftUI 中使用 PHPicker 的正确方法吗?因为我有很多泄漏

Is this the proper way to use PHPicker in SwiftUI? Because I'm getting a lot of leaks

我想弄清楚是不是我的代码导致了问题,或者我是否应该向 Apple 提交错误报告。

在一个新项目中,我有这样的代码:

ContentView()

import SwiftUI

struct ContentView: View {
    
    @State private var showingImagePicker = false
    @State private var inputImage: UIImage?
    @State private var image: Image?
    
    var body: some View {
        ZStack {
            Rectangle()
                .fill(Color.secondary)
            if image != nil {
                image?
                    .resizable()
                    .scaledToFit()
            } else {
                Text("Tap to select a picture")
                    .foregroundColor(.white)
                    .font(.headline)
            }
        }
        .onTapGesture {
            self.showingImagePicker = true
        }
        
        .sheet(isPresented: $showingImagePicker, onDismiss: loadImage){
            SystemImagePicker(image: self.$inputImage)
            
        }
    }
    func loadImage() {
        guard let inputImage = inputImage else { return }
        image = Image(uiImage: inputImage)
        
    }
}

SystemImagePicker.swift

import SwiftUI

struct SystemImagePicker: UIViewControllerRepresentable {
    
    @Environment(\.presentationMode) private var presentationMode
    
    @Binding var image: UIImage?
    
    func makeUIViewController(context: Context) -> PHPickerViewController {
        var configuration = PHPickerConfiguration()
        configuration.selectionLimit = 1
        configuration.filter = .images
        
        let picker = PHPickerViewController(configuration: configuration)
        picker.delegate = context.coordinator
        return picker
    }
    
    func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
        
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(parent: self)
    }
    
    class Coordinator: NSObject, PHPickerViewControllerDelegate {
        let parent: SystemImagePicker
        
        init(parent: SystemImagePicker) {
            self.parent = parent
        }
        
        func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
            for img in results {
                guard img.itemProvider.canLoadObject(ofClass: UIImage.self) else { return }
                img.itemProvider.loadObject(ofClass: UIImage.self) { image, error in
                    if let error = error {
                        print(error)
                        return
                    }
                    
                    guard let image = image as? UIImage else { return }
                    self.parent.image = image
                    self.parent.presentationMode.wrappedValue.dismiss()
                }
            }
        }
    }
}

但是当只选择一个图像时(根据我的代码,不选择然后“改变主意”并选择另一个不同的图像),当 运行 中的内存图 Xcode.

是我的代码,还是 Apple 上的?

尽管如此,图像选择器上的 Cancel 按钮也不起作用。因此,用户不能只关闭选择器 sheet,必须选择图像以关闭 sheet。

关于旧 UIImagePickerController 的进一步说明

以前,我将此代码用于旧 UIImagePickerController

import SwiftUI

struct ImagePicker: UIViewControllerRepresentable {

    @Environment(\.presentationMode) var presentationMode
    @Binding var image: UIImage?
    
    class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        let parent: ImagePicker
        
        init(_ parent: ImagePicker) {
           self.parent = parent
        }
        
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            if let uiImage = info[.originalImage] as? UIImage {
                parent.image = uiImage
            }
            parent.presentationMode.wrappedValue.dismiss()
        }
        
        deinit {
            print("deinit")
        }
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.delegate = context.coordinator
        return picker
    }
    
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
        
    }
}

这也会导致选择图像泄漏,但数量要少得多:

我知道你问这个问题已经一年多了,但希望这对你或其他寻找答案的人有所帮助。

我在帮助文件中使用了这段代码:

import SwiftUI
import PhotosUI

struct ImagePicker: UIViewControllerRepresentable {

let configuration: PHPickerConfiguration
@Binding var selectedImage: UIImage?
@Binding var showImagePicker: Bool

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

func makeUIViewController(context: Context) -> PHPickerViewController {
    
    let picker = PHPickerViewController(configuration: configuration)
    picker.delegate = context.coordinator
    return picker
}

func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
    
    
}
}

extension ImagePicker {

class Coordinator: NSObject, PHPickerViewControllerDelegate {
    
    private let parent: ImagePicker
    
    init(_ parent: ImagePicker) {
        
        self.parent = parent
    }
    
    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        
        picker.dismiss(animated: true) {
            
            self.parent.showImagePicker = false
        }
        
        guard let provider = results.first?.itemProvider else { return }
        
        if provider.canLoadObject(ofClass: UIImage.self) {
            
            provider.loadObject(ofClass: UIImage.self) { image, _ in
                
                self.parent.selectedImage = image as? UIImage
            }
        }
        
        parent.showImagePicker = false
    }
}
}    

这符合您的看法(我在这里设置了配置,因此我可以根据我使用选择器的目的传入自定义版本,提供了 2 个):

@State private var showImagePicker = false
@State private var selectedImage: UIImage?
@State private var profileImage: Image?

var profileConfig: PHPickerConfiguration {
    
    var config = PHPickerConfiguration()
    config.filter = .images
    config.selectionLimit = 1
    config.preferredAssetRepresentationMode = .current
    return config
}

var mediaConfig: PHPickerConfiguration {
    
    var config = PHPickerConfiguration()
    config.filter = .any(of: [.images, .videos])
    config.selectionLimit = 1
    config.preferredAssetRepresentationMode = .current
    return config
}

这在你的 body 中。您可以根据需要自定义它,但这就是我所拥有的,所以我不想尝试拼凑它:

                HStack {
                    
                    Button {
                        
                        showImagePicker.toggle()
                    } label: {
                        
                        Text("Select Photo")
                            .foregroundColor(Color("AccentColor"))
                    }
                    .sheet(isPresented: $showImagePicker) {
                        
                        loadImage()
                    } content: {
                        
                        ImagePicker(configuration: profileConfig, selectedImage: $selectedImage, showImagePicker: $showImagePicker)
                            
                    }
                }
                
                if profileImage != nil {
                    
                    profileImage?
                        .resizable()
                        .scaledToFill()
                        .frame(width: 100, height: 100)
                        .clipShape(Circle())
                        .shadow(radius: 5)
                        .overlay(Circle().stroke(Color.black, lineWidth: 2))
                }
                else {
                    
                    Image(systemName: "person.crop.circle")
                        .resizable()
                        .foregroundColor(Color("AccentColor"))
                        .frame(width: 100, height: 100)
                }

我也会给你加载图片的函数(我会resamp:

func loadImage() {
    
    guard let selectedImage = selectedImage else { return }
    profileImage = Image(uiImage: selectedImage)        
}

我也在我的表单上使用它来更新图像,如果它被更改,但你可以在你使用的任何东西上使用它 body(列表、表单等。无论需要 .onChange) :

.onChange(of: selectedImage) { _ in
            
            loadImage()
        }

我注意到在很多教程中几乎没有提到这一行,这是使取消按钮起作用的原因(我不知道是否需要关闭,但我添加了它并且它有效所以我离开了它在示例中):

picker.dismiss(animated: true)

希望我添加的所有内容对您有所帮助。它似乎没有泄漏任何东西,并让您使用取消按钮。

祝你好运!