Это правильный способ использования PHPicker в SwiftUI? Потому что у меня много утечек

Я пытаюсь выяснить, вызывает ли проблема мой код или мне следует отправить отчет об ошибке в Apple.

В новом проекте у меня есть этот код:

КонтентВью()

      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кнопка на картинке тоже не работает. Таким образом, пользователь не может просто закрыть лист выбора, ДОЛЖНО быть выбрано изображение, чтобы закрыть лист.

Дальнейшее примечание о старом 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>) {
        
    }
}

Это также приводит к утечкам при выборе изображения, но их гораздо меньше:

1 ответ

Я знаю, что прошло больше года с тех пор, как вы задали этот вопрос, но, надеюсь, это поможет вам или кому-то еще найти ответ.

Я использовал этот код во вспомогательном файле:

      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
}

Это входит в ваше тело. Вы можете настроить его так, как хотите, но это то, что у меня есть, поэтому я не хотел пытаться разобрать его по кусочкам:

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

Также я дам вам функцию для загрузки изображения (буду ресампировать:

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

Я также использовал это в своей форме для обновления изображения, если оно было изменено, но вы можете использовать его для всего, что вы используете для своего тела (список, форма и т. д. Что бы ни требовалось .onChange):

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

Я заметил, что во многих учебниках практически не упоминается эта строка, которая делает функцию кнопки отмены (я не знаю, необходимо ли закрытие, но я добавил его, и это сработало, поэтому я оставил его в примере) :

      picker.dismiss(animated: true)

Надеюсь, я добавил все, чтобы помочь вам. Кажется, ничего не протекает, и вы можете использовать кнопку отмены.

Удачи!

Другие вопросы по тегам