Search code examples
iosswiftuipageviewcontroller

UIPageController: Turning the page forward then backward quickly only updates the first page


I have the class SliderPgaeViewController: UIPageViewController with scroll transition style as follows:

class SliderPgaeViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource, PlayerUpdatePageControllerDelegate {


var lastPendingIndex: Int = 0
var sliderPageDelegate: SliderPageDelegate? = nil
let playerManager = PlayerManager.getInstance()

override func viewDidLoad() {
    super.viewDidLoad()

    self.dataSource = self
    self.delegate = self
    setViewControllers([createViewController(index: playerManager.getCurrentIndex())!], direction: .forward, animated: true, completion: nil)
    lastPendingIndex = playerManager.getCurrentIndex()
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
}

func presentationCount(for pageViewController: UIPageViewController) -> Int {
    return playerManager.getSongsCount()
}

func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
    if let vc = viewController as? PlayerImageViewController {
        if (vc.index == 0){
            return nil
        }
        return createViewController(index: vc.index! - 1)
    }
    return nil

}

func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
    if let vc = viewController as? PlayerImageViewController {
        if (vc.index == playerManager.getSongsCount() - 1){
            return nil
        }
        return createViewController(index: vc.index! + 1)
    }
    return nil
}


func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]){
    if let vc = pendingViewControllers[0] as? PlayerImageViewController {
        self.lastPendingIndex = vc.index!
    }
}

func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool,previousViewControllers: [UIViewController],transitionCompleted completed: Bool) {
    print("before completion : \(self.lastPendingIndex)")
    if(completed){
        print("completed : \(self.lastPendingIndex)")
        if (viewControllers?.first as? PlayerImageViewController) != nil {
            sliderPageDelegate?.updateSong(index: self.lastPendingIndex, dir: 0)
        }
    }
}

private func createViewController(index i: Int) -> UIViewController?{
    let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "PlayerImageController") as! PlayerImageViewController
    vc.index = i
    vc.image = playerManager.getSong(index: i).image
    return vc
}
...

I am using this page controller to display the thumbnail of a song in a music player. And when the user turn a page the player changes the song playing by calling sliderPageDelegate?.updateSong(index: self.lastPendingIndex, dir: 0) in pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool,previousViewControllers: [UIViewController],transitionCompleted completed: Bool)

When I'm turning the page forward and then immediately backward, the page is turning correctly (first forward then backward); however, sliderPageDelegate?.updateSong(index: self.lastPendingIndex, dir: 0) is only being called in the forward direction.

So, if we have list of songs (A, B, C, ...) and we are currently at song A. When the user swipes forward, the thumbnail changes to B's thumbnail and the player updates the song to B. However, if the forward swipe was followed by a backward swipe quickly, then the thumbnail changes to A but the song remains B

Update:

if A has index = 0 and B has index = 1, moving A->B->A quickly will print the following:

before completion : 1
before completion : 1
completed : 1

Solution

  • I have added PageControllerDelegate in your code and introduce two new methods. Then implement these delegates in your UIPageViewController class and then in PlayerImageViewController class invoke both methods in viewWillAppear and viewWillDisappear. Now remove you didFinishAnimation method and write that code in viewControllerIsBeingDisplay.

    For deeper knowledge review this code.

    class PlayerImageViewController: UIViewController {
        var index: Int?
        var image: UIImage?
        weak var delegate: PageControllerDelegate?
    
    
        override func viewWillDisappear(_ animated: Bool) {
            super.viewWillDisappear(animated)
            if let del = self.delegate {
                del.viewControllerIsBeingHide(self)
            }
        }
        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            if let del = self.delegate {
                del.viewControllerIsBeingDisplay(self)
            }
        }
    }
    protocol SliderPageDelegate {
        func updateSong(index: Int, dir: Int)
    }
    protocol PageControllerDelegate {
        func viewControllerIsBeingHide(_ viewController: PlayerImageViewController)
        func viewControllerIsBeingDisplay(_ viewController: PlayerImageViewController)
    }
    class SliderPgaeViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource, PageControllerDelegate {
    
    
        var lastPendingIndex: Int = 0
        var sliderPageDelegate: SliderPageDelegate? = nil
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            self.dataSource = self
            self.delegate = self
            setViewControllers([createViewController(index: playerManager.getCurrentIndex())!], direction: .forward, animated: true, completion: nil)
            lastPendingIndex = playerManager.getCurrentIndex()
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
    
        func presentationCount(for pageViewController: UIPageViewController) -> Int {
            return playerManager.getSongsCount()
        }
    
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
            if let vc = viewController as? PlayerImageViewController {
                if (vc.index == 0){
                    return nil
                }
                return createViewController(index: vc.index! - 1)
            }
            return nil
    
        }
    
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
            if let vc = viewController as? PlayerImageViewController {
                if (vc.index == playerManager.getSongsCount() - 1){
                    return nil
                }
                return createViewController(index: vc.index! + 1)
            }
            return nil
        }
    
    
        func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]){
            if let vc = pendingViewControllers[0] as? PlayerImageViewController {
                self.lastPendingIndex = vc.index!
            }
        }
    
    
        private func createViewController(index i: Int) -> UIViewController?{
            let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "PlayerImageController") as! PlayerImageViewController
            vc.index = i
            vc.delegate = self
    //        vc.image = playerManager.getSong(index: i).image //Commented becoz i do not have this file
            return vc
        }
        var previousVCIndex = 0
    
        func viewControllerIsBeingHide(_ viewController: PlayerImageViewController) {
            previousVCIndex = viewController.index!
        }
        func viewControllerIsBeingDisplay(_ viewController: PlayerImageViewController) {
            if let del = sliderPageDelegate {
                del.updateSong(index: previousVCIndex, dir: 0)
            }
        }
    }