我有一个定制的UIViewControllerRepresentable
(与布局相关的代码如下所示)。这将尝试复制本机SwiftUI ScrollView
,除非它从底部滚动(顶部除外)。
查看层次结构
view: UIView
|
\- scrollView: UIScrollView
|
\- innerView: UIView
|
\- hostingController.view: SwiftUI hosting view
在初始化视图时,这一切都按预期工作。宿主视图使用其内容填充,并且约束确保正确设置了滚动视图的contentSize
。
但是,当宿主视图的内容更改时,hostingController.view
不会调整大小以适应其内容。
绿色:与预期的一样,滚动视图与宿主视图控制器的大小匹配。
蓝色:主体视图本身。它保持了第一次加载时的大小,并且没有像应该的那样扩展。
红色:宿主视图中的堆栈视图。在此屏幕截图中,内容被添加到堆栈中,导致堆栈展开。你可以看到结果是大小的不同。
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()
}
}
6条答案
按热度按时间8dtrkrch1#
对我来说,这个解决方案比我在这里看到的任何其他答案都要简单得多(都没有奏效),尽管我花了相当长的时间才找到它。
我所做的只是创建了
UIHostingController
的一个细子类,它在其视图上调用invalidateIntrinsicContentSize()
以响应viewDidLayoutSubviews()
与原来的问题类似,我有一个SwiftUI视图,它托管在
UIScrollView
中的UIViewController
中,它需要与滚动内容视图中的其他视图一起布局。SwiftUI视图的固有大小根据其内容和用户选择的动态文字大小而变化。在我的情况下,事情真的就是这么简单。它适用于iOS 14+(未在iOS 13上测试),其中SwiftUI内容的更改会导致新的固有大小正确地更新滚动视图中基于自动布局的UIKit布局。老实说,这不是
UIHostingController
的隐含行为,这感觉像是一个错误。ax6ht2ek2#
我在涉及
UIHostingController
和滚动视图的类似的视图层次结构中遇到了同样的问题,并找到了一个丑陋的黑客来使其工作。基本上,我添加了一个高度约束并手动更新常量:这是可怕的代码,但这是我发现的唯一能让它工作的东西。
zz2j4svz3#
这是@Rengers所说的,但想要包括我花了相当长时间才弄清楚的解决方案。
希望能节省一些时间
j7dteeu84#
我不推荐使用SelfSizingHostingController。你可以用它得到一个自动布局循环(我成功了)。
最好的解决方案是在设置内容后立即调用
invalidateIntrinsicContentSize()
。就像这样:dly7yett5#
我面临着同样的问题,所有的建议都没有对我起作用。然后,我在
SwiftUIX
项目中找到了以下类:https://github.com/SwiftUIX/SwiftUIX/blob/master/Sources/Intermodular/Helpers/UIKit/UIHostingView.swift这是完美的,除了SwiftUI动画仍然可以工作,但看起来与纯SwiftUI上下文中的不完全相同。
t3irkdon6#
IOS 16的更新答案:
您现在只需设置
yourHostingController.sizingOptions = [.intrinsicContentSize]
,它将在wiftUI视图更改(甚至内部状态更改)时自动更新/无效固有的内容大小。如果您正在嵌入弹出窗口,请查看其他大小调整选项(
.preferredContentSize
)以正确调整弹出窗口的大小