yout_ishida
@yout_ishida (悠斗 石田)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Issue where the same page is displayed twice in a row when using UIPageViewController from SwiftUI

What we want to solve

Paging is achieved by using UIPageView from SwiftUI. I am having trouble with the same page being displayed twice in a row when swiping through pages. The condition that causes this phenomenon is the speed of page turning. This problem does not occur when the page is turned slowly or very quickly, but it does occur when the page is turned at an intermediate speed. Here is a video of this phenomenon.
https://youtube.com/shorts/VD8UcHbomYY?feature=share

The full code for reproduction is attached below. Your assistance would be greatly appreciated. Thank you in advance.

struct ContentView: View {
    @State var currentPage: Int = 2
    
    var body: some View {
        VStack {
            Text(currentPage.description)
                .font(.largeTitle)
            PageView(
                pages: Array(0...100).map({ num in
                    Text("Page of \(num)")
                        .font(.largeTitle)
                        .padding(100)
                        .background(Color(.systemOrange))
                        .cornerRadius(10)
                }),
                currentPage: $currentPage)
        }
    }
}

struct PageView<Page: View>: UIViewControllerRepresentable {
    
    var pages: [Page]
    @Binding var currentPage: Int
    
    func makeUIViewController(context: Context) -> UIPageViewController {
        let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [UIPageViewController.OptionsKey.interPageSpacing: 30])
        pageViewController.dataSource = context.coordinator
        pageViewController.delegate = context.coordinator

        return pageViewController
    }
    
    func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {
        context.coordinator.controllers = pages.map({
            let hostingController = UIHostingController(rootView: $0)
            hostingController.view.backgroundColor = .clear
            return hostingController
        })
        let currentViewController = context.coordinator.controllers[currentPage]
        pageViewController.setViewControllers([currentViewController], direction: .forward, animated: false)
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(parent: self, pages: pages)
    }
    
    class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
        
        var parent: PageView
        var controllers: [UIViewController]
        
        init(parent: PageView, pages: [Page]) {
            self.parent = parent
            self.controllers = pages.map({
                let hostingController = UIHostingController(rootView: $0)
                hostingController.view.backgroundColor = .clear
                return hostingController
            })
        }
        
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
            guard let index = controllers.firstIndex(of: viewController) else {
                return controllers[parent.currentPage - 1]
            }
            if index == 0 {
                return nil
            }
            return controllers[index - 1]
        }
        
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
            guard let index = controllers.firstIndex(of: viewController) else {
                return controllers[parent.currentPage + 1]
            }
            if index + 1 == controllers.count {
                return nil
            }
            return controllers[index + 1]
        }
        func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
            if completed,
                let currentViewController = pageViewController.viewControllers?.first,
                let currentIndex = controllers.firstIndex(of: currentViewController)
            {
                parent.currentPage = currentIndex
            }
        }
    }
}

We also tried to realize paging from SwiftUI such as TabView and ScrollView. However, the screen I wanted to display was very heavy, and the method using UIKit's UIPageView was the only one that had almost no screen nibbling.

0

1Answer

Comments

  1. @yout_ishida

    Questioner

    解決しました。ご報告ありがとうございます。

Your answer might help someone💌