简体   繁体   English

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

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

I am trying to figure out if my code is causing the problem or if I should submit a bug report to Apple.我想弄清楚是不是我的代码导致了问题,或者我是否应该向 Apple 提交错误报告。

In a new project, I have this code:在一个新项目中,我有这段代码:

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 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()
                }
            }
        }
    }
}

But when selecting just one image (as per my code, not selecting and then "changing my mind" and selecting another, different image), I get these leaks when running the memory graph in Xcode.但是当只选择一个图像时(根据我的代码,不是选择然后“改变主意”并选择另一个不同的图像),我在 Xcode 中运行 memory 图时会出现这些泄漏。

内存图泄漏视图

Is it my code, or is this on Apple?这是我的代码,还是在 Apple 上?

For what it is worth, the Cancel button on the imagepicker doesn't work either.值得一提的是,图像选择器上的“ Cancel ”按钮也不起作用。 So, the user cannot just close the picker sheet, an image MUST be selected to dismiss the sheet.因此,用户不能只关闭选取器工作表,必须选择一个图像来关闭工作表。

Further note on old UIImagePickerController关于旧 UIImagePickerController 的进一步说明

Previously, I've used this code for the old 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>) {
        
    }
}

This also result in leaks from choosing an image, but far fewer of them:这也会导致选择图像泄漏,但数量要少得多:

旧的 UIImagePickerController 内存泄漏

I know it's been over a year since you asked this question but hopefully this helps you or someone else looking for the answer.我知道你问这个问题已经一年多了,但希望这能帮助你或其他寻找答案的人。

I used this code in a helper file:我在帮助文件中使用了这段代码:

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
    }
}
}    

This goes in your view (I set up configuration here so I could pass in custom versions depending on what I'm using the picker for, 2 are provided):这符合您的看法(我在这里设置了配置,因此我可以根据我使用选择器的目的传递自定义版本,提供了 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
}

This goes in your body.这进入你的身体。 You can customize it how you want but this is what I have so I didn't want to try and piece it out:您可以根据需要对其进行自定义,但这就是我所拥有的,所以我不想尝试将其拼凑出来:

                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)
                }

I will also give you the func for loading the image (I will be resamp:我还将为您提供加载图像的功能(我将重新采样:

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

I also used this on my Form to update the image if it is changed but you can use it on whatever you're using for your body (List, Form, etc. Whatever takes.onChange):我也在我的表单上使用它来更新图像,如果它被更改,但你可以在你用于你的身体的任何东西上使用它(列表,表单等。无论什么 takes.onChange):

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

I noticed in a lot of tutorials there is little to no mention of this line which is what makes the cancel button function (I don't know if the closure is necessary but I added it and it worked so I left it in the example):我注意到在很多教程中几乎没有提到这一行,这就是取消按钮 function 的原因(我不知道是否需要关闭,但我添加了它并且它起作用了所以我把它留在了例子中) :

picker.dismiss(animated: true)

I hope I added everything to help you.希望我添加的所有内容对您有所帮助。 It doesn't appear to leak anything and gives you use of the cancel button.它似乎没有泄漏任何东西,并让您使用取消按钮。

Good luck!祝你好运!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM