I have the following PageViewController class :
class ProjectorPageViewController : UIPageViewController, UIPageViewControllerDataSource {
var randUsed : [String]?
var pageViewMatches : [SingleMatch]? {
didSet {
//irrelevant code
}
let initialcontroller = viewControlerAtIndex(0)
let viewControllers = [initialcontroller!]
setViewControllers(viewControllers, direction: .Forward, animated: true, completion: nil)
}
}
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
let pageControl = UIPageControl.appearance()
pageControl.currentPageIndicatorTintColor = UIColor.whiteColor()
pageControl.pageIndicatorTintColor = UIColor(red: (69/255.0), green: (209/255.0), blue: (153/255.0), alpha: 1.0)
}
func viewControlerAtIndex(index : Int) -> PageViewContentViewController? {
if (self.pageUsers!.count == 0 || index >= self.pageUsers!.count) {
return nil
}
let controller = PageViewContentViewController()
controller.location = pageLocations![index]
controller.image_name = pageImages![index]
controller.user_name = pageUsers![index]
controller.pageIndex = index
return controller
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let curr_index = (viewController as! PageViewContentViewController).pageIndex
print ("attempting after with index : " + String(curr_index))
if (curr_index! < pageViewMatches!.count - 1) {
return viewControlerAtIndex(curr_index! + 1) }
else {
return nil
}
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let curr_index = (viewController as! PageViewContentViewController).pageIndex
print ("attempting before with index : " + String(curr_index))
if (curr_index! > 0 ) {
return viewControlerAtIndex(curr_index! - 1)
}
else {
return nil
}
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
if let matches = pageViewMatches {
return matches.count
}
else {
return 0
}
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return pageViewController.viewControllers!.indexOf((pageViewController.viewControllers?.first)!)!
}
}
The problem is that if I swipe fast enough between the pages, the page indicator becomes out of sync, and points to the wrong page. If I swipe at a moderate pace this doesn't occur. I have seen a similar post which said the solution was to implement the presentationIndexForPageViewController method, however translating this from Objective-C (unless I made a mistake in translation) did not solve the problem. Here is the post I am referring to : UIPageViewController setViewControllers, UIPageControl not showing right current number
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
let vc = pageViewController.viewControllers!.first as! PageViewContentViewController
return vc.pageIndex
}
You must return pageIndex... pageViewController.viewControllers!.indexOf((pageViewController.viewControllers?.first)!)! won't return right index
Related
I am trying to jump to a specific page on a button press, I have made a ViewController which contains a ContainerView of the PageViewController, so that I can have permanent buttons on top. From there i do:
#IBAction func jump(_ sender: Any) {
PageViewController().jump()
}
and here is the Jump function in the PageViewController
func jump() {
var lastCoinNumber:Int = Manager.shared.coins.index(of: last!)!
lastNumber = lastCoinNumber
if let jumpView = viewControllerAtIndex(lastNumber, storyboard: self.storyboard!) {
self.setViewControllers([jumpView], direction: .forward, animated: false, completion: nil)
}
}
For some reason, I always get an error at the ViewControllerAtIndex part of the jump function:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
. even if I manually choose an index it does the same.
if let firstViewController = viewControllerAtIndex(0, storyboard: self.storyboard!) {
self.setViewControllers([firstViewController], direction: .forward, animated: false, completion: nil)
}
Above is how I set up the ViewControllers initialy in viewDidLoad(), and that works fine, if I am essentially doing the same thing, why doesn't it work? Here is how I index the pages and instantiate the viewcontrollers, they use the same single ViewController so a new instance of it is created for each page, otherwise I would have created an array of viewControllers.
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { if !completed { return }
DispatchQueue.main.async() {
self.dataSource = nil
self.dataSource = self
self.pageControl.numberOfPages = (Manager.shared.coins.count)
}
}
func viewControllerAtIndex(_ index: Int, storyboard: UIStoryboard) -> TemplateViewController? {
if Manager.shared.coins.count == 0 || index >= Manager.shared.coins.count {
return nil
}
let templateViewController = storyboard.instantiateViewController(withIdentifier: "templateController") as! TemplateViewController
templateViewController.dataObject = Manager.shared.coins[index]
return templateViewController
}
func indexOfViewController(_ viewController: TemplateViewController) -> Int {
self.pageControl.currentPage = Manager.shared.coins.index(of: viewController.dataObject)!
return Manager.shared.coins.index(of: viewController.dataObject)!
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
var index = self.indexOfViewController(viewController as! TemplateViewController)
if (index == 0) {
return nil
}
index -= 1
return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
var index = self.indexOfViewController(viewController as! TemplateViewController)
if index == -1 {
return nil
}
index += 1
if index == Manager.shared.coins.count {
return nil
}
return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}
I hope this is enough information, thank you.
Edit:
Coin is a string array, I declare this:
var last = Manager.shared.coins.last
var lastNumber: Int = 0
and then the jump() takes care of the rest. So I am getting the last object, finding its index, and then doing setViewControllers with the index of the last number, which should correspond with viewControllerAtIndex, because that uses the same array to index and load the pages.
for some reason, when I try to do:
var lastCoinNumber:Int = Manager.shared.coins.index(of: last)
It forces me to use ! because:
"Value of optional type 'Array.Index?' (aka 'Optional') not unwrapped; did you mean to use '!' or '?'?"
I'm not sure what I'm doing wrong in that regard, here is how coins is declared:
var coins = [""]
Edit: Code in full, PageViewController:
import UIKit
var pageView = PageViewController()
class PageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
//page Control dots
var pageControl = UIPageControl()
var last = Manager.shared.coins.last!
var lastNumber: Int = 0
var viewControllerArray = [UIViewController()]
func configurePageControl() {
// The total number of pages that are available is based on ManagerCoins names array
pageControl = UIPageControl(frame: CGRect(x: 0,y:
UIScreen.main.bounds.maxY - 50,width: UIScreen.main.bounds.width,height:
50))
self.pageControl.numberOfPages = (Manager.shared.coins.count)
self.pageControl.currentPage = 0
self.pageControl.tintColor = UIColor.black
self.pageControl.pageIndicatorTintColor = UIColor.gray
self.pageControl.currentPageIndicatorTintColor = UIColor.white
self.view.addSubview(pageControl)
}
// MARK: Delegate functions
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
}
func viewControllerAtIndex(_ index: Int, storyboard: UIStoryboard) -> TemplateViewController? {
if Manager.shared.coins.count == 0 || index >= Manager.shared.coins.count {
return nil
}
let templateViewController = storyboard.instantiateViewController(withIdentifier: "templateController") as! TemplateViewController
templateViewController.dataObject = Manager.shared.coins[index]
return templateViewController
}
func indexOfViewController(_ viewController: TemplateViewController) -> Int {
self.pageControl.currentPage = Manager.shared.coins.index(of: viewController.dataObject)!
return Manager.shared.coins.index(of: viewController.dataObject)!
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
var index = self.indexOfViewController(viewController as! TemplateViewController)
if (index == 0) {
return nil
}
index -= 1
return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
var index = self.indexOfViewController(viewController as! TemplateViewController)
if index == -1 {
return nil
}
index += 1
if index == Manager.shared.coins.count {
return nil
}
return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}
func jump() {
var lastCoinNumber:Int = Manager.shared.coins.index(of: last!)!
lastNumber = lastCoinNumber
if let jumpView = viewControllerAtIndex(lastCoinNumber, storyboard: self.storyboard!){
self.setViewControllers([jumpView], direction: .forward, animated: false, completion: nil)
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
configurePageControl()
self.dataSource = self
if let firstViewController = viewControllerAtIndex(0, storyboard: self.storyboard!){
self.setViewControllers([firstViewController], direction: .forward, animated: false, completion: nil)
}
}
}
Edit:
After much research, This seems to be the correct way to jump a page, by calling setViewControllers, the first comment on this answer seems to have the same problem, it works fine for setting the view on viewDidLoad() but trying to programmatically navigate to a page based on the index falls to an error. https://stackoverflow.com/a/24335008/10058239
I think it's crashing because of for force unwrapping:
var lastCoinNumber:Int = Manager.shared.coins.index(of: last!)!
if coins is a collection you can use .last to get the last element for example:
let numbers = [10, 20, 30, 40, 50]
if let lastNumber = numbers.last {
print(lastNumber)
}
// Prints "50"
I created a PageController with 3 pages .. I allocated and declared everything, I implemented the two functions that allow scrolling views, in the same I entered the commands to enable the controller with the right page reported. the result is that if I flow my pages is all ok, while under the page counter works as he wants.. and I can not understand what is wrong
problemproblem2problem3
import UIKit
import AVFoundation
protocol IntroNavigationDelegate: class {
func showNextViewController()
func showPreviousViewController()
var isPagingEnabled: Bool { get set }
}
final class IntroViewController: UIPageViewController, UIPageViewControllerDelegate, IntroNavigationDelegate, UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
if let viewControllerIndex = self.displayedControllers.index(of: viewController) {
if viewControllerIndex == 0 {
} else if viewControllerIndex == 1 {
return self.displayedControllers[viewControllerIndex - 1]
}
}
return nil
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
if let viewControllerIndex = self.displayedControllers.index(of: viewController) {
if viewControllerIndex < self.displayedControllers.count - 1 {
self.pageControl.currentPage = 1
return self.displayedControllers[viewControllerIndex + 1]
} else {
self.pageControl.currentPage = 0
}
}
return nil
}
var introRouter: IntroRouter?
var pageControl = UIPageControl()
var displayedControllers: [UIViewController] = []
private var scrollView: UIScrollView? {
for view in view.subviews {
if let subView = view as? UIScrollView {
return subView
}
}
return nil
}
var isPagingEnabled: Bool {
get {
return scrollView?.isScrollEnabled ?? true
}
set {
scrollView?.isScrollEnabled = newValue
}
}
override func viewDidLoad() {
super.viewDidLoad()
configurePageControl()
setupUI()
self.pageControl.updateCurrentPageDisplay()
arrangeSubviews()
self.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.setNavigationBarHidden(true, animated: animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.isNavigationBarHidden = false
}
func setDiplayedControllers(_ controllers: [UIViewController], visualizedController: UIViewController) {
displayedControllers = controllers
setViewControllers([visualizedController], direction: .forward, animated: false, completion: nil)
}
func showNextViewController() {
if let current = viewControllers?.first, let next = IntroViewController(self, viewControllerAfter: current) {
setViewControllers([next], direction: .forward, animated: true, completion: nil)
}
}
func showPreviousViewController() {
if let current = viewControllers?.first, let previous = IntroViewController(self, viewControllerBefore: current) {
setViewControllers([previous], direction: .reverse, animated: true, completion: nil)
}
}
func configurePageControl(){
self.pageControl.frame = CGRect()
self.pageControl.numberOfPages = self.displayedControllers.count
self.pageControl.translatesAutoresizingMaskIntoConstraints = false
self.pageControl.updateCurrentPageDisplay()
self.view.addSubview(self.pageControl)
pageControl.activate([
pageControl.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -20),
pageControl.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.1),
pageControl.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 1),
])
}
override init(transitionStyle style: UIPageViewControllerTransitionStyle, navigationOrientation: UIPageViewControllerNavigationOrientation, options: [String : Any]? = nil) {
super.init(transitionStyle: style, navigationOrientation: navigationOrientation, options: options)
setup()
}
func setRouter(introRouter: IntroRouter) {
self.introRouter = introRouter
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup() {
view.backgroundColor = .white
dataSource = self
}
}
private extension IntroViewController {
func IntroViewController(_ IntroViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
if let viewControllerIndex = self.displayedControllers.index(of: viewController) {
if viewControllerIndex == 0 {
// wrap to last page in array
return self.displayedControllers.last
} else {
// go to previous page in array
return self.displayedControllers[viewControllerIndex + 1]
}
}
return nil
}
func IntroViewController(_ IntroViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
self.pageControl.updateCurrentPageDisplay()
if let viewControllerIndex = self.displayedControllers.index(of: viewController) {
if viewControllerIndex < self.displayedControllers.count - 1 {
// go to next page in array
self.pageControl.currentPage = viewControllerIndex
return self.displayedControllers[viewControllerIndex + 1]
} else {
// wrap to first page in array
self.pageControl.currentPage = viewControllerIndex
return self.displayedControllers.first
}
}
return nil
}
func IntroViewController(_ IntroViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
// set the pageControl.currentPage to the index of the current viewController in pages
if let viewControllers = IntroViewController.viewControllers {
if let viewControllerIndex = self.displayedControllers.index(of: viewControllers[0]) {
self.pageControl.currentPage = viewControllerIndex
self.pageControl.updateCurrentPageDisplay()
}
}
}
func setupUI() {
configurePageControl()
pageControl.do {
$0.numberOfPages = 3
// $0.currentPage = 0
$0.pageIndicatorTintColor = .lightGray
$0.currentPageIndicatorTintColor = Theme.Colors.white
$0.currentPage = self.displayedControllers.count
}
}
func arrangeSubviews() {
view.addSubview(pageControl)
}
}
Just try the below code. It would be helpfull.
class PageViewModel: NSObject {
private var timer: Timer?
private var interval: Double?
var enableAutoScroll: Bool = false {
didSet {
self.toggleTimer()
}
}
var viewControllers: [UIViewController]
var currentIndex: Int = 0
var direction: UIPageViewControllerNavigationDirection = .forward
init(viewControllers: [UIViewController]) {
self.viewControllers = viewControllers
super.init()
}
override convenience init() {
self.init(viewControllers: [])
}
private func stopTimer() {
if self.timer != nil {
timer?.invalidate()
timer = nil
return
}
}
private func startTimer() {
if self.timer != nil {
return
}
if #available(iOS 10.0, *) {
if let time = interval {
timer = Timer.scheduledTimer(withTimeInterval: time, repeats: true, block: { (timer: Timer) in
self.timerFire()
})
}
} else {
// Fallback on earlier versions
if let time = interval {
timer = Timer.scheduledTimer(timeInterval: time, target: self, selector: #selector(PageViewModel.timerFire), userInfo: nil, repeats: true)
}
}
}
#objc private func timerFire() {
if direction == .forward {
var index = currentIndex
if index < self.viewControllers.count - 1 {
index += 1
} else {
index = 0
}
currentIndex = index
scrollToNextPage?(currentIndex)
pageNumberChangedObserver?(currentIndex)
} else {
var index = currentIndex
if index <= 0 {
index = (self.viewControllers.count - 1)
} else {
index -= 1
}
currentIndex = index
scrollToNextPage?(index)
pageNumberChangedObserver?(currentIndex)
}
}
// MARK: - Timer
func toggleTimer() {
if enableAutoScroll == true {
startTimer()
} else {
stopTimer()
}
}
fileprivate var scrollToNextPage: ((_ index: Int) -> Void)?
func scrollToNextPage(callBack: #escaping (_ index: Int) -> Void) {
scrollToNextPage = callBack
}
// MARK: - PageNumberChangedObserver
fileprivate var pageNumberChangedObserver: ((_ index: Int) -> Void)?
func pageNumberChanged(callBack: #escaping (_ index: Int) -> Void) {
pageNumberChangedObserver = callBack
}
}
// MARK: - UIPageViewControllerDataSource
extension PageViewModel: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = viewControllers.index(of: viewController) else {
return nil
}
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 orderedViewControllers.last
// Uncommment the line below, remove the line above if you don't want the page control to loop.
return nil
}
guard viewControllers.count > previousIndex else {
return nil
}
return viewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = viewControllers.index(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let orderedViewControllersCount = viewControllers.count
// User is on the last view controller and swiped right to loop to
// the first view controller.
guard orderedViewControllersCount != nextIndex else {
//return orderedViewControllers.first
// 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 viewControllers[nextIndex]
}
}
// MARK: - UIPageViewControllerDelegate
extension PageViewModel: UIPageViewControllerDelegate {
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if let pageContentViewController = pageViewController.viewControllers?[0] {
if let index = viewControllers.index(of: pageContentViewController) {
currentIndex = index
pageNumberChangedObserver?(currentIndex)
}
}
}
}
Write below code in your Container View Controller: -
// set current page to the page controller
func setNextPage(index: Int, direction: UIPageViewControllerNavigationDirection) {
if let firstViewController = pageViewModel?.viewControllers[selectedCategoryCell] {
pageViewController?.setViewControllers([firstViewController],
direction: direction,
animated: true,
completion: nil)
}
}
// MARK: - SET PAGE CONTROLLER's VIEW's
private func setupPageViewController() {
let pageViewController: UIPageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
pageViewController.dataSource = pageViewModel
pageViewController.delegate = pageViewModel
if let firstViewController = pageViewModel?.viewControllers[selectedCategoryCell] {
pageViewController.setViewControllers([firstViewController],
direction: .forward,
animated: true,
completion: nil)
}
for recognizer in pageViewController.gestureRecognizers {
if recognizer is UITapGestureRecognizer {
recognizer.isEnabled = false
}
}
self.addChildViewController(pageViewController)
self.containerView.addSubview(pageViewController.view)
var pageViewRect = self.containerView.bounds
if UIDevice.current.userInterfaceIdiom == .pad {
pageViewRect = pageViewRect.insetBy(dx: 40.0, dy: 40.0)
}
pageViewController.view.frame = pageViewRect
pageViewController.didMove(toParentViewController: self)
self.pageViewController = pageViewController
}
// set the selected page and accordingly set the above collection view ( that is treatment option's collection view)
func pageNumberChanged(index: Int) {
self.selectedCategoryCell = index
}
// MARK: Init View Controller for Page Controller
func initViewController(index: Int) -> UIViewController {
guard let itemsViewController = self.storyboard?.instantiateViewController(withIdentifier: "MatchesV2ViewController") as? MatchesV2ViewController else {
fatalError("Items View Controller Not Found")
}
itemsViewController.delegate = self
itemsViewController.currentMatch = matches.user[index]
return itemsViewController
}
// MARK: Setting VC In Page View Controller
func instantiateViewControllers() {
matchesScreen.removeAll()
for i in 0..<matches.user.count {
matchesScreen.append(initViewController(index: i))
}
pageViewModel = PageViewModel(viewControllers: matchesScreen)
pageViewModel?.pageNumberChanged(callBack: { (index: Int) in
self.pageNumberChanged(index: index)
})
pageViewModel?.scrollToNextPage(callBack: { (index: Int) in
self.setNextPage(index: index, direction: .forward)
})
if matchesScreen.count > 0 {
self.setupPageViewController()
}
}
I am trying to load appropriate images from an array into my UIPageViewController's Views.
I have 3 views which I cycle through in order to prevent crashes.
I keep track whether I am scrolling forward or backwards but sometimes when flipping forth and back really quick, I run into a bug that flips the direction and than I get the same view repeating for couple of scrolls.
Here is the function that updates the local variable index when scrolling completes
func pageViewController(pvc: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed {
let thisPage = pvc.viewControllers!.last as! PageContentViewController
let currentIndex = thisPage.pageIndex
let lastIndex = lastPage.pageIndex
if(currentIndex > urlIndex){
urlIndex += 1
}else if(currentIndex < urlIndex) {
urlIndex -= 1
}else{
}
}
}
And here is the rest of my UIPageViewController code:
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
delegate = self
}
private(set) lazy var orderedViewControllers: [PageContentViewController] = {
return [self.newPageContentViewController(),
self.newPageContentViewController(),
self.newPageContentViewController()
]
}()
private func newPageContentViewController() -> PageContentViewController {
return UIStoryboard(name: "Main", bundle: nil) .
instantiateViewControllerWithIdentifier("PageContentViewController") as! PageContentViewController
}
private func getNextViewController() -> PageContentViewController{
let scrollView = orderedViewControllers.removeAtIndex(0)
orderedViewControllers.append(scrollView)
return scrollView
}
private func getPreviousViewController() -> PageContentViewController{
let scrollView = orderedViewControllers.removeLast()
orderedViewControllers.insert(scrollView, atIndex: 0)
return scrollView
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?
{
guard urlIndex > 0 else {
return nil
}
let previousUrlIndex = urlIndex - 1
return getViewControllerAtIndex(previousUrlIndex, next: false)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController?
{
let nextUrlIndex = urlIndex + 1
guard arrayRouteName.count > nextUrlIndex else{
return nil
}
return getViewControllerAtIndex(nextUrlIndex, next: true)
}
func getViewControllerAtIndex(index: NSInteger, next: Bool) -> PageContentViewController
{
// Create a new view controller and pass suitable data.
var pageContentViewController:PageContentViewController
if(next){
pageContentViewController = getNextViewController()
}else{
pageContentViewController = getPreviousViewController()
}
pageContentViewController.localImage = arrayLocalImage[index]
pageContentViewController.pageIndex = index
pageContentViewController.view.clipsToBounds = true
return pageContentViewController
}
I have managed to fix the problem. I have removed the pageViewControllerfunction from the UIPageControllerDelegate an simply chaged my pageViewController functions in my data source to get the index from viewController like so
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?
{
let index = ((viewController as! UIPageContentViewController).pageIndex) - 1
guard index < 0 {
return nil
}
return getViewControllerAtIndex(index, pageContentViewController: getPreviousViewController() )
}
and it behaves like it should.
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 page without releasing a finger from the screen. If this happens, it messes of the rest of the presentation dots.
here is what the bug it looks like in action.
And here is the code that was used for the UIPageViewController.
import UIKit
class PageViewController: UIPageViewController {
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return UIStatusBarStyle.LightContent
}
private(set) lazy var orderedViewControllers: [UIViewController] = {
return [self.newColoredViewController("Green"),
self.newColoredViewController("Red"),
self.newColoredViewController("Blue")]
}()
private func newColoredViewController(color: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil) .
instantiateViewControllerWithIdentifier("\(color)ViewController")
}
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController],
direction: .Forward,
animated: true,
completion: nil)
}
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
//MARK: UIPageViewControllerDataSource
extension PageViewController: UIPageViewControllerDataSource {
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.indexOf(viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return nil
}
guard orderedViewControllers.count > previousIndex else {
return nil
}
return orderedViewControllers[previousIndex]
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.indexOf(viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let orderedViewControllersCount = orderedViewControllers.count
guard orderedViewControllersCount != nextIndex else {
return nil
}
guard orderedViewControllersCount > nextIndex else {
return nil
}
return orderedViewControllers[nextIndex]
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return orderedViewControllers.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
guard let firstViewController = viewControllers?.first,
firstViewControllerIndex = orderedViewControllers.indexOf(firstViewController) else {
return 0
}
return firstViewControllerIndex
}
}
Any help with this would be greatly appreciated, thanks.
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
}
}
I'm starting study swift, and use UIPageViewController to slide 3 page. I use class:
import Foundation
import UIKit
class MyPageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var pages = [UIViewController]()
var pageIndicator : UIPageControl!
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
self.dataSource = self
let page1: UIViewController! = storyboard?.instantiateViewControllerWithIdentifier("page1")
let page2: UIViewController! = storyboard?.instantiateViewControllerWithIdentifier("page2")
let page3: UIViewController! = storyboard?.instantiateViewControllerWithIdentifier("page3")
pages.append(page1)
pages.append(page2)
pages.append(page3)
setViewControllers([page1], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
func viewControllerAtIndex(index: Int) -> UINavigationController! {
return nil
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let currentIndex = pages.indexOf(viewController)!
pageIndicator.currentPage = currentIndex
let previousIndex = abs((currentIndex - 1) % pages.count)
return pages[previousIndex]
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let currentIndex = pages.indexOf(viewController)!
pageIndicator.currentPage = currentIndex
let nextIndex = abs((currentIndex + 1) % pages.count)
return pages[nextIndex]
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return pages.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return 0
}
}
But sliding didn't stops on the third page, and begins again with first page.
How limit slide page forward and backward?
I think your index calculation is wrong in the following line.
`
let nextIndex = abs((currentIndex + 1) % pages.count)
return pages[nextIndex]`
for the last view controller currentIndex=2 ,then nextIndex=(2+1)%3=0.
So you are returning the view controller at index 0 again.
add the following line to your method `
if(currentIndex == (pages.count-1))
{
return nil;
}
`Note:this is according to objective c.
As UIBittu already pointed out: your calculation needs a fix.
A possible (probably more readable) solution may look like this:
func indexOfViewController(viewController: UIViewController) -> Int {
guard let index = pages.indexOf(viewController) else {
return NSNotFound
}
return index
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
var index = indexOfViewController(viewController)
if (index == 0) || (index == NSNotFound) {
return nil
}
index--
return pages[index]
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
var index = indexOfViewController(viewController)
if (index == NSNotFound) || (index+1 == pages.count) {
return nil
}
index++
return pages[index]
}
This is different solution.
You don't need to have a full set of complex code to execute what you need.
Create a ScrollView. Set its content size width as the multiple of number of pages.
Try this code. this may help You
override func viewDidLoad() {
super.viewDidLoad()
let screensize = UIScreen.mainScreen().bounds
pagescroll.frame = CGRectMake(0, 0,screensize.width, screensize.height)
pagescroll.contentSize = CGSize(width: screensize.width*3, height: screensize.height)
pagescroll.pagingEnabled = true
page1.frame = CGRectMake(0, 0, screensize.width, screensize.height)
page2.frame = CGRectMake(screensize.width, 0, screensize.width, screensize.height)
page3.frame = CGRectMake(screensize.width*2, 0, screensize.width, screensize.height)
pagescroll.addSubview(page1)
pagescroll.addSubview(page2)
pagescroll.addSubview(page2)
self.view.addSubview(pagescroll)
// Do any additional setup after loading the view.
}
var pagescroll = UIScrollView()
var page1 = UIView()
var page2 = UIView()
var page3 = UIView()
You can also add a pagecontroller: UIPageControl according to your requirement