Должен ли я вызывать viewDidLoad() внутри updateUIViewController (_: context :) в SwiftUI

Я создаю 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()
    }
}

Я не думаю, что это хороший способ. Я хочу знать, есть ли лучший способ обновить контроллер. Если кто знает лучшее решение, поделитесь, пожалуйста. Спасибо.

1 ответ

Решение

Вы правы, мы никогда не должны называть свои viewDidLoad.


Давайте диагностируем проблему, используя отладчик представления. Так, например, вот он (установкаmax к 8 чтобы он оставался управляемым):

Обратите внимание на высоту хост-контроллера. viewравно 800 (потому что у нас 8 подвидов по 100 пт каждое). Все идет нормально.

Теперь нажмите кнопку "добавить" и повторите:

Мы видим, что проблема не в представлении прокрутки, а скорее в представлении контроллера представления хоста. Несмотря на то, что сейчас есть 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()
    }
}
Другие вопросы по тегам