My designer is asking that I display 3 dots in a UIPageViewController for 10 views.
When the first 3 view controllers display, the 0th dot should be highlighted; when the next 4 view controllers display, the 1st dot should be highlighted; when the final 3 view controllers display, the 2nd dot should be highlighted.
So far I'm able to display 3 dots in the UIPageControl, but the indicator dot just rotates around indicating the n%3
position as active.
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
return 3
}
I haven't seen any documentation on how to control with UIPageControl index is the active index, so I'm not sure if this is something Apple wants you to be able to override.
If there is a way to accomplish what I'm trying to do, I'd appreciate the help.
It turns out that what I'm trying to accomplish can't be done with a UIPageViewController. By default the UIPageControl in this class cannot be overridden directly.
Instead, I was able to use a combination of a UICollectionView (with a hack that allows it to resemble a UIPageViewController in its page changing effects) and a UIPageControl, as subviews to the same overarching UIViewController.
class MyPageViewController : UIViewController {
// MARK: subviews
private var collectionView:UICollectionView!
/// the collection layout controls the scrolling behavior of the collection view
private var collectionLayout = MyLayout()
private var pageControl = UIPageControl()
let CollectionViewCellReuseIdentifer = "CollectionViewCellReuseIdentifier"
// MARK: autolayout
private var autolayoutConstraints:[NSLayoutConstraint] = [NSLayoutConstraint]()
// MARK: constructors
init() {
super.init(nibName: nil, bundle: nil)
}
// MARK: UIViewController lifecycle methods
override func viewDidLoad() {
super.viewDidLoad()
self.setupView()
}
/**
Set up the collection view, page control, skip & log in buttons
*/
func setupView() {
self.setupCollectionView()
self.setupPageControl()
self.setupConstraints()
self.view.addConstraints(self.autolayoutConstraints)
}
/**
Set up the collection view
*/
func setupCollectionView() {
self.collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: self.collectionLayout)
self.collectionView.translatesAutoresizingMaskIntoConstraints = false
self.collectionView.registerClass(MyPageView.self, forCellWithReuseIdentifier: self.CollectionViewCellReuseIdentifer)
self.collectionView.dataSource = self
self.collectionView.delegate = self
self.collectionView.backgroundColor = UIColor.whiteColor()
self.collectionView.scrollEnabled = true
self.collectionView.decelerationRate = UIScrollViewDecelerationRateFast;
self.collectionLayout.minimumInteritemSpacing = 1
self.collectionLayout.minimumLineSpacing = 1
self.collectionLayout.scrollDirection = .Horizontal
self.collectionLayout.delegate = self
self.view.addSubview(self.collectionView)
}
/**
Set up view showing pagination dots for slideshow items
*/
func setupPageControl() {
self.pageControl.translatesAutoresizingMaskIntoConstraints = false
self.pageControl.numberOfPages = 3
self.pageControl.backgroundColor = UIColor.whiteColor()
self.view.addSubview(self.pageControl)
}
func setupConstraints() {
let views:[String:AnyObject] = [
"collectionView" : self.collectionView,
"pageControl" : self.pageControl,
]
self.autolayoutConstraints.appendContentsOf(
NSLayoutConstraint.constraintsWithVisualFormat(
"V:|[collectionView][pageControl]|",
options: .AlignAllCenterX,
metrics: nil,
views: views
)
)
self.autolayoutConstraints.appendContentsOf(
NSLayoutConstraint.constraintsWithVisualFormat(
"H:|[collectionView]|",
options: .AlignAllCenterY,
metrics: nil,
views: views
)
)
self.autolayoutConstraints.appendContentsOf(
NSLayoutConstraint.constraintsWithVisualFormat(
"H:|[pageControl]|",
options: NSLayoutFormatOptions(),
metrics: nil,
views: views
)
)
}
}
extension MyPageViewController : MyPageViewControllerDelegate {
func didSwitchToPage(imageIndex: Int) {
if imageIndex < 3 {
self.pageControl.currentPage = 0
} else if imageIndex < 7 {
self.pageControl.currentPage = 1
} else {
self.pageControl.currentPage = 2
}
self.pageControl.setNeedsDisplay()
}
}
The layout class was derived from an answer my coworker found when researching a similar issue. http://karmadust.com/centered-paging-with-preview-cells-on-uicollectionview/
/**
* Delegate for slide interactions
*/
protocol MyPageViewControllerDelegate {
/**
Triggered when a new page has been 'snapped' into place
- parameter imageIndex: index of the image that has been snapped to
*/
func didSwitchToPage(imageIndex: Int)
}
class MyLayout : UICollectionViewFlowLayout {
var delegate:MyPageViewControllerDelegate?
/*
Allows different items in the collection to 'snap' onto the current screen section.
Based off of http://karmadust.com/centered-paging-with-preview-cells-on-uicollectionview/
*/
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
if let cv = self.collectionView {
let cvBounds = cv.bounds
let halfWidth = cvBounds.size.width * 0.5;
let proposedContentOffsetCenterX = proposedContentOffset.x + halfWidth;
if let attributesForVisibleCells = self.layoutAttributesForElementsInRect(cvBounds) {
var candidateAttributes : UICollectionViewLayoutAttributes?
// the index of the image selected
var index:Int = 0
for attributes in attributesForVisibleCells {
// == Skip comparison with non-cell items (headers and footers) == //
if attributes.representedElementCategory != UICollectionElementCategory.Cell {
index++
continue
}
if let candAttrs = candidateAttributes {
let a = attributes.center.x - proposedContentOffsetCenterX
let b = candAttrs.center.x - proposedContentOffsetCenterX
if fabsf(Float(a)) < fabsf(Float(b)) {
candidateAttributes = attributes;
}
}
else { // == First time in the loop == //
candidateAttributes = attributes;
index++
continue;
}
}
// Beautification step , I don't know why it works!
if(proposedContentOffset.x == -(cv.contentInset.left)) {
return proposedContentOffset
}
if let delegate = self.delegate {
delegate.didSwitchToPage((candidateAttributes?.indexPath.row)!)
}
return CGPoint(x: floor(candidateAttributes!.center.x - halfWidth), y: proposedContentOffset.y)
}
}
// fallback
return super.targetContentOffsetForProposedContentOffset(proposedContentOffset)
}
}
Note: I trimmed down the actual code I used and replaced a bunch of names to make them more appropriate for examples. I did not run this specific code and did not test for errors in my IDE. That being said, the approach behind the code is solid.