如何在iOS >=8.0中更改UIPageControl的位置?

19

我有一个简单的UIPageViewController,它在页面底部显示默认的UIPageControl。我想知道是否可以修改UIPageControl的位置,比如将其放在屏幕顶部而不是底部。 我一直在寻找答案,但只找到了旧帖子,说我需要创建自己的UIPageControl。 在iOS8和9中这个问题更简单吗? 谢谢。

14个回答

40

是的,您可以添加自定义页面控制器来实现。

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];

然后移除

- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController

and
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController

然后再添加另一个委托方法:

- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray<UIViewController *> *)pendingViewControllers
 {
     PageContentViewController *pageContentView = (PageContentViewController*) pendingViewControllers[0];
     self.pageControl.currentPage = pageContentView.pageIndex;
 }

1
谢谢您的回答。如果我理解正确,删除 presentationCountForPageViewController: 和 presentationIndexForPageViewController: 的部分是为了隐藏默认的页面控制器,对吗? - Nguyen Thu
5
这个描述很好,但并不完全有效。正如Jiri所指出的那样,如果用户部分滑动然后取消了(因为它更新页面控制器,就好像页面转换实际上已经发生了),则会出现问题。我选择的解决方案是在delegate didFinishAnimating中更新页面控制器。这种行为与苹果的行为相匹配,例如iOS主屏幕(在转换完成后更新页面控制)。 - davew
我正在尝试按照答案中描述的方法操作,但是点根本没有出现。我做错了什么吗? - Rohan Sanap
@RohanSanap 你应该设置 pageControl 的 numberOfPages 属性。 - Mohan Meruva

10

只需在 PageViewController 子类中查找 PageControl 并设置框架、位置或您想要的任何内容即可。

override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        for subView in view.subviews {
            if  subView is  UIPageControl {
                subView.frame.origin.y = self.view.frame.size.height - 164
            }
        }
    }

这个解决方案在对我的现有代码设置干扰最小的情况下效果最佳。 - Karthik Kannan
  1. 如果苹果更改其实现细节,这可能会出现问题。
  2. 即使不包含点,UIPageViewController的底部仍会显示空白空间。
- Zorayr

9

重写pageviewcontroller的viewDidLayoutSubviews()方法并使用以下代码:

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
}

2
这样可以非常好地控制一切,并防止添加新的页面控件。谢谢! - chrilith

7

以下是 @Jan 的答案的 Swift 4 版本,修复了当用户取消过渡时的错误问题:

首先,您需要创建自定义 pageControl:

let pageControl = UIPageControl()

您可将其添加到视图中,并根据需要进行定位:

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)
])

然后您需要初始化pageControl

self.pageControl.numberOfPages = self.dataSource.controllers.count

最后,您需要实现 UIPageViewControllerDelegate 及其方法 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
}

现在与@Jan的答案相比,我们使用pageViewController(_:didFinishAnimating:previousViewControllers:transitionCompleted:)(如上所示)更新self.pageControl.currentPage,而不是使用pageViewController(_:willTransitionTo:)。这样就解决了取消转换的问题-当转换完成时总是会调用pageViewController(_:didFinishAnimating:previousViewControllers:transitionCompleted:)(它还更好地模拟了标准页面控件的行为)。
最后,要删除标准页面控制,请确保删除UIPageViewControllerDataSource的和presentationIndex(for:)方法的实现-如果实现了这些方法,标准页面控制将被呈现。
因此,在您的代码中,您不希望有以下内容:
func presentationCount(for pageViewController: UIPageViewController) -> Int {
    return self.dataSource.controllers.count
}


func presentationIndex(for pageViewController: UIPageViewController) -> Int {
    return 0
}

7

Shameerjan的答案非常好,但是它需要执行另一个委托方法才能正常工作:

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;
    }
}

这是由于如果您不这样做,如果您稍微移动页面控件,它将返回到先前的位置 - 但页面控件将显示不同的页面。
希望对你有所帮助!

这是一个不错的评论,但你混合了Swift和Objective-C代码。也许可以这样写:let pageContentView = previousViewControllers.first。 - EPage_Ed

4

对于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)

记得使用pageController.numberOfPages,并将pageView委托给代理。
然后删除。
func presentationCountForPageViewController(
    pageViewController: UIPageViewController
) -> Int

并且

func presentationIndexForPageViewController(
    pageViewController: UIPageViewController
) -> Int

然后再添加另一个代理方法:

func pageViewController(
    pageViewController: UIPageViewController,
    willTransitionToViewControllers pendingViewControllers:[UIViewController]){
       if let itemController = pendingViewControllers[0] as? PageContentViewController {
           self.pageController.currentPage = itemController.pageIndex
       }
    }
}

