使用 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,因此您可以使用它。