使用 UIViewControllerRepresentable 更新值时 Popover 崩溃
Popover crash when update value using UIViewControllerRepresentable
我想在 iPhone 上制作弹出窗口,我注意到在 iPhone 中使用 .popover
时它总是显示为 sheet ,但在 iPad 上它将显示为 popover
所以我决定使用 UIKit 版本
一切正常,直到我点击按钮更新视图
它会因这个错误而崩溃
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Application tried to present modally a view controller <_TtGC7SwiftUI19UIHostingControllerGVS_6HStackGVS_9TupleViewTGVS_6ButtonVS_4Text_S4_GS3_S4______: 0x7fd47b424df0> that is already being presented by <UIViewController: 0x7fd47b426200>.
我的代码:
struct PopoverViewModifier<PopoverContent>: ViewModifier where PopoverContent: View {
@Binding var isPresented: Bool
let onDismiss: (() -> Void)?
let content: () -> PopoverContent
func body(content: Content) -> some View {
content
.background(
Popover(
isPresented: self.$isPresented,
onDismiss: self.onDismiss,
content: self.content
)
)
}
}
extension View {
func popover<Content>(
isPresented: Binding<Bool>,
onDismiss: (() -> Void)? = nil,
content: @escaping () -> Content
) -> some View where Content: View {
ModifiedContent(
content: self,
modifier: PopoverViewModifier(
isPresented: isPresented,
onDismiss: onDismiss,
content: content
)
)
}
}
struct Popover<Content: View> : UIViewControllerRepresentable {
@Binding var isPresented: Bool
let onDismiss: (() -> Void)?
@ViewBuilder let content: () -> Content
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self, content: self.content())
}
func makeUIViewController(context: Context) -> UIViewController {
return UIViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
let host = context.coordinator.host
if self.isPresented {
host.preferredContentSize = host.sizeThatFits(in: CGSize(width: Int.max , height: Int.max))
host.modalPresentationStyle = UIModalPresentationStyle.popover
host.popoverPresentationController?.delegate = context.coordinator
host.popoverPresentationController?.sourceView = uiViewController.view
host.popoverPresentationController?.sourceRect = uiViewController.view.bounds
uiViewController.present(host, animated: true, completion: nil)
}
else {
host.dismiss(animated: true, completion: nil)
}
}
class Coordinator: NSObject, UIPopoverPresentationControllerDelegate {
let host: UIHostingController<Content>
private let parent: Popover
init(parent: Popover, content: Content) {
self.parent = parent
self.host = UIHostingController(rootView: content)
}
func presentationControllerWillDismiss(_ presentationController: UIPresentationController) {
self.parent.isPresented = false
if let onDismiss = self.parent.onDismiss {
onDismiss()
}
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
}
我如何使用它:
struct ContentView: View {
@State var openChangeFont = false
@State var currentFontSize = 0
var body: some View {
NavigationView {
Text("Test")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
Text("Popover")
.popover(isPresented: $openChangeFont, content: {
HStack {
Button(action: {
DispatchQueue.main.async {
currentFontSize += 2
}
}, label: {
Text("Increase")
})
Text("\(currentFontSize)")
Button(action: {
currentFontSize -= 2
}, label: {
Text("Decrease")
})
}
})
.onTapGesture {
openChangeFont.toggle()
}
}
}
}
}
在 Popover.updateUIViewController
中放置一个断点,我想你会发现问题的。每次更新 SwiftUI 视图时都会调用 updateUIViewController
,这意味着当 isPresented
为真时可能会调用它 并且 弹出窗口已经 正在展示。
如果这是问题所在,那么您需要跟踪您是否已经在呈现弹出窗口。您已经在实施 UIPopoverPresentationControllerDelegate
,因此您可以使用它。
我想在 iPhone 上制作弹出窗口,我注意到在 iPhone 中使用 .popover
时它总是显示为 sheet ,但在 iPad 上它将显示为 popover
所以我决定使用 UIKit 版本 一切正常,直到我点击按钮更新视图
它会因这个错误而崩溃
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Application tried to present modally a view controller <_TtGC7SwiftUI19UIHostingControllerGVS_6HStackGVS_9TupleViewTGVS_6ButtonVS_4Text_S4_GS3_S4______: 0x7fd47b424df0> that is already being presented by <UIViewController: 0x7fd47b426200>.
我的代码:
struct PopoverViewModifier<PopoverContent>: ViewModifier where PopoverContent: View {
@Binding var isPresented: Bool
let onDismiss: (() -> Void)?
let content: () -> PopoverContent
func body(content: Content) -> some View {
content
.background(
Popover(
isPresented: self.$isPresented,
onDismiss: self.onDismiss,
content: self.content
)
)
}
}
extension View {
func popover<Content>(
isPresented: Binding<Bool>,
onDismiss: (() -> Void)? = nil,
content: @escaping () -> Content
) -> some View where Content: View {
ModifiedContent(
content: self,
modifier: PopoverViewModifier(
isPresented: isPresented,
onDismiss: onDismiss,
content: content
)
)
}
}
struct Popover<Content: View> : UIViewControllerRepresentable {
@Binding var isPresented: Bool
let onDismiss: (() -> Void)?
@ViewBuilder let content: () -> Content
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self, content: self.content())
}
func makeUIViewController(context: Context) -> UIViewController {
return UIViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
let host = context.coordinator.host
if self.isPresented {
host.preferredContentSize = host.sizeThatFits(in: CGSize(width: Int.max , height: Int.max))
host.modalPresentationStyle = UIModalPresentationStyle.popover
host.popoverPresentationController?.delegate = context.coordinator
host.popoverPresentationController?.sourceView = uiViewController.view
host.popoverPresentationController?.sourceRect = uiViewController.view.bounds
uiViewController.present(host, animated: true, completion: nil)
}
else {
host.dismiss(animated: true, completion: nil)
}
}
class Coordinator: NSObject, UIPopoverPresentationControllerDelegate {
let host: UIHostingController<Content>
private let parent: Popover
init(parent: Popover, content: Content) {
self.parent = parent
self.host = UIHostingController(rootView: content)
}
func presentationControllerWillDismiss(_ presentationController: UIPresentationController) {
self.parent.isPresented = false
if let onDismiss = self.parent.onDismiss {
onDismiss()
}
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
}
我如何使用它:
struct ContentView: View {
@State var openChangeFont = false
@State var currentFontSize = 0
var body: some View {
NavigationView {
Text("Test")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
Text("Popover")
.popover(isPresented: $openChangeFont, content: {
HStack {
Button(action: {
DispatchQueue.main.async {
currentFontSize += 2
}
}, label: {
Text("Increase")
})
Text("\(currentFontSize)")
Button(action: {
currentFontSize -= 2
}, label: {
Text("Decrease")
})
}
})
.onTapGesture {
openChangeFont.toggle()
}
}
}
}
}
在 Popover.updateUIViewController
中放置一个断点,我想你会发现问题的。每次更新 SwiftUI 视图时都会调用 updateUIViewController
,这意味着当 isPresented
为真时可能会调用它 并且 弹出窗口已经 正在展示。
如果这是问题所在,那么您需要跟踪您是否已经在呈现弹出窗口。您已经在实施 UIPopoverPresentationControllerDelegate
,因此您可以使用它。