2

So I have a ParentView, which has a NavigationLink, leading to a UIViewControllerRepresentable-conforming PageViewController.

Now that ParentView also has some subscription on some publisher. Whenever that one is fired, not only will the ParentView redraw all its content (which it should), it will also re-initialize the (already presenting) PageViewController.

That leads to stuttering/glitching, because the PageViewController is already presenting and using the controllers that are continually being resetted.

Below is the ParentView and PageViewController (without the Coordinator stuff), both is pretty vanilla. The commented guard line is a hack I tried to prevent it from updating if displayed already. It helps but it's still stuttering on every swipe.

So the question is: How can we prevent the updating of a presented ViewController-wrapped-View when its presenting View is redrawn?

struct ParentView: View {

    @Binding var something: Bool

    var body: some View {
        NavigationLink(destination: PageViewController(controllers: controllers)) {
            Text("Push me")
        }
    }
}
final class PageViewController: UIViewControllerRepresentable {

    var controllers: [UIViewController]
    private var currentPage = 0

    init(controllers: [UIViewController]) {
        self.controllers = controllers
    }

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

    func makeUIViewController(context: Context) -> UIPageViewController {
        let pageViewController = UIPageViewController(
            transitionStyle: .scroll,
            navigationOrientation: .horizontal)
        pageViewController.dataSource = context.coordinator
        pageViewController.delegate = context.coordinator

        return pageViewController
    }

    func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
        // I tried this: guard pageViewController.viewControllers!.isEmpty else { return }
        pageViewController.setViewControllers(
            [controllers[currentPage]], direction: .forward, animated: true)
    }
}
hoshy
  • 481
  • 6
  • 15
  • 1
    I'm pretty sure that PageViewController should be a struct. – Michael Salmon Sep 11 '19 at 16:29
  • Thanks for the suggestions, forgot to mention that, but tried that too, to no avail. – hoshy Sep 11 '19 at 17:34
  • SwiftUI doesn't redraw so much as reread. body is a pretty normal computed property that is read when necessary. I don't think that there is a silver bullet for your problem, each case is different. If your update is caused by an ObservedObject then you can delay the objectWillChange.send() until a better time but you need to take control of the sending to do that. – Michael Salmon Sep 12 '19 at 03:50
  • Well it calls `init()` on the `PageViewController`, that at least resets the `controllers`, which are already presented. I think I found a solution: Even though the `PageViewController` is reinitialized, it's coordinator is not. So to store the controllers (and the current page) on the coordinator keeps it from being interrupted. And the best: In `updateUIViewController` you can access both properties via `context.coordinator`. This is significantly different than Apple's implementation in their PageView Tutorial, from which my code was taken, but I think that's the way it should be. – hoshy Sep 12 '19 at 11:00
  • The controllers are hosted Views though and they should be stable or at least I guess that they are hosted Views as this is for SwiftUI. I agree that calling init() is not a particularly good idea but I wouldn't have thought it would be a catastrophe. – Michael Salmon Sep 12 '19 at 17:42
  • I just looked at your code again and I noticed that you use a local property for currentPage. When I did the same thing I used a binding which means that it is preserved between instancations although I did it that way to communicate with the parent View and the UIPageControl. – Michael Salmon Sep 12 '19 at 17:53

1 Answers1

1

If your controllers don't change after being displayed once, you can simply call:

pageViewController.setViewControllers([controllers[currentPage]], direction: .forward, animated: true)

from the makeUIViewController(context:) function instead of the updateUIViewController(:) function.

A.Andino
  • 128
  • 1
  • 7