UIHostingController 应该展开以适应内容
UIHostingController should expand to fit contents
我有一个自定义 UIViewControllerRepresentable
(与布局相关的代码如下所示)。这试图复制本机 SwiftUI ScrollView
,除了它从顶部滚动以外的底部。
查看层次结构
view: UIView
|
\- scrollView: UIScrollView
|
\- innerView: UIView
|
\- hostingController.view: SwiftUI hosting view
初始化视图时,这一切都按预期工作。托管视图填充了它的内容,约束确保滚动视图的 contentSize
设置正确。
但是,当托管视图的内容发生变化时,hostingController.view
不会调整大小以适应其内容。
Green: As intended, the scroll view matches the size of the hosting view controller.
Blue: The hosting view itself. It keeps the size it had when it was first loaded, and doesn't expend as it should.
Red: A stack view within the hosting view. In this screenshot, content was been added to the stack, causing it to expand. You can see the difference in size as a result.
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()
}
}
我在涉及 UIHostingController
和滚动视图的类似视图层次结构中遇到了同样的问题,并找到了一个丑陋的 hack 来使其工作。基本上,我添加了一个高度约束并手动更新常量:
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
}
这是一段糟糕的代码,但这是我发现的唯一让它起作用的东西。
这与@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
}
}
我遇到了同样的问题,none 的建议对我有用。然后在SwiftUIX
项目中发现了如下class:https://github.com/SwiftUIX/SwiftUIX/blob/master/Sources/Intermodular/Helpers/UIKit/UIHostingView.swift
除了 SwiftUI 动画仍然有效但看起来与纯 SwiftUI 上下文中的动画不完全一样之外,这非常有效。
对我来说,这个解决方案比我在这里看到的任何其他答案(none 有效)简单得多,尽管我花了很长时间才找到它。
我所做的只是创建一个 UIHostingController
的精简子类,在其视图中调用 invalidateIntrinsicContentSize()
以响应 viewDidLayoutSubviews()
class SelfSizingHostingController<Content>: UIHostingController<Content> where Content: View {
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.view.invalidateIntrinsicContentSize()
}
}
与原始问题类似,我有一个 SwiftUI 视图,我在 UIViewController
中托管在 UIScrollView
中,需要在滚动内容视图中与其他视图一起布局. SwiftUI 视图的固有大小会根据其内容和用户选择的动态类型大小而变化。
就我而言,这真的很简单。它适用于 iOS 14+(未在 iOS 13 上测试),其中 SwiftUI 内容的更改会导致新的固有大小正确更新我在滚动视图中基于自动布局的 UIKit 布局.老实说,这不是 UIHostingController
.
的隐式行为,这感觉像是一个错误
我不推荐使用。你可以得到一个自动布局循环(我成功了)。
最好的解决方案是在设置内容后立即调用 invalidateIntrinsicContentSize()
。
喜欢这里:
hostingController.rootView = content
hostingController.view.invalidateIntrinsicContentSize()
我有一个自定义 UIViewControllerRepresentable
(与布局相关的代码如下所示)。这试图复制本机 SwiftUI ScrollView
,除了它从顶部滚动以外的底部。
查看层次结构
view: UIView
|
\- scrollView: UIScrollView
|
\- innerView: UIView
|
\- hostingController.view: SwiftUI hosting view
初始化视图时,这一切都按预期工作。托管视图填充了它的内容,约束确保滚动视图的 contentSize
设置正确。
但是,当托管视图的内容发生变化时,hostingController.view
不会调整大小以适应其内容。
Green: As intended, the scroll view matches the size of the hosting view controller.
Blue: The hosting view itself. It keeps the size it had when it was first loaded, and doesn't expend as it should.
Red: A stack view within the hosting view. In this screenshot, content was been added to the stack, causing it to expand. You can see the difference in size as a result.
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()
}
}
我在涉及 UIHostingController
和滚动视图的类似视图层次结构中遇到了同样的问题,并找到了一个丑陋的 hack 来使其工作。基本上,我添加了一个高度约束并手动更新常量:
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
}
这是一段糟糕的代码,但这是我发现的唯一让它起作用的东西。
这与@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
}
}
我遇到了同样的问题,none 的建议对我有用。然后在SwiftUIX
项目中发现了如下class:https://github.com/SwiftUIX/SwiftUIX/blob/master/Sources/Intermodular/Helpers/UIKit/UIHostingView.swift
除了 SwiftUI 动画仍然有效但看起来与纯 SwiftUI 上下文中的动画不完全一样之外,这非常有效。
对我来说,这个解决方案比我在这里看到的任何其他答案(none 有效)简单得多,尽管我花了很长时间才找到它。
我所做的只是创建一个 UIHostingController
的精简子类,在其视图中调用 invalidateIntrinsicContentSize()
以响应 viewDidLayoutSubviews()
class SelfSizingHostingController<Content>: UIHostingController<Content> where Content: View {
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.view.invalidateIntrinsicContentSize()
}
}
与原始问题类似,我有一个 SwiftUI 视图,我在 UIViewController
中托管在 UIScrollView
中,需要在滚动内容视图中与其他视图一起布局. SwiftUI 视图的固有大小会根据其内容和用户选择的动态类型大小而变化。
就我而言,这真的很简单。它适用于 iOS 14+(未在 iOS 13 上测试),其中 SwiftUI 内容的更改会导致新的固有大小正确更新我在滚动视图中基于自动布局的 UIKit 布局.老实说,这不是 UIHostingController
.
我不推荐使用
最好的解决方案是在设置内容后立即调用 invalidateIntrinsicContentSize()
。
喜欢这里:
hostingController.rootView = content
hostingController.view.invalidateIntrinsicContentSize()