这是在 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)
希望我添加的所有内容对您有所帮助。它似乎没有泄漏任何东西,并让您使用取消按钮。
祝你好运!
我想弄清楚是不是我的代码导致了问题,或者我是否应该向 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)
希望我添加的所有内容对您有所帮助。它似乎没有泄漏任何东西,并让您使用取消按钮。
祝你好运!