我应该在 SwiftUI 的 updateUIViewController(_:context:) 中调用 viewDidLoad()
Should I call viewDidLoad() inside updateUIViewController(_:context:) in SwiftUI
我创建 UIScrollView
以集成到 SwiftUI 视图中。它包含 UIHostingController
来托管 SwiftUI 视图。当我更新 UIHostingController
时,UIScrollView
不会更改其约束。我既不能滚动到顶部也不能滚动到底部。当我尝试在 updateUIViewController(_:context:)
中调用 viewDidLoad()
时,它的工作方式与我预期的一样。这是我的示例代码,
struct ContentView: View {
@State private var max = 100
var body: some View {
VStack {
Button("Add") { self.max += 2 }
ScrollableView {
ForEach(0..<self.max, id: \.self) { index in
Text("Hello \(index)")
.frame(width: UIScreen.main.bounds.width, height: 100)
.background(Color(red: Double.random(in: 0...255) / 255, green: Double.random(in: 0...255) / 255, blue: Double.random(in: 0...255) / 255))
}
}
}
}
}
class ScrollViewController<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()
override func viewDidLoad() {
self.view = UIView()
self.addChild(hostingController)
view.addSubview(scrollView)
scrollView.addSubview(hostingController.view)
scrollView.delegate = self
scrollView.scrollsToTop = true
scrollView.isScrollEnabled = true
makeConstraints()
hostingController.didMove(toParent: self)
}
func makeConstraints() {
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
hostingController.view.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
hostingController.view.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
hostingController.view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false
}
}
struct ScrollableView<Content: View>: UIViewControllerRepresentable {
var content: () -> Content
init(@ViewBuilder content: @escaping () -> Content) {
self.content = content
}
func makeUIViewController(context: Context) -> ScrollViewController<Content> {
let vc = ScrollViewController(rootView: self.content())
return vc
}
func updateUIViewController(_ viewController: ScrollViewController<Content>, context: Context) {
viewController.hostingController.rootView = self.content()
viewController.viewDidLoad()
}
}
我认为这不是一个好方法。我想知道是否有更新控制器的最佳方法。如果有人知道最好的解决方案,请与我分享。谢谢。
你是对的,我们永远不应该调用我们自己的 viewDidLoad
。
让我们使用视图调试器诊断问题。因此,例如,这里是(将 max
设置为 8
以使其易于管理):
注意宿主控制器的高度 view
是 800(因为我们有 8 个子视图,每个 100 pt)。到目前为止,还不错。
现在点击“添加”按钮并重复:
我们可以看到问题不是滚动视图,而是托管视图控制器的视图。即使现在有 10 个项目,它仍然认为托管视图控制器的视图高度为 800。
所以,我们可以调用 setNeedsUpdateConstraints
来解决问题:
func updateUIViewController(_ viewController: ScrollViewController<Content>, context: Context) {
viewController.hostingController.rootView = content()
viewController.hostingController.view.setNeedsUpdateConstraints()
}
因此:
struct ContentView: View {
@State private var max = 8
var body: some View {
GeometryReader { geometry in // don't reference `UIScreen.main.bounds` as that doesn’t work in split screen multitasking
VStack {
Button("Add") { self.max += 2 }
ScrollableView {
ForEach(0..<self.max, id: \.self) { index in
Text("Hello \(index)")
.frame(width: geometry.size.width, height: 100)
.background(Color(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1)))
}
}
}
}
}
}
class ScrollViewController<Content: View>: UIViewController {
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()
override func viewDidLoad() {
super.viewDidLoad() // you need to call `super`
// self.view = UIView() // don't set `self.view`
addChild(hostingController)
view.addSubview(scrollView)
scrollView.addSubview(hostingController.view)
// scrollView.delegate = self // you're not currently using this delegate protocol, so we probably shouldn't set the delegate
// scrollView.scrollsToTop = true // these are the default values
// scrollView.isScrollEnabled = true
makeConstraints()
hostingController.didMove(toParent: self)
}
func makeConstraints() {
NSLayoutConstraint.activate([
// constraints for scroll view w/in main view
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
// define contentSize of scroll view relative to hosting controller's view
hostingController.view.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
hostingController.view.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor)
])
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false
}
}
struct ScrollableView<Content: View>: UIViewControllerRepresentable {
var content: () -> Content
init(@ViewBuilder content: @escaping () -> Content) {
self.content = content
}
func makeUIViewController(context: Context) -> ScrollViewController<Content> {
ScrollViewController(rootView: content())
}
func updateUIViewController(_ viewController: ScrollViewController<Content>, context: Context) {
viewController.hostingController.rootView = content()
viewController.hostingController.view.setNeedsUpdateConstraints()
}
}
我创建 UIScrollView
以集成到 SwiftUI 视图中。它包含 UIHostingController
来托管 SwiftUI 视图。当我更新 UIHostingController
时,UIScrollView
不会更改其约束。我既不能滚动到顶部也不能滚动到底部。当我尝试在 updateUIViewController(_:context:)
中调用 viewDidLoad()
时,它的工作方式与我预期的一样。这是我的示例代码,
struct ContentView: View {
@State private var max = 100
var body: some View {
VStack {
Button("Add") { self.max += 2 }
ScrollableView {
ForEach(0..<self.max, id: \.self) { index in
Text("Hello \(index)")
.frame(width: UIScreen.main.bounds.width, height: 100)
.background(Color(red: Double.random(in: 0...255) / 255, green: Double.random(in: 0...255) / 255, blue: Double.random(in: 0...255) / 255))
}
}
}
}
}
class ScrollViewController<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()
override func viewDidLoad() {
self.view = UIView()
self.addChild(hostingController)
view.addSubview(scrollView)
scrollView.addSubview(hostingController.view)
scrollView.delegate = self
scrollView.scrollsToTop = true
scrollView.isScrollEnabled = true
makeConstraints()
hostingController.didMove(toParent: self)
}
func makeConstraints() {
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
hostingController.view.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
hostingController.view.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
hostingController.view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false
}
}
struct ScrollableView<Content: View>: UIViewControllerRepresentable {
var content: () -> Content
init(@ViewBuilder content: @escaping () -> Content) {
self.content = content
}
func makeUIViewController(context: Context) -> ScrollViewController<Content> {
let vc = ScrollViewController(rootView: self.content())
return vc
}
func updateUIViewController(_ viewController: ScrollViewController<Content>, context: Context) {
viewController.hostingController.rootView = self.content()
viewController.viewDidLoad()
}
}
我认为这不是一个好方法。我想知道是否有更新控制器的最佳方法。如果有人知道最好的解决方案,请与我分享。谢谢。
你是对的,我们永远不应该调用我们自己的 viewDidLoad
。
让我们使用视图调试器诊断问题。因此,例如,这里是(将 max
设置为 8
以使其易于管理):
注意宿主控制器的高度 view
是 800(因为我们有 8 个子视图,每个 100 pt)。到目前为止,还不错。
现在点击“添加”按钮并重复:
我们可以看到问题不是滚动视图,而是托管视图控制器的视图。即使现在有 10 个项目,它仍然认为托管视图控制器的视图高度为 800。
所以,我们可以调用 setNeedsUpdateConstraints
来解决问题:
func updateUIViewController(_ viewController: ScrollViewController<Content>, context: Context) {
viewController.hostingController.rootView = content()
viewController.hostingController.view.setNeedsUpdateConstraints()
}
因此:
struct ContentView: View {
@State private var max = 8
var body: some View {
GeometryReader { geometry in // don't reference `UIScreen.main.bounds` as that doesn’t work in split screen multitasking
VStack {
Button("Add") { self.max += 2 }
ScrollableView {
ForEach(0..<self.max, id: \.self) { index in
Text("Hello \(index)")
.frame(width: geometry.size.width, height: 100)
.background(Color(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1)))
}
}
}
}
}
}
class ScrollViewController<Content: View>: UIViewController {
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()
override func viewDidLoad() {
super.viewDidLoad() // you need to call `super`
// self.view = UIView() // don't set `self.view`
addChild(hostingController)
view.addSubview(scrollView)
scrollView.addSubview(hostingController.view)
// scrollView.delegate = self // you're not currently using this delegate protocol, so we probably shouldn't set the delegate
// scrollView.scrollsToTop = true // these are the default values
// scrollView.isScrollEnabled = true
makeConstraints()
hostingController.didMove(toParent: self)
}
func makeConstraints() {
NSLayoutConstraint.activate([
// constraints for scroll view w/in main view
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
// define contentSize of scroll view relative to hosting controller's view
hostingController.view.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
hostingController.view.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor)
])
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false
}
}
struct ScrollableView<Content: View>: UIViewControllerRepresentable {
var content: () -> Content
init(@ViewBuilder content: @escaping () -> Content) {
self.content = content
}
func makeUIViewController(context: Context) -> ScrollViewController<Content> {
ScrollViewController(rootView: content())
}
func updateUIViewController(_ viewController: ScrollViewController<Content>, context: Context) {
viewController.hostingController.rootView = content()
viewController.hostingController.view.setNeedsUpdateConstraints()
}
}