Обновление переменной @EnvironmentObject для передачи данных в PageViewController в SwiftUI приводит к потере считывания между ViewControllers. - PullRequest
3 голосов
/ 19 января 2020

В моем приложении SwiftUI у меня в настоящее время есть PageViewController, реализованный с использованием UIKit. Он следует традиционной реализации SwiftUI - UIKit, описанной в Apple SwiftUI UIKit по интеграции учебники .

У меня есть данные, которые заполняют UIViewController, в массиве контроллеров, который передается в PageViewController, предоставленный переменная @Environment. На другом экране приложения вы можете выполнить действие, которое вызывает обновление объекта Environment, вызывая повторную визуализацию ViewController, который находится в PageViewController.

Эта повторная визуализация, однако, вызывает проблему поскольку ViewController заново создается с новым идентификатором, и поэтому индекс viewController не может быть найден в массиве parent.controller внутри функции ClassView Coordinator pageViewController. Это приводит к тому, что индекс по умолчанию равен nil, и отключает любое перелистывание в обновленном viewController. Я все еще могу перемещаться между viewControllers, используя точки управления страницей, но я хотел бы определить, как я могу обновить массив parent.controller, чтобы включить новый ViewController и отказаться от старого.

После нескольких часов поиска Я не смог найти способ, как я могу сбросить массив родительского контроллера с новым ViewController, заменив старый вид, который был отброшен. Ниже приведен код для PageViewController.

import SwiftUI
import UIKit

struct PageViewController: UIViewControllerRepresentable {
    var controllers: [UIViewController]
    @Binding var currentPage: Int

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIViewController(context: Context) -> UIPageViewController {
        let pageViewController = UIPageViewController(
            transitionStyle: .scroll,
            navigationOrientation: .horizontal)
        pageViewController.view.backgroundColor = UIColor.clear
        pageViewController.dataSource = context.coordinator
        pageViewController.delegate = context.coordinator
        return pageViewController
    }

    func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
        pageViewController.setViewControllers(
            [controllers[currentPage]], direction: .forward, animated: false)
    }

    class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
        var parent: PageViewController

        init(_ pageViewController: PageViewController) {
            self.parent = pageViewController
        }

        func pageViewController(
            _ pageViewController: UIPageViewController,
            viewControllerBefore viewController: UIViewController) -> UIViewController? {
            print(parent.controllers)
            print(viewController)
            guard let index = parent.controllers.firstIndex(of: viewController) else {
                return nil
            }
            if index == 0 {
                return parent.controllers.last
            }
            return parent.controllers[index - 1]
        }

        func pageViewController(
            _ pageViewController: UIPageViewController,
            viewControllerAfter viewController: UIViewController) -> UIViewController? {
            guard let index = parent.controllers.firstIndex(of: viewController) else {
                return nil
            }
            if index + 1 == parent.controllers.count {
                return parent.controllers.first
            }
            return parent.controllers[index + 1]
        }

        func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
            if completed,
                let visibleViewController = pageViewController.viewControllers?.first,
                let index = parent.controllers.firstIndex(of: visibleViewController) {
                parent.currentPage = index
            }
        }
    }
}

После обновления состояния среды, которое заполняет данные для данного ViewController, вызывая повторную визуализацию, класс-координатор может печатать массив контроллеров, которые включают в себя старые ViewController, а также новый ViewController в приведенном ниже аннотированном коде, но я пока не смог найти надежный способ обеспечить эффективную замену старого ViewController старым.

struct PageViewController: UIViewControllerRepresentable {
     ...

    class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate { 
        ...

        func pageViewController(
            _ pageViewController: UIPageViewController,
            viewControllerBefore viewController: UIViewController) -> UIViewController? {
            print(parent.controllers)
            // prints out array of ViewControllers including old ViewController that has now been 
            // discarded 
            // [<_TtGC7SwiftUI19UIHostingControllerVS_7AnyView_: 0x7fd595c93de0>, 
            // <_TtGC7SwiftUI19UIHostingControllerVS_7AnyView_: 0x7fd595c94be0>, 
            // <_TtGC7SwiftUI19UIHostingControllerVS_7AnyView_: 0x7fd595c96830>, 
            // <_TtGC7SwiftUI19UIHostingControllerVS_7AnyView_: 0x7fd595c976b0>]
            print(viewController)
            // prints out new ViewController that does not exist within the parent.controllers array
            // and hence nil is returned from the guard
            // <_TtGC7SwiftUI19UIHostingControllerVS_7AnyView_: 0x7fd593721710>
            guard let index = parent.controllers.firstIndex(of: viewController) else {
                return nil
            }
            if index == 0 {
                return parent.controllers.last
            }
            return parent.controllers[index - 1]
        }

        ...
}

Любая помощь или руководство с этим вопрос будет принята с благодарностью!

1 Ответ

5 голосов
/ 23 января 2020

Имея ту же проблему, за последние несколько часов я нашел обходной путь.

Кажется, это ошибка. Если вы посмотрите на цикл UIViewControllerRepresentable, он должен go из Init> Coordinator> makeUIViewController> updateUIViewController.

Но если страницы в PageViewController обновляются (в нашем случае). Отлично, страницы восстанавливаются SwiftUI, но PageViewController получает внутренний цикл создания ошибок, идущий от Init> updateUIViewController. Без makeUIViewController нет ни координатора. Каким-то образом создается новый UIPageViewController (как?), Что приводит к несоответствию, которое вы заметили.

Чтобы решить эту проблему и принудительно воссоздать PageViewController, добавьте .id (UUID) в код просмотра страницы, например:

struct SGPageView<Page: View>: View {
    var viewControllers: [UIHostingController<Page>]
    @Binding var currentPage:Int

    init(_ views: [Page], currentPage:Binding<Int>) {
        self.viewControllers = views.map { UIHostingController(rootView: $0) }
        self._currentPage = currentPage
    }

    var body: some View {
        PageViewController(controllers: viewControllers, currentPage: $currentPage).id(UUID())
    }
}

Это будет работать, но вы будете обратите внимание, что положение прокрутки страниц сбрасывается. Еще один баг;)

...