什么是页面索引?:/ - David Seek
类型“PageViewController”没有成员“pageController”的值。 - Jesus Rodriguez

3

是的,Shameerjan的回答非常好。但是你可以使用默认的页面指示器而不是添加另一个页面控件:

- (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;
}

然后将UIPageViewController的大小扩展以覆盖底部间隙:

//somewhere in your code
self.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height+40);

3

这里有一种非常有效的方法可以改变PageViewController的默认PageControl位置,而无需创建新的PageControl...

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)
        }
    }
}
}

1

Swift 5 & iOS 13.5(使用手动布局)

下面的示例使用自定义的UIPageControl,位于UIPageViewController底部中心位置。要使用此代码,请将MemberProfilePhotoViewController替换为您作为页面使用的视图控制器类型。

快速说明

  1. 您必须在pageControl上设置numberOfPages属性。
  2. 要调整pageControl的大小,可以使用pageControl.size(forNumberOfPages: count)
  3. 请确保删除presentationCount(...)presentationIndex(...)以删除默认的页面控件。
  4. 使用UIPageViewControllerDelegatedidFinishAnimating似乎更好地定时更新pageControl.currentPage

代码:

import Foundation
import UIKit

class MemberProfilePhotosViewController: UIPageViewController {
    
    private let profilePhotoURLs: [URL]
    private let profilePhotoViewControllers: [MemberProfilePhotoViewController]
    private var pageControl: UIPageControl?
    
    // MARK: - Initialization
    
    init(profilePhotoURLs: [URL]) {
        self.profilePhotoURLs = profilePhotoURLs
        profilePhotoViewControllers = profilePhotoURLs.map { (profilePhotoURL) -> MemberProfilePhotoViewController in
            MemberProfilePhotoViewController(profilePhotoURL: profilePhotoURL)
        }
        super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError()
    }
    
    // MARK: UIViewController
    
    override func loadView() {
        super.loadView()
        pageControl = UIPageControl(frame: CGRect.zero)
        pageControl!.numberOfPages = profilePhotoViewControllers.count
        self.view.addSubview(pageControl!)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        dataSource = self
        delegate = self
        if let firstViewController = profilePhotoViewControllers.first {
            setViewControllers([firstViewController],
                               direction: .forward,
                               animated: true,
                               completion: nil)
        }
    }
    
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        if let pageControl = pageControl {
            let pageControlSize = pageControl.size(forNumberOfPages: profilePhotoViewControllers.count)
            pageControl.frame = CGRect(
                origin: CGPoint(x: view.frame.midX - pageControlSize.width / 2, y: view.frame.maxY - pageControlSize.height),
                size: pageControlSize
            )
        }
    }
    
    // MARK: Private Helpers
    
    private func indexOf(_ viewController: UIViewController) -> Int? {
        return profilePhotoViewControllers.firstIndex(of: viewController as! MemberProfilePhotoViewController)
    }
}

extension MemberProfilePhotosViewController: UIPageViewControllerDelegate {
    func pageViewController(_ pageViewController: UIPageViewController,
                            didFinishAnimating finished: Bool,
                            previousViewControllers: [UIViewController],
                            transitionCompleted completed: Bool) {
        guard let selectedViewController = pageViewController.viewControllers?.first else { return }
        if let indexOfSelectViewController = indexOf(selectedViewController) {
            pageControl?.currentPage = indexOfSelectViewController
        }
    }
}

extension MemberProfilePhotosViewController: UIPageViewControllerDataSource {
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        guard let viewControllerIndex = profilePhotoViewControllers.firstIndex(of: viewController as! MemberProfilePhotoViewController) else {
            return nil
        }
        let previousIndex = viewControllerIndex - 1
        guard previousIndex >= 0 else {
            return nil
        }
        guard profilePhotoViewControllers.count > previousIndex else {
            return nil
        }
        return profilePhotoViewControllers[previousIndex]
    }
    
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        guard let viewControllerIndex = profilePhotoViewControllers.firstIndex(of: viewController as! MemberProfilePhotoViewController) else {
            return nil
        }
        let nextIndex = viewControllerIndex + 1
        let profilePhotoViewControllersCount = profilePhotoViewControllers.count
        guard profilePhotoViewControllersCount != nextIndex else {
            return nil
        }
        guard profilePhotoViewControllersCount > nextIndex else {
            return nil
        }
        
        return profilePhotoViewControllers[nextIndex]
    }
}


1
这是我的看法。这个版本只在动画完成时,即到达目标页面时才更改指示器。
/** 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
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接