Fast scroll UIPageViewController prevents viewcont

2020-07-18 07:41发布

I have a UIPageviewcontroller which got two controllers inside. As you swipe to the next, I use the viewController argument to set the appropriate delegate. But I experience that if you swipe too fast, the function viewControllerAfter isn't updating the viewController correctly. The initially swipe should update the index of the viewcontroller from 0 to 1, but doesn't do so if you swipe too fast.

import UIKit

class WizardPageViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {

    lazy var orderedViewControllers: [UIViewController] = {
        return [self.newVc(viewController: "intro"),
                self.newVc(viewController: "welcome")]
    }()

    var pageControl = UIPageControl()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.dataSource = self
        self.delegate = self
        configurePageControl()

        // This sets up the first view that will show up on our page control
        if let firstViewController = orderedViewControllers.first {
            setViewControllers([firstViewController],
                               direction: .forward,
                               animated: true,
                               completion: nil)
        }
    }

    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        let pageContentViewController = pageViewController.viewControllers![0]
        self.pageControl.currentPage = orderedViewControllers.index(of: pageContentViewController)!
    }

    func newVc(viewController: String) -> UIViewController {
        return UIStoryboard(name: "Wizard", bundle: nil).instantiateViewController(withIdentifier: viewController)
    }

    func configurePageControl() {
        // The total number of pages that are available is based on how many available colors we have.
        pageControl = UIPageControl(frame: CGRect(x: 0,y: UIScreen.main.bounds.maxY - 225,width: UIScreen.main.bounds.width,height: 50))
        self.pageControl.numberOfPages = orderedViewControllers.count
        self.pageControl.currentPage = 0
        pageControl.isEnabled = false
        //self.pageControl.tintColor = UIColor.black
        self.pageControl.pageIndicatorTintColor = UIColor.gray
        self.pageControl.currentPageIndicatorTintColor = UIColor(red:0.647, green:0.192, blue:0.216, alpha:1.00)
        self.view.addSubview(pageControl)
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        guard let viewControllerIndex = orderedViewControllers.index(of: viewController) else {
            return nil
        }

        print(orderedViewControllers.index(of: viewController))
        let previousIndex = viewControllerIndex - 1

        // User is on the first view controller and swiped left to loop to
        // the last view controller.
        guard previousIndex >= 0 else {
            return nil
            // Uncommment the line below, remove the line above if you don't want the page control to loop.
            // return nil
        }

        guard orderedViewControllers.count > previousIndex else {
            return nil
        }

        return orderedViewControllers[previousIndex]
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        guard let viewControllerIndex = orderedViewControllers.index(of: viewController) else {
            return nil
        }

        print(orderedViewControllers.index(of: viewController)) // Returns 0 is I swipe too fast, otherwise 1
        if let vc = orderedViewControllers[viewControllerIndex] as? WelcomeViewController {
            vc.delegate = self
        }

        let nextIndex = viewControllerIndex + 1
        let orderedViewControllersCount = orderedViewControllers.count

        // User is on the last view controller and swiped right to loop to
        // the first view controller.
        guard orderedViewControllersCount != nextIndex else {
            return nil
            // Uncommment the line below, remove the line above if you don't want the page control to loop.
            // return nil
        }

        guard orderedViewControllersCount > nextIndex else {
            return nil
        }

        return orderedViewControllers[nextIndex]
    }

}

2条回答
家丑人穷心不美
2楼-- · 2020-07-18 08:36

I've encountered exactly the same problem. It essentially boils down to UIPageViewController (_UIQueuingScrollView to be exact) not updating its view hierarchy correctly.

I noticed UIPageViewController is clever enough not to add/remove the content view if the page remains the same (even if it's in wrong place in the view hierarchy it somehow manages to cope with it so it looks good to the user). That's why I added a PageTrackingView to observe the content view being added (or not) to UIPageViewController view hierarchy. By tracking if the change happens I can calculate the current index by flipping it. So currently the workaround is good enough for 2 pages only.

class PageTrackingView: UIView {

    var pageIndexable: PageIndexable?

    override func willMove(toSuperview newSuperview: UIView?) {
        super.willMove(toSuperview: newSuperview)
        if var pageIndexable = pageIndexable {
            let newIndex = (pageIndexable.internalIndex == 0) ? 1 : 0
            pageIndexable.internalIndex = newIndex
        }
        //Reset pageIndexable so that the index flip is fired only once
        pageIndexable = nil
    }
}

protocol PageIndexable {
    var internalIndex: Int { get set }
}

class PageViewController: UIPageViewController,
    UIPageViewControllerDataSource,
    UIPageViewControllerDelegate,
    PageIndexable {

    private var supportedViewControllers = [UIViewController]()

    internal var internalIndex: Int = 0 {
    didSet {
        if supportedViewControllers.indices.contains(internalIndex) {
            //Do something with the actual internalIndex value
        } else {
            assertionFailure()
        }
    }

    init(transitionStyle style: UIPageViewControllerTransitionStyle = .scroll,
        navigationOrientation: UIPageViewControllerNavigationOrientation = .horizontal,
        options: [String: Any]? = nil,
        viewControllers: [T]) {
        supportedViewControllers = viewControllers

        if !supportedViewControllers.isEmpty {
            let viewController = supportedViewControllers[0]
            let trackingView = PageTrackingView(frame: viewController.view.frame)
            viewController.view.frame = viewController.view.bounds
            trackingView.addSubview(viewController.view)
            viewController.view = trackingView
        }
        super.init(transitionStyle: style,
            navigationOrientation: navigationOrientation,
            options: options)
    }

    func pageViewController(_ pageViewController: UIPageViewController,
        didFinishAnimating finished: Bool,
        previousViewControllers: [UIViewController],
        transitionCompleted completed: Bool) {

        if !supportedViewControllers.isEmpty, let trackingView = supportedViewControllers[0].view as? PageTrackingView {
            trackingView.pageIndexable = self
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        delegate = self
        datasource = self
    }
}
查看更多
来,给爷笑一个
3楼-- · 2020-07-18 08:38
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {

    guard let index = arrayVCs.index(of: viewController) else {
        return nil
    }

    if index == 0 {
        return nil
    }

    let prevIndex = abs((index - 1) % arrayVCs.count)
    return arrayVCs[prevIndex]
}

func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {

    guard let index = arrayVCs.index(of: viewController) else {
        return nil
    }

    if index == arrayVCs.count - 1 {
        return nil
    }

    let nextIndex = abs((index + 1) % arrayVCs.count)
    return arrayVCs[nextIndex]

}

func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {

        if let viewController = pageViewController.viewControllers?[0] {
            guard let index = arrayVCs.index(of: viewController) else {
                return
            }
            self.segment.selectedSegmentIndex = index
        }
    }

Try this. It's work for me.

查看更多
登录 后发表回答