在SwiftUI中,我应该在updateUIViewController(_:context:)中调用viewDidLoad()吗?

问题描述 投票:0回答:1

我创造 UIScrollView 将被集成到SwiftUI视图中。它包含 UIHostingController 来承载SwiftUI视图。当我更新 UIHostingController, UIScrollView 不改变其约束条件。我既不能滚动到顶部,也不能滚动到底部。当我尝试调用 viewDidLoad() 里面 updateUIViewController(_:context:),它的工作原理和我的期望一样。这是我的示例代码。

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个回答
2
投票

你是对的,我们不应该调用自己的 viewDidLoad.


让我们使用视图调试器来诊断问题。所以,例如,这里是(设置 max8 以保持其可管理性)。)

before

注意主机控制器的高度 view 是800(因为我们有8个子视图,每个100pt)。到目前为止,一切正常。

现在点 "添加 "按钮,然后重复。

enter image description here

我们可以看到,问题不在于滚动视图,而在于托管视图控制器的视图。即使现在有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()
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.