UIHostingController应该扩展以适合内容

时间:2019-10-15 16:33:44

标签: ios swift swiftui ios-autolayout uihostingcontroller

我有一个自定义UIViewControllerRepresentable(下面显示了与布局相关的代码)。这会尝试复制本机SwiftUI ScrollView,除了它会从顶部滚动(顶部除外)。

视图层次结构

view: UIView
|
\- scrollView: UIScrollView
   |
   \- innerView: UIView
      |
      \- hostingController.view: SwiftUI hosting view

初始化视图时,所有这些均按预期工作。托管视图填充了其内容,并且约束条件确保滚动视图的contentSize设置正确。

但是,当托管视图的内容更改时,hostingController.view不会调整大小以适应其内容。

Screenshot of UI capture from app in Xcode. Shows contents of hosting controller expanding behind the bounds of the hosting view itself, without properly resizing.

  

绿色:按预期,滚动视图与托管视图控制器的大小匹配。

     

蓝色:托管视图本身。它保持了第一次加载时的大小,并且不会像预期的那样消耗。

     

红色:托管视图中的堆栈视图。在此屏幕快照中,内容已添加到堆栈中,导致其扩展。您可以看到大小上的差异。

UIHostingController(蓝色)应展开以适合其内容(红色)。

滚动视图的内容大小未明确设置 ,因为这是通过自动布局处理的。

约束代码如下所示(如果有帮助的话)。

class UIBottomScrollViewController<Content: View>: UIViewController, UIScrollViewDelegate {
    var hostingController: UIHostingController<Content>! = nil

    init(rootView: Content) {
        self.hostingController = UIHostingController<Content>(rootView: rootView)
        super.init(nibName: nil, bundle: nil)
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    var scrollView: UIScrollView = UIScrollView()
    var innerView = UIView()

    override func loadView() {
        self.view = UIView()
        self.addChild(hostingController)
        view.addSubview(scrollView)
        scrollView.addSubview(innerView)
        innerView.addSubview(hostingController.view)

        scrollView.delegate = self
        scrollView.scrollsToTop = true
        scrollView.isScrollEnabled = true
        scrollView.clipsToBounds = false

        scrollView.layoutMargins = .zero
        scrollView.preservesSuperviewLayoutMargins = true

        scrollView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        scrollView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true

        innerView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
        innerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        innerView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        innerView.leftAnchor.constraint(equalTo: scrollView.leftAnchor).isActive = true
        innerView.rightAnchor.constraint(equalTo: scrollView.rightAnchor).isActive = true
        innerView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true


        hostingController.view.topAnchor.constraint(equalTo: innerView.topAnchor).isActive = true
        hostingController.view.leftAnchor.constraint(equalTo: innerView.leftAnchor).isActive = true
        hostingController.view.rightAnchor.constraint(equalTo: innerView.rightAnchor).isActive = true
        hostingController.view.bottomAnchor.constraint(equalTo: innerView.bottomAnchor).isActive = true


        hostingController.view.autoresizingMask = []
        hostingController.view.layoutMargins = .zero
        hostingController.view.insetsLayoutMarginsFromSafeArea = false
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false

        scrollView.autoresizingMask = []
        scrollView.layoutMargins = .zero
        scrollView.insetsLayoutMarginsFromSafeArea = false
        scrollView.translatesAutoresizingMaskIntoConstraints = false

        innerView.autoresizingMask = []
        innerView.layoutMargins = .zero
        innerView.insetsLayoutMarginsFromSafeArea = false
        innerView.translatesAutoresizingMaskIntoConstraints = false

        hostingController.didMove(toParent: self)

        scrollView.keyboardDismissMode = .interactive
    }
}

struct BottomScrollView<Content: View>: UIViewControllerRepresentable {
    var content: () -> Content

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }

    func makeUIViewController(context: Context) -> UIBottomScrollViewController<Content> {
        let vc = UIBottomScrollViewController(rootView: self.content())
        return vc
    }
    func updateUIViewController(_ viewController: UIBottomScrollViewController<Content>, context: Context) {
        viewController.hostingController.rootView = self.content()
    }
}

3 个答案:

答案 0 :(得分:2)

我遇到了类似问题,涉及一个类似UIHostingController和滚动视图的类似视图的层次结构,并发现了一个丑陋的骇客程序来使其工作。基本上,我添加了一个高度约束并手动更新了常量:

private var heightConstraint: NSLayoutConstraint?

...

override func viewDidLoad() {
    ...


    heightConstraint = viewHost.view.heightAnchor.constraint(equalToConstant: 0)

    ...
}

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    // ?
    viewHost.view.sizeToFit()
    heightConstraint?.constant = viewHost.view.bounds.height
    heightConstraint?.isActive = true
}

这是可怕的代码,但这是我发现使它起作用的唯一原因。

答案 1 :(得分:0)

这与@Rengers所说的不符,但想包括我的解决方案,这使我花了很多时间弄清楚。

希望节省一些时间

struct SizingView<T: View>: View {
    
    let view: T
    let updateSizeHandler: ((_ size: CGSize) -> Void)
    init(view: T, updateSizeHandler: @escaping (_ size: CGSize) -> Void) {
        self.view = view
        self.updateSizeHandler = updateSizeHandler
    }
    var body: some View {
        view.background(
            GeometryReader { proxy in
                Color.clear
                    .preference(key: SizePreferenceKey.self, value: proxy.size)
            }
        )
        .onPreferenceChange(SizePreferenceKey.self) { preferences in
            updateSizeHandler(preferences)
        }

    }
    
    func size(with view: T, geometry: GeometryProxy) -> T {
        updateSizeHandler?(geometry.size)
        return view
    }
}

答案 2 :(得分:0)

我遇到了同样的问题,但没有任何建议对我有用。然后我在 SwiftUIX 项目中找到了以下类:https://github.com/SwiftUIX/SwiftUIX/blob/master/Sources/Intermodular/Helpers/UIKit/UIHostingView.swift

这很完美,除了 SwiftUI 动画仍然有效但看起来与纯 SwiftUI 上下文中的不完全相同。