【问题标题】:Should I call viewDidLoad() inside updateUIViewController(_:context:) in SwiftUI我应该在 SwiftUI 中的 updateUIViewController(_:context:) 内调用 viewDidLoad()
【发布时间】:2020-09-02 12:09:26
【问题描述】:

我创建了 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()
    }
}

我认为这不是一个好方法。我想知道是否有更新控制器的最佳方法。如果有人知道最好的解决方案,请分享我。谢谢。

【问题讨论】:

    标签: uiviewcontroller swiftui viewdidload uihostingcontroller


    【解决方案1】:

    你是对的,我们永远不应该调用我们自己的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()
        }
    }
    

    【讨论】:

    • 感谢您的详细解释。
    • @Rob 任何想法,为什么在这个 ScrollableView 中显示的 SwiftUI 列表根本不显示?作为测试,我添加了List { Text("Abc") }
    • 附录(对于编辑来说太慢了):只要将列表作为内容给出,ScrollViewController 的 HostingView 和 ViewHost 的大小都是 1x1 像素。
    • 没关系,通过使用闭包参数 (var content: (CGSize) -&gt; Content) 并在 updateUIViewController 中返回 ViewController 的框架大小来修复它。现在内容可以使用给定的大小使用.frame 调整自身大小。解决了 1x1 大小的子 List 的问题。
    猜你喜欢
    • 1970-01-01
    • 2020-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多