I have a simple UIPageViewController which displays the default UIPageControl at the bottom of the pages. I wonder if it's possible to modify the position of the UIPageControl, e.g. to be on top of the screen instead of the bottom. I've been looking around and only found old discussions that say I need to create my own UIPageControl. Is this thing simpler with iOS8 and 9? Thanks.
问题:
回答1:
Yes, you can add custom page controller for that.
self.pageControl = [[UIPageControl alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height - 50, self.view.frame.size.width, 50)]; // your position
[self.view addSubview: self.pageControl];
then remove
- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController
and
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController
Then add another delegate method:
- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray<UIViewController *> *)pendingViewControllers
{
PageContentViewController *pageContentView = (PageContentViewController*) pendingViewControllers[0];
self.pageControl.currentPage = pageContentView.pageIndex;
}
回答2:
Override the viewDidLayoutSubviews() of the pageviewcontroller and use this
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// get pageControl and scroll view from view's subviews
let pageControl = view.subviews.filter{ $0 is UIPageControl }.first! as! UIPageControl
let scrollView = view.subviews.filter{ $0 is UIScrollView }.first! as! UIScrollView
// 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 pagecontroll
pageControl.translatesAutoresizingMaskIntoConstraints = false
pageControl.addConstraint(pageControl.heightAnchor.constraintEqualToConstant(35))
pageControl.backgroundColor = view.backgroundColor
// create constraints for pagecontrol
let leading = pageControl.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor)
let trailing = pageControl.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor)
let bottom = pageControl.bottomAnchor.constraintEqualToAnchor(scrollView.topAnchor, constant:8) // add to scrollview not view
// pagecontrol constraint to view
view.addConstraints([leading, trailing, bottom])
view.bounds.origin.y -= pageControl.bounds.maxY
}
回答3:
Just lookup PageControl in PageViewController subclass and set frame, location or whatever you want
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
for subView in view.subviews {
if subView is UIPageControl {
subView.frame.origin.y = self.view.frame.size.height - 164
}
}
}
回答4:
The Shameerjan answer is very good, but it needs one more thing to work properly, and that is implementation of another delegate method:
func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
// If user bailed our early from the gesture,
// we have to revert page control to previous position
if !completed {
let pageContentView = previousViewControllers[0] as! PageContentViewController;
self.pageControl.currentPage = pageContentView.pageIndex;
}
}
This is because if you don't, if you move the page control just so slightly, it will go back to previous position - but the page control will show different page.
Hope it helps!
回答5:
Swift 4 version of @Jan's answer with fixed bug when the user cancels transition:
First, you create custom pageControl:
let pageControl = UIPageControl()
You add it to the view and position it as you want:
self.view.addSubview(self.pageControl)
self.pageControl.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
self.pageControl.leftAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leftAnchor, constant: 43),
self.pageControl.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: -33)
])
Then you need to initialize the pageControl
:
self.pageControl.numberOfPages = self.dataSource.controllers.count
Finally, you need to implement UIPageViewControllerDelegate
, and its method pageViewController(_:didFinishAnimating:previousViewControllers:transitionCompleted:)
:
func pageViewController(_ pageViewController: UIPageViewController,
didFinishAnimating finished: Bool,
previousViewControllers: [UIViewController],
transitionCompleted completed: Bool) {
// this will get you currently presented view controller
guard let selectedVC = pageViewController.viewControllers?.first else { return }
// and its index in the dataSource's controllers (I'm using force unwrap, since in my case pageViewController contains only view controllers from my dataSource)
let selectedIndex = self.dataSource.controllers.index(of: selectedVC)!
// and we update the current page in pageControl
self.pageControl.currentPage = selectedIndex
}
Now in comparison with @Jan's answer, we update self.pageControl.currentPage
using pageViewController(_:didFinishAnimating:previousViewControllers:transitionCompleted:)
(shown above), instead of pageViewController(_:willTransitionTo:)
. This overcomes the problem of cancelled transition - pageViewController(_:didFinishAnimating:previousViewControllers:transitionCompleted:)
is called always when a transition was completed (it also better mimics the behavior of standard page control).
Finally, to remove the standard page control, be sure to remove implementation of presentationCount(for:)
and presentationIndex(for:)
methods of the UIPageViewControllerDataSource
- if the methods are implemented, the standard page control will be presented.
So, you do NOT want to have this in your code:
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return self.dataSource.controllers.count
}
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
return 0
}
回答6:
Yes, the Shameerjan answer is very good, but instead of adding another page control you can use default page indicator:
- (UIPageControl*)pageControl {
for (UIView* view in self.view.subviews) {
if ([view isKindOfClass:[UIPageControl class]]) {
//set new pageControl position
view.frame = CGRectMake( 100, 200, width, height);
return (id)view;
}
}
return nil;
}
and then extend the size of the UIPageViewController to cover up the bottom gap:
//somewhere in your code
self.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height+40);
回答7:
For swift:
self.pageController = UIPageControl(
frame: CGRect(
x: 0,
y: self.view.frame.size.height - 50,
width: self.view.frame.size.width,
height: 50
)
)
self.view.addSubview(pageController)
remember use pageController.numberOfPages and delegate the pageView
then remove
func presentationCountForPageViewController(
pageViewController: UIPageViewController
) -> Int
and
func presentationIndexForPageViewController(
pageViewController: UIPageViewController
) -> Int
Then add another delegate method:
func pageViewController(
pageViewController: UIPageViewController,
willTransitionToViewControllers pendingViewControllers:[UIViewController]){
if let itemController = pendingViewControllers[0] as? PageContentViewController {
self.pageController.currentPage = itemController.pageIndex
}
}
}
回答8:
here s a very effective way to change the position of the default PageControl for the PageViewController without having the need to create a new one ...
extension UIPageViewController {
override open func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
for subV in self.view.subviews {
if type(of: subV).description() == "UIPageControl" {
let pos = CGPoint(x: subV.frame.origin.x, y: subV.frame.origin.y - 75 * 2)
subV.frame = CGRect(origin: pos, size: subV.frame.size)
}
}
}
}
回答9:
Here's my take. This version only changes de indicator when the animation is finished, i.e. when you get to the destination page.
/** Each page you add to the UIPageViewController holds its index */
class Page: UIViewController {
var pageIndex = 0
}
class MyPageController : UIPageViewController {
private let pc = UIPageControl()
private var pendingPageIndex = 0
// ... add pc to your view, and add Pages ...
/** Will transition, so keep the page where it goes */
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController])
{
let controller = pendingViewControllers[0] as! Page
pendingPageIndex = controller.pageIndex
}
/** Did finish the transition, so set the page where was going */
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool)
{
if (completed) {
pc.currentPage = pendingPageIndex
}
}
}
回答10:
This is good, and with more change
var pendingIndex = 0;
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed {
pageControl.currentPage = pendingIndex
}
}
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
let itemController = pendingViewControllers.first as! IntroPageItemViewController
pendingIndex = itemController.itemIndex
}
回答11:
Make sure pageControl is added as subview. Then in
-(void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
CGRect frame = self.pageControl.frame;
frame.origin.x = self.view.frame.size.width/2 -
frame.size.width/2;
frame.origin.y = self.view.frame.size.height - 100 ;
self.pageControl.numberOfPages = self.count;
self.pageControl.currentPage = self.currentIndex;
self.pageControl.frame = frame;
}
回答12:
Just get the first view controller and find out the index in didFinishAnimating:
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
pageControl.currentPage = onboardingViewControllers.index(of: pageViewController.viewControllers!.first!)!
}