【发布时间】:2021-11-03 18:53:17
【问题描述】:
我正在开发一个通过 UIViewControllerRepresentable 使用 UIImagePickerController 的 SwiftUI 项目。在 UIViewControllerRepresentable 文件中,我添加以下行,以便我可以在 imagePickerController didFinishPickingMediaWithOptions 方法中关闭 ImagePicker。
@Environment(\.presentationMode) var presentationMode
在我的视图文件中,我还希望能够关闭视图,因此我添加了该行。
@Environment(\.presentationMode) var presentaionMode: Binding<PresentationMode>
然而,当这一行被添加到视图中时,它会导致 ActionSheet 被创建四次。一旦从 ActionSheet 中选择了一个项目,它就会被调用两次。打印语句 ShowSheetButtons 是我识别它被多次调用的方式。如果删除此 @Environment 行,则所有操作都按预期执行。发生这种情况是否有原因,我该如何解决。我希望能够在 View 和 UIViewControllerRpresentable 中使用presentationMode。
查看
struct CreateListingView: View {
// MARK: ++++++++++++++++++++++++++++++++++++++ Properties ++++++++++++++++++++++++++++++++++++++
// Dismiss the View
@Environment(\.presentationMode) var presentaionMode: Binding<PresentationMode>
// Media Picker
@State var showMediaPickerSheet = false
@State var showLibrary = false
@State var showMediaErrorAlert = false
@frozen enum MediaTypeSource {
case library
}
// MARK: ++++++++++++++++++++++++++++++++++++++ View ++++++++++++++++++++++++++++++++++++++
var body: some View {
return
Button(action: {
self.presentMediaPicker()
}, label: {
Text("Button")
})
.actionSheet(isPresented: $showMediaPickerSheet, content: {
ActionSheet(
title: Text("Add Store Picture"),
buttons: sheetButtons()
)
}) // Action Sheet
.sheet(isPresented: $showLibrary, content: {
MediaPickerPhoto(sourceType: .photoLibrary, showError: $showMediaErrorAlert) { (image, error) in
if error != nil {
print(error!)
} else {
guard let image = image else {
return
}
}
}
})
} // View
// MARK: ++++++++++++++++++++++++++++++++++++++ Methods ++++++++++++++++++++++++++++++++++++++
// Media Picker Methods
func presentMediaPicker() {
print("PresentMediaPicker1Listing")
self.showMediaPickerSheet = true
}
func sheetButtons() -> [Alert.Button] {
print("ShowSheetButtonsListing")
return UIImagePickerController.isSourceTypeAvailable(.camera) ? [
.default(Text("Choose Photo")) {
presentMediaPicker1(.library)
},
.cancel {
showMediaPickerSheet = false
}
] : [
.default(Text("Choose Photo")) {
presentMediaPicker1(.library)
},
.cancel {
showMediaPickerSheet = false
}
]
}
private func presentMediaPicker1(_ type: MediaTypeSource) {
print("PresentMediaPicker2Listing")
showMediaPickerSheet = false
switch type {
case .library:
showLibrary = true
}
}
}
UIViewControllerRepresentable
struct MediaPickerPhoto: UIViewControllerRepresentable {
typealias UIViewControllerType = UIImagePickerController
/// Presentation wrapper
@Environment(\.presentationMode) var presentationMode
/// Source type to present for
let sourceType: UIImagePickerController.SourceType
/// Binding for showing error alerts
@Binding var showError: Bool
/// Callback for media selection
let completion: (UIImage?, String?) -> Void
// MARK: - Representable
func makeUIViewController(context: UIViewControllerRepresentableContext<MediaPickerPhoto>) -> UIImagePickerController {
print("MakeUIViewController")
let picker = UIImagePickerController()
if sourceType == .camera {
picker.videoQuality = .typeMedium
}
picker.sourceType = sourceType
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<MediaPickerPhoto>) {
// no-op
}
// MARK: - Coordinator
func makeCoordinator() -> MediaCoordinatorPhoto {
return Coordinator(self)
}
}
/// Coordinator for media picker
class MediaCoordinatorPhoto: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
// AuthSession
@EnvironmentObject var authSession: AuthSession
let db = Firestore.firestore()
/// Parent picker
let parent: MediaPickerPhoto
// MARK: - Init
init(_ parent: MediaPickerPhoto) {
print("MediaCoordinatorPhoto")
self.parent = parent
}
// MARK: - Delegate
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let type = (info[.mediaType] as? String)?.lowercased() else {
return
}
if type.contains("image"), let uiImage = info[.originalImage] as? UIImage {
// We attempt to resize the image to max os 1280x1280 for perf
// If it fails, we use the original selection image
let image = uiImage.resized(maxSize: CGSize(width: 1280, height: 1280)) ?? uiImage
self.parent.completion(image, nil)
} else {
print("Invalid media type selected")
let error = "There was an error updating your user profile picture. Please try again later."
parent.completion(nil, error)
//parent.showError = true
}
parent.presentationMode.wrappedValue.dismiss()
}
}
编辑
2021-11-03 17:20:13.799510-0700 Global Store Exchange[6900:1991500] [lifecycle] [u478AD6F6-A9E2-4FE9-96D0-D310B754DD59:m(空)] [com.apple.mobileslideshow.photo-picker(1.0)] 连接插件 使用时中断。 2021-11-03 17:20:13.800173-0700 全球商店交换 [6900:1990765] viewServiceDidTerminateWithError:: 错误 域=_UIViewServiceInterfaceErrorDomain 代码=3“(空)” UserInfo={Message=服务连接中断} 2021-11-03 17:20:13.800383-0700 全球商店交换[6900:1990765] [UI] -[PUPhotoPickerHostViewController viewServiceDidTerminateWithError:] 错误错误 域=_UIViewServiceInterfaceErrorDomain 代码=3“(空)” UserInfo={Message=服务连接中断} 2021-11-03 17:20:13.800987-0700 全球商店交换 [6900:1990765] viewServiceDidTerminateWithError:: 错误 域=_UIViewServiceInterfaceErrorDomain 代码=3“(空)” UserInfo={Message=服务连接中断} 2021-11-03 17:20:13.801016-0700 全球商店交换[6900:1990765] [UI] -[PUPhotoPickerHostViewController viewServiceDidTerminateWithError:] 错误错误 域=_UIViewServiceInterfaceErrorDomain 代码=3“(空)” UserInfo={Message=服务连接中断} 2021-11-03 17:20:13.801089-0700 全球商店交换 [6900:1990765] UIImagePickerController UIViewController 创建 错误:错误域=NSCocoaErrorDomain 代码=4097“连接到 名为 com.apple.mobileslideshow.photo-picker.viewservice 的服务” UserInfo={NSDebugDescription=连接到服务命名 com.apple.mobileslideshow.photo-picker.viewservice} 2021-11-03 17:20:13.801266-0700 全球商店交换 [6900:1990765] viewServiceDidTerminateWithError:: 错误 域=_UIViewServiceInterfaceErrorDomain 代码=3“(空)” UserInfo={Message=服务连接中断} 2021-11-03 17:20:13.801313-0700 全球商店交换[6900:1990765] [UI] -[PUPhotoPickerHostViewController viewServiceDidTerminateWithError:] 错误错误 域=_UIViewServiceInterfaceErrorDomain 代码=3“(空)” UserInfo={Message=服务连接中断} 2021-11-03 17:20:13.801421-0700 全球商店交换 [6900:1990765] viewServiceDidTerminateWithError:: 错误 域=_UIViewServiceInterfaceErrorDomain 代码=3“(空)” UserInfo={Message=服务连接中断} 2021-11-03 17:20:13.801459-0700 全球商店交换[6900:1990765] [UI] -[PUPhotoPickerHostViewController viewServiceDidTerminateWithError:] 错误错误 域=_UIViewServiceInterfaceErrorDomain 代码=3“(空)” UserInfo={Message=服务连接中断}
【问题讨论】:
-
我用显示
CreateListingView的简单父视图尝试了您的代码,但无法重现您的问题。你能确定你有一个minimal reproducible example吗? -
是的,我刚刚构建了一个新应用并将 CreateListingView 添加到 ContentView 并添加了一个 MediaPickerPhoto 文件。虽然 ShowSheetButtons 在这种情况下只触发两次,但它仍然会导致问题。该问题发生在设备和模拟器上。你可以在这里找到这个简单项目的 repo。 github.com/jonthornham/EnvironmentTest2
-
好的,我正在运行代码。我确实看到
ShowSheetButtons打印了四次,这意味着ActionSheet被渲染了多次。但是,我不一定认为这是固有问题——SwiftUI 视图经常被重新创建/重新渲染。在这种情况下,一定是因为presentationMode在工作表出现时被重新计算。这会导致不必要的副作用吗? -
是的。在我正在进行的项目中,我允许用户上传多张照片作为市场列表的一部分。多次调用后,图像选择器加载空白并崩溃。相机不会发生这种情况。删除环境后,问题就消失了。
-
啊——有趣。在 GitHub 存储库中,
ContentView中的presentaionMode: Binding<PresentationMode>似乎根本没有被使用——但在你的实际应用程序中,它的存在是关键,对吧?有没有办法在示例中表示它,以便我们可以尝试找到解决方法?
标签: swiftui swiftui-environment