Regarding to this tutorial by AppCoda about how to implement a app with UIPageViewController I'd like to use a custom page control element on top of the pages instead of at the bottom.
When I just put a page control element on top of the single views which will be displayed, the logical result is that the control elements scrolls with the page view away from the screen.
How is it possible to put the control element on top of the views so the page views are full screen (like with an image) so the user can see the views underneath the fixed control element?
I attached an example screenshot - credits to AppCoda and Path:
I didn't have the rep to comment on the answer that originated this, but I really like it. I improved the code and converted it to swift for the below subclass of UIPageViewController:
class UIPageViewControllerWithOverlayIndicator: UIPageViewController {
override func viewDidLayoutSubviews() {
for subView in self.view.subviews as! [UIView] {
if subView is UIScrollView {
subView.frame = self.view.bounds
} else if subView is UIPageControl {
self.view.bringSubviewToFront(subView)
}
}
super.viewDidLayoutSubviews()
}
}
Clean and it works well. No need to maintain anything, just make your page view controller an instance of this class in storyboard, or make your custom page view controller class inherit from this class instead.
After further investigation and searching I found a solution, also on stackoverflow.
The key is the following message to send to a custom UIPageControl
element:
[self.view bringSubviewToFront:self.pageControl];
The AppCoda tutorial is the foundation for this solution:
Add a UIPageControl
element on top of the RootViewController
- the view controller with the arrow.
Create a related IBOutlet
element in your ViewController.m
.
In the viewDidLoad
method you should then add the following code as the last method you call after adding all subviews.
[self.view bringSubviewToFront:self.pageControl];
To assign the current page based on the pageIndex of the current content view you can add the following to the UIPageViewControllerDataSource
methods:
- (UIPageViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
// ...
index--;
[self.pageControl setCurrentPage:index];
return [self viewControllerAtIndex:index];
}
- (UIPageViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
// ...
index++;
[self.pageControl setCurrentPage:index];
// ...
return [self viewControllerAtIndex:index];
}
sn3ek Your answer got me most of the way there. I didn't set the current page using the viewControllerCreation
methods though.
I made my ViewController also
the delegate of the UIPageViewController
. Then I set the PageControl
's CurrentPage
in that method. Using the pageIndex
maintained I'm the ContentViewController
mention in the original article.
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
APPChildViewController *currentViewController = pageViewController.viewControllers[0];
[self.pageControl setCurrentPage:currentViewController.pageIndex];
}
don't forget to add this to viewDidLoad
self.pageViewController.delegate = self;
To follow up on PropellerHead's comment the interface for the ViewController
will have the form
@interface ViewController : UIViewController <UIPageViewControllerDataSource, UIPageViewControllerDelegate>
The same effect can be achieved simply by subclassing UIPageViewController and overriding viewDidLayoutSubviews as follows:
-(void)viewDidLayoutSubviews {
UIView* v = self.view;
NSArray* subviews = v.subviews;
if( [subviews count] == 2 ) {
UIScrollView* sv = nil;
UIPageControl* pc = nil;
for( UIView* t in subviews ) {
if( [t isKindOfClass:[UIScrollView class]] ) {
sv = (UIScrollView*)t;
} else if( [t isKindOfClass:[UIPageControl class]] ) {
pc = (UIPageControl*)t;
}
}
if( sv != nil && pc != nil ) {
// expand scroll view to fit entire view
sv.frame = v.bounds;
// put page control in front
[v bringSubviewToFront:pc];
}
}
[super viewDidLayoutSubviews];
}
Then there is no need to maintain a seperate UIPageControl and such.
You have to implement a custom UIPageControl
and add it to the view. As others have mentioned, view.bringSubviewToFront(pageControl)
must be called.
I have an example of a view controller with all the code on setting up a custom UIPageControl
(in storyboard) with UIPageViewController
There are 2 methods which you need to implement to set the current page indicator.
func pageViewController(pageViewController: UIPageViewController, willTransitionToViewControllers pendingViewControllers: [UIViewController]) {
pendingIndex = pages.indexOf(pendingViewControllers.first!)
}
func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed {
currentIndex = pendingIndex
if let index = currentIndex {
pageControl.currentPage = index
}
}
}
Here is a RxSwift/RxCocoa answer I put together after looking at the other replies.
let pages = Variable<[UIViewController]>([])
let currentPageIndex = Variable<Int>(0)
let pendingPageIndex = Variable<(Int, Bool)>(0, false)
let db = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
pendingPageIndex
.asDriver()
.filter { $0.1 }
.map { $0.0 }
.drive(currentPageIndex)
.addDisposableTo(db)
currentPageIndex.asDriver()
.drive(pageControl.rx.currentPage)
.addDisposableTo(db)
}
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
if let index = pages.value.index(of: pendingViewControllers.first!) {
pendingPageIndex.value = (index, false)
}
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
pendingPageIndex.value = (pendingPageIndex.value.0, completed)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With