I implement the Page controller ..automatically moving the page in swift
but my issue is the page controller dots not getting changed not indicates the page any one help me to solve this issue
here is my code
override func viewDidLoad() {
super.viewDidLoad()
UpdateCounter = 0
arrPageTitle = ["In SignUp screen user can able to input the first name, last name, emailid and password.", "After SignUp email verification link has been send to his mail then add basic profile information and sport preferences.", "In Profile setting can view profile, privacy and notifications, friends, account and championships won."];
self.pageViewController = self.storyboard?.instantiateViewController(withIdentifier: "myPageviewcontroller") as! UIPageViewController
self.pageViewController.dataSource = self
let initialContentviewcontroller = self.getViewControllerAtIndex(index: 0) as PageContentViewController
let viewcontrollers = NSArray(object: initialContentviewcontroller)
self.pageViewController.setViewControllers(viewcontrollers as? [UIViewController], direction: UIPageViewControllerNavigationDirection.forward, animated: true, completion: nil)
self.pageViewController.view.frame = CGRect(x: 0, y: 50, width:self.view.frame.width,height: 350)
//pagecontroller.delegate = self
pagecontroller.numberOfPages = arrPageTitle.count
pagecontroller.currentPage = 0;
self.addChildViewController(self.pageViewController)
self.view.addSubview(self.pageViewController.view)
self.pageViewController.didMove(toParentViewController: self)
timer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: (#selector(StartUpPage.advancePage)), userInfo: nil, repeats: true)
}
func getViewControllerAtIndex(index: Int) -> PageContentViewController
{
// Create a new view controller and pass suitable data.
let pageContentViewController = self.storyboard?.instantiateViewController(withIdentifier: "PageContentViewController") as! PageContentViewController
pageContentViewController.strTitle = "\(arrPageTitle[index])"
pageContentViewController.pageIndex = index
return pageContentViewController
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController?
{
let viewController = viewController as! PageContentViewController
var index = viewController.pageIndex as Int
if(index == 0 || index == NSNotFound)
{ return nil
}
index -= 1
return self.getViewControllerAtIndex(index: index)
}
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController?
{
let viewController = viewController as! PageContentViewController
var index = viewController.pageIndex as Int
if(( index == NSNotFound))
{
return nil
}
index += 1
if(index == arrPageTitle.count)
{
return nil
}
return self.getViewControllerAtIndex(index: index)
}
public func presentationCount(for pageViewController: UIPageViewController) -> Int
{
return arrPageTitle.count
}
public func presentationIndex(for pageViewController: UIPageViewController) -> Int
{
let viewController = self.getViewControllerAtIndex(index: 0)
let index = viewController.pageIndex as Int
return index
}
func advancePage ()
{
UpdateCounter += 1
if UpdateCounter > 2 {
UpdateCounter = 0
}
var nextviewcontroller = self.getViewControllerAtIndex(index: UpdateCounter)
if (nextviewcontroller .isEqual(nil)) {
UpdateCounter = 0
nextviewcontroller = self.getViewControllerAtIndex(index: UpdateCounter)
}
let startingViewControllers = [nextviewcontroller]
pageViewController.setViewControllers(startingViewControllers, direction: UIPageViewControllerNavigationDirection.forward, animated: true, completion: nil)
pagecontroller.currentPage = UpdateCounter
pagecontroller.numberOfPages = 3
pagecontroller.currentPage = 0
pagecontroller.addTarget(self, action: #selector(pageControlTapHandler(sender:)), for: .touchUpInside)
}
Any one help me how to solve this issues when auto scroll the page ..pagecontroll dots also get moved
Thanks in advance
public func presentationIndex(for pageViewController: UIPageViewController) -> Int
{
let viewController = pageViewController.viewControllers?[0] as! PageContentViewController
let index = viewController.pageIndex
pagecontroller.currentPage = index
UIPageControl.appearance().pageIndicatorTintColor = UIColor.lightGray
UIPageControl.appearance().currentPageIndicatorTintColor = UIColor.red
return index
}
add this code this is delegate method for page controller
It will Work fine
Happy Code:)
try change
pagecontroller.currentPage = 0
to
pagecontroller.currentPage = UpdateCounter
You reseted the current in func AdvancedPage
pagecontroller.currentPage = UpdateCounter
pagecontroller.numberOfPages = 3
pagecontroller.currentPage = 0
just delete "pagecontroller.currentPage = 0" is ok.
try this (I only know objective-c codes, try to use it in swift)
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {
if (finished) {
YourViewControllersInPageController *childViewController = [pageViewController.viewControllers firstObject];
[self.pageControl setCurrentPage:childViewController.index];
}
}
remember set delegate
pagecontroller.delegate = self
From Another related answer
A page indicator will be visible if both methods are implemented,
transition style is 'UIPageViewControllerTransitionStyleScroll', and
navigation orientation is
'UIPageViewControllerNavigationOrientationHorizontal'. Both methods
are called in response to a 'setViewControllers:...' call, but the
presentation index is updated automatically in the case of
gesture-driven navigation.
You also need to implement below functions to set the page counts for UIPageViewController
- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController NS_AVAILABLE_IOS(6_0); // The number of items reflected in the page indicator.
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController NS_AVAILABLE_IOS(6_0); // The selected item reflected in the page indicator.
have implemented a custom page control with the help of which you can change your size , color , shape and page number very easily ... here is the class
import UIKit
import PureLayout
extension PageControl {
func setupView() {
for subview in subviews {
subview.removeFromSuperview()
}
pages.removeAll(keepingCapacity: true)
container = UIView(frame: CGRect.zero)
self.addSubview(container!)
container?.autoCenterInSuperview()
container?.autoPinEdge(toSuperviewEdge: .top)
container?.autoPinEdge(toSuperviewEdge: .bottom)
for index in 0 ..< totalPages {
let page = UIView()
container?.addSubview(page)
pages.append(page)
page.autoMatch(.width, to: .height, of: page)
page.autoPinEdge(toSuperviewEdge: .top, withInset: padding)
page.autoPinEdge(toSuperviewEdge: .bottom, withInset: padding)
if index == 0 {
page.autoPinEdge(toSuperviewEdge: .left, withInset: padding, relation: .greaterThanOrEqual)
} else if index == totalPages-1 {
page.autoPinEdge(toSuperviewEdge: .right, withInset: padding, relation: .greaterThanOrEqual)
} else {
page.autoPinEdge(.left, to: .right, of: pages[index-1], withOffset: padding)
}
}
let size = frame.height - padding * 2
let width = padding * CGFloat(totalPages+1) + size * CGFloat(totalPages)
container?.autoSetDimension(.width, toSize: width)
//layoutIfNeeded()
setNeedsDisplay()
}
}
#IBDesignable class PageControl: UIControl {
var container:UIView?
var pages = [UIView]()
#IBInspectable var currentPage:Int = 0 {
didSet {
setNeedsLayout()
}
}
#IBInspectable var allPagesColor: UIColor = Colors.greyELight {
didSet {
setNeedsDisplay()
}
}
#IBInspectable var currentPageColor: UIColor = Colors.green {
didSet {
setNeedsDisplay()
}
}
#IBInspectable var totalPages: Int = 3 {
didSet {
if totalPages > 0 {
setupView()
}
}
}
#IBInspectable var padding: CGFloat = 2 {
didSet {
if padding > 0 {
setupView()
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
override func layoutSubviews() {
super.layoutSubviews()
let size = frame.height - padding * 2
for (index, view) in pages.enumerated() {
view.layer.cornerRadius = size / 2
view.layer.masksToBounds = true
view.backgroundColor = (index == currentPage) ? currentPageColor: allPagesColor
}
}
}
here is the implentation
var pageControl = PageControl()
and viewDidLoad add this code
let f = CGRect(x: CGFloat(0), y: CGFloat.adjustYAxis(91), width: CGFloat.adjustXAxis(100), height: CGFloat.adjustYAxis(6))
pageControl = PageControl(frame:f)
pageControl.totalPages = 5
pageControl.tag = 716
pageControl.delegate = self
pageControl.allPagesColor = UIColor(hexString:"#afc6de")
let firstMood = self.moodsArray[0] as! MoodsModel
pageControl.currentPageColor = UIColor(hexString: firstMood.MoodBackGroundColor!)
pageControl.currentPage = 0
pageControl.padding = 10
self.pageViewController.view.addSubview(pageControl)
and change you page number in delegate methods just like that
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController?
{
let viewController = viewController as! PageContentViewController
var index = viewController.pageIndex as Int
if(index == 0 || index == NSNotFound)
{ return nil
}
index -= 1
pageControl.currentPage = index
return self.getViewControllerAtIndex(index: index)
}
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController?
{
let viewController = viewController as! PageContentViewController
var index = viewController.pageIndex as Int
if(( index == NSNotFound))
{
return nil
}
index += 1
if(index == arrPageTitle.count)
{
return nil
}
pageControl.currentPage = index
return self.getViewControllerAtIndex(index: index)
}
Related
Is it possible to reverse the animation direction for UIPageViewController for right-to-left languages when using UIPageViewController.TransitionStyle.scroll?
Swiping left and right works inverts correctly for right-to-left, but my next button solution animates in the wrong direction (animates like it does for left-to-right).
My current solution to do so is to set pageControl.semanticContentAttribute = .forceLeftToRight, and to inverse the viewModels so that index 0 for right-to-left is the last index (viewModels.count - 1), as well as inverting the animation direction + viewControllerBefore/viewControllerAfter, e.g.:
viewControllerBefore becomes: nextIndex = isRightToLeft ? vc.index - 1 : vc.index + 1(and vice-versa for viewControllerAfter)
nextTapped's inDirection parameter for transitionFrom changes to isRightToLeft ? .reverse : .forward
Change loadFirstPage to set the startIndex to numberOfPages - 1 (aka viewModels.count - 1) rather than 0
Reverse the view models so index 0 is viewModels.count - 1 (in init):
if isRightToLeft {
viewModels = viewModels.map({ viewModel in
ViewModel(index: (numberOfPages - viewModel.index - 1), text: viewModel.text, color: viewModel.color)
}).reversed()
But I would like a solution that simply changes the animation direction out-of-the-box, like it seems to be possible when changing the spineLocation for UIPageViewController.TransitionStyle.pageCurl, like this question states.
Animations
Note that for the How it is by default the page comes from the right, but the page control indicator moves in the opposite (expected) direction.
How it is by default
Expected Functionality
Default Implementation:
import UIKit
struct ViewModel {
var index: Int
var text: String
var color: UIColor
}
class DummyViewController : UIViewController {
let label = UILabel()
let vm: ViewModel
let index: Int
init(vm: ViewModel) {
self.vm = vm
self.index = vm.index
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
override func loadView() {
let view = UIView()
view.backgroundColor = vm.color
let label = UILabel()
label.frame = CGRect(x: 20, y: 200, width: 200, height: 20)
label.textColor = .black
label.text = vm.text
view.addSubview(label)
self.view = view
}
}
class ViewController : UIViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
let pageControl = UIPageControl()
let nextButton = UIButton()
var isRightToLeft: Bool {
traitCollection.layoutDirection == .rightToLeft
}
var currentIndex: Int = 0 {
didSet {
pageControl.currentPage = currentIndex
}
}
var numberOfPages: Int { return viewModels.count }
var viewModels: [ViewModel] = [
ViewModel(index: 0, text: "First", color: .red),
ViewModel(index: 1, text: "Second", color: .blue),
ViewModel(index: 2, text: "Third", color: .green),
]
required init?(coder: NSCoder) {
super.init(coder: coder)
pageViewController.dataSource = self
pageViewController.delegate = self
pageControl.currentPage = currentIndex
pageControl.numberOfPages = numberOfPages
nextButton.setTitle("Next", for: .normal)
nextButton.setTitleColor(.black, for: .normal)
}
override func viewDidLoad() {
addChild(pageViewController)
pageViewController.didMove(toParent: self)
view.addSubview(pageViewController.view)
view.addSubview(pageControl)
view.addSubview(nextButton)
view.subviews.forEach { $0.translatesAutoresizingMaskIntoConstraints = false }
NSLayoutConstraint.activate([
pageViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
pageViewController.view.leftAnchor.constraint(equalTo: view.leftAnchor),
pageViewController.view.rightAnchor.constraint(equalTo: view.rightAnchor),
pageViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
pageControl.centerXAnchor.constraint(equalTo: view.centerXAnchor),
pageControl.centerYAnchor.constraint(equalTo: nextButton.centerYAnchor),
nextButton.trailingAnchor.constraint(equalTo: view.trailingAnchor),
nextButton.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor)
])
nextButton.addTarget(self, action: #selector(nextTapped), for: .primaryActionTriggered)
loadFirstPage()
}
func loadFirstPage() {
let startIndex: Int
startIndex = 0
guard let start = viewControllerAtIndex(startIndex) else {
return
}
pageViewController.setViewControllers([start], direction: .forward, animated: true, completion: nil)
}
func transitionFrom(index: Int, inDirection direction: UIPageViewController.NavigationDirection) {
let nextIndex = direction == .forward ? index + 1 : index - 1
guard let next = viewControllerAtIndex(nextIndex) else {
return
}
pageViewController.setViewControllers([next], direction: direction, animated: true, completion: { finished in
self.pageViewController(self.pageViewController, didFinishAnimating: finished, previousViewControllers: [], transitionCompleted: finished)
})
}
func viewControllerAtIndex(_ index: Int) -> DummyViewController? {
guard index >= 0 && index < numberOfPages else { return nil }
let viewModel = viewModels[index]
return DummyViewController(vm: viewModel)
}
#objc
func nextTapped(_ sender: UIButton) {
guard currentIndex < numberOfPages - 1 else {
print("ending because \(currentIndex)")
return
}
let direction: UIPageViewController.NavigationDirection = .forward
transitionFrom(index: currentIndex, inDirection: direction)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let vc = viewController as? DummyViewController else { return nil }
let nextIndex: Int = vc.index - 1
return viewControllerAtIndex(nextIndex)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let vc = viewController as? DummyViewController else { return nil }
let nextIndex: Int = vc.index + 1
return viewControllerAtIndex(nextIndex)
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
guard let viewController = pageViewController.viewControllers?.first as? DummyViewController else { return }
currentIndex = viewController.index // Update currentIndex, which updates pageControl.currentPage
}
}
Manual reversing to support right-to-left (solution I'm trying to avoid).
Excluded ViewModel and DummyViewController as they're unchanged.
import UIKit
class ViewController : UIViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
let pageControl = UIPageControl()
let nextButton = UIButton()
var isRightToLeft: Bool {
traitCollection.layoutDirection == .rightToLeft
}
var currentIndex: Int = 0 {
didSet {
pageControl.currentPage = currentIndex
}
}
var numberOfPages: Int {
return viewModels.count
}
var viewModels: [ViewModel] = [
ViewModel(index: 0, text: "First", color: .red),
ViewModel(index: 1, text: "Second", color: .blue),
ViewModel(index: 2, text: "Third", color: .green),
]
required init?(coder: NSCoder) {
super.init(coder: coder)
pageViewController.dataSource = self
pageViewController.delegate = self
pageControl.currentPage = currentIndex
pageControl.numberOfPages = numberOfPages
pageControl.semanticContentAttribute = .forceLeftToRight
nextButton.setTitle("Next", for: .normal)
nextButton.setTitleColor(.black, for: .normal)
if isRightToLeft { // HERE
viewModels = viewModels.map({ viewModel in
ViewModel(index: (numberOfPages - viewModel.index - 1), text: viewModel.text, color: viewModel.color)
}).reversed()
}
}
override func viewDidLoad() {
addChild(pageViewController)
pageViewController.didMove(toParent: self)
view.addSubview(pageViewController.view)
view.addSubview(pageControl)
view.addSubview(nextButton)
view.subviews.forEach { $0.translatesAutoresizingMaskIntoConstraints = false }
NSLayoutConstraint.activate([
pageViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
pageViewController.view.leftAnchor.constraint(equalTo: view.leftAnchor),
pageViewController.view.rightAnchor.constraint(equalTo: view.rightAnchor),
pageViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
pageControl.centerXAnchor.constraint(equalTo: view.centerXAnchor),
pageControl.centerYAnchor.constraint(equalTo: nextButton.centerYAnchor),
nextButton.trailingAnchor.constraint(equalTo: view.trailingAnchor),
nextButton.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor)
])
nextButton.addTarget(self, action: #selector(nextTapped), for: .primaryActionTriggered)
loadFirstPage()
}
func loadFirstPage() {
let startIndex: Int
if isRightToLeft { // HERE
startIndex = numberOfPages - 1
currentIndex = startIndex
} else {
startIndex = 0
}
guard let start = viewControllerAtIndex(startIndex) else {
return
}
pageViewController.setViewControllers([start], direction: .forward, animated: true, completion: nil)
}
func transitionFrom(index: Int, inDirection direction: UIPageViewController.NavigationDirection) { // UNCHANGED
let nextIndex = direction == .forward ? index + 1 : index - 1
guard let next = viewControllerAtIndex(nextIndex) else {
return
}
pageViewController.setViewControllers([next], direction: direction, animated: true, completion: { finished in
self.pageViewController(self.pageViewController, didFinishAnimating: finished, previousViewControllers: [], transitionCompleted: finished)
})
}
func viewControllerAtIndex(_ index: Int) -> DummyViewController? {
guard index >= 0 && index < numberOfPages else { return nil }
let viewModel = viewModels[index]
return DummyViewController(vm: vm)
}
#objc
func nextTapped(_ sender: UIButton) {
guard (currentIndex < numberOfPages - 1 && !isRightToLeft) || (currentIndex > 0 && isRightToLeft) else { // HERE
return
}
let direction: UIPageViewController.NavigationDirection = isRightToLeft ? .reverse : .forward // HERE
transitionFrom(index: currentIndex, inDirection: direction)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let vc = viewController as? DummyViewController else {
return nil
}
let nextIndex: Int
if isRightToLeft { // HERE
nextIndex = vc.index + 1
} else {
nextIndex = vc.index - 1
}
return viewControllerAtIndex(nextIndex)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let vc = viewController as? DummyViewController else {
return nil
}
let nextIndex: Int
if isRightToLeft { // HERE
nextIndex = vc.index - 1
} else {
nextIndex = vc.index + 1
}
return viewControllerAtIndex(nextIndex)
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
guard let viewController = pageViewController.viewControllers?.first as? DummyViewController else {
return
}
currentIndex = viewController.index
}
}
Turns out this is much simpler than I realized - setting the direction simply changes the animation direction, it doesn't change what index/view controller is going to be presented.
As such, the solution is simply changing transitionFrom to:
func transitionTo(nextIndex: Int, inDirection direction: UIPageViewController.NavigationDirection) {
// Removed the `next` setting here in favor of getting it passed from `nextTapped`
guard let next = viewControllerAtIndex(nextIndex) else {
return
}
pageViewController.setViewControllers([next], direction: direction, animated: true, completion: { finished in
self.pageViewController(self.pageViewController, didFinishAnimating: finished, previousViewControllers: [], transitionCompleted: finished)
})
}
and changing nextTapped to:
#objc
func nextTapped(_ sender: UIButton) {
guard currentIndex < numberOfPages - 1 else { return }
let direction: UIPageViewController.NavigationDirection = isRightToLeft ? .reverse : .forward // This is key
transitionTo(nextIndex: currentIndex + 1, inDirection: direction)
}
Alternate, less intrusive solution
Alternatively, a less intrusive solution would be to subclass UIPageViewController and simply flip the animation direction if we're in a right-to-left locale.
private extension UIPageViewController.NavigationDirection {
var flipped: Self {
switch self {
case .forward:
return .reverse
case .reverse:
return .forward
#unknown default:
return .reverse
}
}
}
class LocalizedPageViewController: UIPageViewController {
override func setViewControllers(
_ viewControllers: [UIViewController]?,
direction: UIPageViewController.NavigationDirection,
animated: Bool,
completion: ((Bool) -> Void)? = nil
) {
let isRTL = view.effectiveUserInterfaceLayoutDirection == .rightToLeft
let direction = isRTL ? direction.flipped : direction
super.setViewControllers(viewControllers, direction: direction, animated: animated, completion: completion)
}
}
Then I would simply need to change my initialization of the UIPageViewController to LocalizedPageViewController, and no other changes are needed!
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
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
This question already has answers here:
Disable UIPageViewController bounce
(16 answers)
Closed 5 years ago.
I've been making a sample app with scrolling between UIViewControllers but the point is i want to disable the bouncing effect at the end of scrolling and when going back to the first UIViewController.
here is my code :
class PageViewController: UIPageViewController,UIPageViewControllerDataSource, UIPageViewControllerDelegate{
var index = 0
var identifiers: NSArray = ["FirstNavigationController", "SecondNavigationController"]
override func viewDidLoad() {
self.dataSource = self
self.delegate = self
let startingViewController = self.viewControllerAtIndex(self.index)
let viewControllers: NSArray = [startingViewController]
self.setViewControllers(viewControllers as [AnyObject], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
func viewControllerAtIndex(index: Int) -> UINavigationController! {
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
//first view controller = firstViewControllers navigation controller
if index == 0 {
return storyBoard.instantiateViewControllerWithIdentifier("FirstNavigationController") as! UINavigationController
}
//second view controller = secondViewController's navigation controller
if index == 1 {
return storyBoard.instantiateViewControllerWithIdentifier("SecondNavigationController") as! UINavigationController
}
return nil
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let identifier = viewController.restorationIdentifier
let index = self.identifiers.indexOfObject(identifier!)
//if the index is the end of the array, return nil since we dont want a view controller after the last one
if index == identifiers.count - 1 {
return nil
}
//increment the index to get the viewController after the current index
self.index = self.index + 1
return self.viewControllerAtIndex(self.index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let identifier = viewController.restorationIdentifier
let index = self.identifiers.indexOfObject(identifier!)
//if the index is 0, return nil since we dont want a view controller before the first one
if index == 0 {
return nil
}
//decrement the index to get the viewController before the current one
self.index = self.index - 1
return self.viewControllerAtIndex(self.index)
}
}
Take a look here :
I don't know if you can. UIPageController is not very customizable.
Personally, when I want to scroll between UIViewController, I prefer using a simple UIViewController, which will be a container, with an UIScrollView in it. Then I add programmatically all the controllers in the contentSize of the UIScrollView. You have to add all the controllers as child of the container.
UPDATE iOS 9
func setupDetailViewControllers() {
var previousController: UIViewController?
for controller in self.controllers {
addChildViewController(controller)
addControllerInContentView(controller, previousController: previousController)
controller.didMoveToParentViewController(self)
previousController = controller
}
}
func addControllerInContentView(controller: UIViewController, previousController: UIViewController?) {
contentView.addSubview(controller.view)
controller.view.translatesAutoresizingMaskIntoConstraints = false
// top
controller.view.topAnchor.constraintEqualToAnchor(contentView.topAnchor).active = true
// bottom
controller.view.bottomAnchor.constraintEqualToAnchor(contentView.bottomAnchor).active = true
// trailing
trailingContentViewConstraint?.active = false
trailingContentViewConstraint = controller.view.trailingAnchor.constraintEqualToAnchor(contentView.trailingAnchor)
trailingContentViewConstraint?.active = true
// leading
let leadingAnchor = previousController?.view.trailingAnchor ?? contentView.leadingAnchor
controller.view.leadingAnchor.constraintEqualToAnchor(leadingAnchor).active = true
// width
controller.view.widthAnchor.constraintEqualToAnchor(scrollView.widthAnchor).active = true
}
PREVIOUS ANSWER
Like so :
scrollView is an IBOutlet with contraints to each edge of the ContainerViewController
class ContainerViewController: UIViewController {
#IBOutlet var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView.bounces = false
scrollView.pagingEnabled = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewDidLayoutSubviews() {
initScrollView()
}
func initScrollView(){
let viewController1 = storyboard?.instantiateViewControllerWithIdentifier("ViewController1") as! ViewController1
viewController1.willMoveToParentViewController(self)
viewController1.view.frame = scrollView.bounds
let viewController2 = storyboard?.instantiateViewControllerWithIdentifier("ViewController2") as! ViewController2
viewController2.willMoveToParentViewController(self)
viewController2.view.frame.size = scrollView.frame.size
viewController2.view.frame.origin = CGPoint(x: view.frame.width, y: 0)
scrollView.contentSize = CGSize(width: 2 * scrollView.frame.width, height: scrollView.frame.height)
scrollView.addSubview(viewController2.view)
self.addChildViewController(viewController2)
viewController2.didMoveToParentViewController(self)
scrollView.addSubview(viewController1.view)
self.addChildViewController(viewController1)
viewController1.didMoveToParentViewController(self)
}}