You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

UIViewControllerRepresentable实现的popover仅iOS14下无法更新的兼容方案求助

解决方案

问题根因

iOS 14 与 iOS 15 在 UIHostingController 尺寸计算、视图生命周期、UIPopoverPresentationController 代理生效逻辑上存在差异,原有代码主要触发了三处iOS14兼容性问题:

  • iOS14 下 UIHostingController 的 sizeThatFits 不会自动触发根视图布局,直接调用得到的内容尺寸错误,导致popover显示异常
  • 基于 host.viewIfLoaded?.window 的弹出状态判断在iOS14下不可靠,视图加载到挂载window存在时间差,会重复触发弹出逻辑
  • 仅实现单参数的 adaptivePresentationStyle 代理方法在iOS14下部分场景不生效,会导致popover被强制转为全屏模态

兼容双版本的修改后代码

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 cocoaPopover<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)
    }

    func makeUIViewController(context: Context) -> UIViewController {
        return UIViewController()
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
        let host = context.coordinator.host
        host.rootView = content()
        
        // 弹出逻辑
        if isPresented, !context.coordinator.isPresenting {
            // 强制触发布局,保证iOS14下能拿到正确尺寸
            host.view.layoutIfNeeded()
            // 计算内容自适应尺寸
            let contentSize = host.view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
            host.preferredContentSize = contentSize
            host.modalPresentationStyle = .popover
            
            guard let popover = host.popoverPresentationController else { return }
            popover.delegate = context.coordinator
            // 保证sourceView布局完成,bounds正确
            uiViewController.view.layoutIfNeeded()
            popover.sourceView = uiViewController.view
            popover.sourceRect = uiViewController.view.bounds
            
            context.coordinator.isPresenting = true
            uiViewController.present(host, animated: true)
        } 
        // 收起逻辑
        else if !isPresented, context.coordinator.isPresenting {
            context.coordinator.isPresenting = false
            host.dismiss(animated: true)
        }
    }

    class Coordinator: NSObject, UIPopoverPresentationControllerDelegate {
        let host: UIHostingController<Content>
        private let parent: Popover
        // 新增状态标记,避免重复弹出/收起
        var isPresenting = false

        init(parent: Popover) {
            self.parent = parent
            // 初始化时不需要传入content,update阶段会更新rootView
            self.host = UIHostingController(rootView: parent.content())
        }

        func presentationControllerWillDismiss(_ presentationController: UIPresentationController) {
            parent.isPresented = false
            isPresenting = false
            parent.onDismiss?()
        }

        // 实现两个版本的代理方法,兼容iOS14
        func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
            return .none
        }
        
        func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
            return .none
        }
    }
}

核心修改点说明

  • 给Coordinator新增isPresenting状态标记,替代原有基于window的判断逻辑,避免重复触发弹出/收起
  • 计算内容尺寸前强制触发UIHostingController的视图布局,使用systemLayoutSizeFitting替代sizeThatFits保证iOS14下拿到正确的内容尺寸
  • 补充双参数的adaptivePresentationStyle代理方法,保证iOS14下popover不会被强制转为全屏模态
  • 获取sourceRect前强制布局背景视图,避免弹出位置偏移
  • 调整dismiss逻辑的判断条件,避免已收起的状态下重复调用dismiss方法

内容的提问来源于stack exchange,提问作者Basel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 23:39:04