UIPageViewController can return the wrong index

后端 未结 1 695
死守一世寂寞
死守一世寂寞 2021-02-09 11:43

As the title states, there is a way it can return the wrong index. This messes up the index presentation dots at the bottom of the page. The way this is done is by skipping a

1条回答
  •  轻奢々
    轻奢々 (楼主)
    2021-02-09 12:37

    I made a workaround because this is still not fixed!! It has been more than 4 years darn it.

    Class to subclass instead of UIPageViewController:

    import UIKit
    
    class PageController: UIPageViewController {
        lazy var pages: [UIViewController] = []
    
        var newOffset: CGFloat = 20
    
        var showReal = true
        var pageControlX: CGFloat = 0 // 0 is the middle of the screen
        var pageControlY: CGFloat = 200
    
        private var scrollView: UIScrollView?
        private var scrollPoints: [UIView] = []
        private var oldScrollPoints: [UIView] = []
    
        private var indexKeeper = IndexKeeper()
    
        private var selectedColor = UIColor(white: 1, alpha: 1)
        private var unselectedColor = UIColor(white: 1, alpha: 0.2)
    
        override func viewDidLayoutSubviews() {
            super.viewDidLayoutSubviews()
    
            // whole screen scroll
            scrollView = view.subviews.filter{ $0 is UIScrollView }.first! as? UIScrollView
            scrollView!.frame = UIScreen.main.bounds
    
            // get pageControl and scroll view from view's subviews
            let pageControl = view.subviews.filter{ $0 is UIPageControl }.first! as! UIPageControl
            oldScrollPoints = pageControl.subviews
    
            // remove all constraint from view that are tied to pageControl
            let const = view.constraints.filter { $0.firstItem as? NSObject == pageControl || $0.secondItem as? NSObject == pageControl }
            view.removeConstraints(const)
    
            // customize pageControl
            pageControl.translatesAutoresizingMaskIntoConstraints = false
            pageControl.frame = CGRect(x: pageControlX, y: pageControlY,
                                       width: view.frame.width, height: 0)
    
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
                self.replacePoints()
                self.updateIndex()
            }
        }
    
        private func replacePoints() {
            for point in scrollPoints {
                point.removeFromSuperview()
            }
            scrollPoints = []
            for oldPoint in oldScrollPoints {
                let newPoint = UIView(frame: oldPoint.frame)
                // make rounded
                newPoint.layer.borderWidth = 0
                newPoint.layer.masksToBounds = false
                newPoint.layer.cornerRadius = newPoint.frame.height / 2
                newPoint.clipsToBounds = true
    
                newPoint.center = CGPoint(x: oldPoint.center.x, y: newOffset)
                newPoint.backgroundColor = oldPoint.backgroundColor
                oldPoint.superview?.addSubview(newPoint)
                scrollPoints.append(newPoint)
                oldPoint.alpha = showReal ? 1 : 0
            }
        }
    
        private func offsetFromFirstPage() -> CGFloat {
            var coordinates: [CGFloat] = []
            for (index, page) in pages.enumerated() {
                let offset = scrollView!.convert(scrollView!.bounds.origin, to: page.view!)
                coordinates.append(offset.x + CGFloat(index) * view.frame.width)
            }
    
            let duplicates: [CGFloat] = coordinates.enumerated().map
            { current in
                if coordinates.firstIndex(of: current.element) != coordinates.lastIndex(of: current.element) {
                    return current.element
                } else {
                    return .nan
                }
            }
    
            return duplicates.first(where: { !$0.isNaN }) ?? 0
        }
    
        private var lastIndex: Int = 0
        private func pageIndexFrom(offset: CGFloat, visibleRatio: CGFloat=0.6) -> Int {
            let adjustedOffset = (offset / view.frame.width)
    
            if adjustedOffset > CGFloat(lastIndex) + visibleRatio {
                if lastIndex + 1 < pages.count {
                    lastIndex += 1
                }
            } else if adjustedOffset < CGFloat(lastIndex) - visibleRatio {
                if lastIndex - 1 >= 0 {
                    lastIndex -= 1
                }
            }
            return lastIndex
        }
    
        private func updateIndex() {
            let fastIndex = pages.firstIndex(of: viewControllers!.first!)!
            indexKeeper.fastIndexUpdate(index: fastIndex)
    
            let offset = offsetFromFirstPage()
            let slowIndex = pageIndexFrom(offset: offset)
            indexKeeper.slowIndexUpdate(index: slowIndex)
    
            for (index, point) in scrollPoints.enumerated() {
                if index == indexKeeper.finalIndex {
                    point.backgroundColor = selectedColor
                } else {
                    point.backgroundColor = unselectedColor
                }
            }
    
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
                self.updateIndex()
            }
        }
    }
    
    class IndexKeeper {
        var finalIndex = 0
        private var slowIndex = 0
        private var fastIndex = 0
    
        func slowIndexUpdate(index: Int) {
            if index != slowIndex {
                slowIndex = index
                finalIndex = slowIndex
            }
        }
    
        func fastIndexUpdate(index: Int) {
            if index != fastIndex {
                fastIndex = index
                finalIndex = fastIndex
            }
        }
    }
    

    Example Use:

    import UIKit
    
    class PageViewController: PageController, UIPageViewControllerDataSource {
    
        private func pageInstance(name:String) -> UIViewController {
            return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: name)
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            pages = [
                pageInstance(name: "FirstPage"),
                pageInstance(name: "SecondPage"),
                pageInstance(name: "ThirdPage"),
                pageInstance(name: "FourthPage")
            ]
    
            dataSource = self
    
            setViewControllers([pages.first!], direction: .forward, animated: false, completion: nil)
        }
    
        // get page before current page
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
            guard let index = pages.firstIndex(of: viewController), index > 0 else {
                return nil
            }
    
            return pages[index - 1]
        }
    
        // get page after current page
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
            guard let index = pages.firstIndex(of: viewController), index + 1 < pages.count else {
                return nil
            }
    
            return pages[index + 1]
        }
    
        func presentationCount(for pageViewController: UIPageViewController) -> Int {
            return pages.count
        }
    
        func presentationIndex(for pageViewController: UIPageViewController) -> Int {
            if let vc = viewControllers?.first {
                return pages.firstIndex(of: vc)!
            }
            return 0
        }
    }
    

    0 讨论(0)
提交回复
热议问题