【问题标题】:Popover crash when update value using UIViewControllerRepresentable使用 UIViewControllerRepresentable 更新值时弹出窗口崩溃
【发布时间】:2021-11-29 14:03:36
【问题描述】:

我想在 iPhone 上制作弹出框,我注意到在 iPhone 中使用 .popover 时它总是显示为工作表,但在 iPad 上它会显示为弹出框

所以我决定使用 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()
                    }
                }
}
}

}

【问题讨论】:

    标签: swift swiftui popover uipopovercontroller uiviewcontrollerrepresentable


    【解决方案1】:

    Popover.updateUIViewController 中设置断点,我想你会发现问题的。每次更新 SwiftUI 视图时都会调用updateUIViewController,这意味着当isPresented 为真并且已经显示弹出框时可能会调用它。 p>

    如果这是问题所在,那么您需要跟踪您是否已经展示了弹出框。你已经在实现UIPopoverPresentationControllerDelegate,所以你可以使用它。

    【讨论】:

    • 我尝试通过添加host.viewIfLoaded?.window == nil &amp;&amp; self.isPresented来更改updateUIViewController内部的逻辑,它解决了崩溃问题,但是每次视图更新它都会关闭
    • 如果您将其添加到您的if-else,请确保您没有遇到关闭弹出框的else。你基本上想要两个if 语句:if host.viewIfLoaded?.window == nil &amp;&amp; self.isPresented 然后显示弹出框; if self.isPresented == false 然后隐藏弹出框。
    • 目前它不会关闭,也不会崩溃,但如果我将 currentFontSize 传递给 UIViewControllerRepresentable 或有其他方法,它不会更新?
    • 我不确定您要更新什么,但请查看传递给 updateUIViewController 的 Context — 它包含所有 SwiftUI 环境值,例如字体和强调色,所以你需要的任何东西都可能在那里。如果您仍然遇到问题,我建议您使用当前代码发布一个新问题。
    • 非常感谢,我想通了,我在let host = context.coordinator.host之后添加host.rootView = content(),现在一切正常
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-08
    • 1970-01-01
    • 1970-01-01
    • 2021-02-02
    • 2023-03-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多