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

Posted

技术标签:

【中文标题】如何在 iOS >=8.0 中更改 UIPageControl 的位置?【英文标题】:How to change position of UIPageControl in iOS >=8.0? 【发布时间】:2015-09-28 12:13:41 【问题描述】:

我有一个简单的 UIPageViewController,它在页面底部显示默认的 UIPageControl。我想知道是否可以修改 UIPageControl 的位置,例如位于屏幕顶部而不是底部。 我一直在环顾四周,只发现旧的讨论说我需要创建自己的 UIPageControl。 使用 ios8 和 9 会更简单吗? 谢谢。

【问题讨论】:

【参考方案1】:

是的,您可以为此添加自定义页面控制器。

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

- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController

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

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

【讨论】:

感谢您的回答。如果我理解的很好,去掉presentationCountForPageViewController: 和presentationIndexForPageViewController: 的部分就是隐藏默认的页面控制器,对吗? 这描述得很好,但并不完全有效。正如 Jiri 指出的那样,如果用户滑动了一部分但又取消了,这会中断(因为它会更新页面控件,就好像页面转换确实发生了一样)。我采用的解决方案是更新委托 didFinishAnimating 中的页面控件。此行为与 Apple 在 iOS 主屏幕上的行为相匹配(转换完成后更新页面控件) 我正在尝试按照答案中描述的方式进行操作,但点根本没有出现。我哪里出错了? @RohanSanap 你应该设置 pageControl 的 numberOfPages 属性【参考方案2】:

只需在 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
            
        
    

【讨论】:

此解决方案效果最佳,对我现有代码设置的干扰最少。 1) 如果 Apple 更改其实现细节,这可能会中断,2) UIPageViewController 的底部仍然显示空白空间,即使它不包含点。【参考方案3】:

覆盖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

【讨论】:

这可以很好地控制所有内容并防止添加新的 pageControl。谢谢!【参考方案4】:

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 代码的混合体。也许.. 让 pageContentView = previousViewControllers.first【参考方案5】:

Swift 4 版本 @Jan 的回答,修复了错误,当用户取消转换时:

首先,您创建自定义 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:) 总是在转换完成时调用(它也更好地模仿标准页面控件的行为)。

最后,要移除标准页面控件,请务必移除UIPageViewControllerDataSourcepresentationCount(for:)presentationIndex(for:) 方法的实现——如果这些方法被实现,就会呈现标准页面控件。

所以,你不想在你的代码中包含这个:

func presentationCount(for pageViewController: UIPageViewController) -> Int 
    return self.dataSource.controllers.count



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

【讨论】:

【参考方案6】:

对于快速:

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
       
    

【讨论】:

什么是页面索引? :// “PageViewController”类型的值没有成员“pageController”【参考方案7】:

是的,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);

【讨论】:

【参考方案8】:

这是一种非常有效的方法,可以为 PageViewController 更改默认 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)
        
    


【讨论】:

【参考方案9】:

这是我的看法。此版本仅在动画完成时更改指示符,即当您到达目标页面时。

/** 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】:

确保将 pageControl 添加为子视图。 然后在

-(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】:
override func viewDidLayoutSubviews() 
    super.viewDidLayoutSubviews()
    for view in self.view.subviews
        if view is UIScrollView
            view.frame = UIScreen.main.bounds
        
        else if view is UIPageControl 
            view.backgroundColor = UIColor.clear
            view.frame.origin.y = self.view.frame.size.height - 75
        
    

【讨论】:

【参考方案13】:

Swift 5 & iOS 13.5(手动布局)

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

快速笔记

    您必须在pageControl 上设置numberOfPages 属性。 要调整pageControl 的大小,您可以使用pageControl.size(forNumberOfPages: count)。 确保删除 presentationCount(...)presentationIndex(...) 以移除默认页面控件。 使用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]
    


【讨论】:

【参考方案14】:

只需获取第一个视图控制器并在 didFinishAnimating 中找出索引:

 func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) 

    pageControl.currentPage = onboardingViewControllers.index(of: pageViewController.viewControllers!.first!)!

【讨论】:

以上是关于如何在 iOS >=8.0 中更改 UIPageControl 的位置?的主要内容,如果未能解决你的问题,请参考以下文章

有 iOS 9 项目支持 iOS 8

如何更改 iPhone 部署目标

Mysql 8.0:如何更改过程定义器

iOS 部署目标设置为 7.0,但此平台支持的部署目标版本范围为 8.0 到 12.1。 (在目标“反应”中)

如何在 Xcode 6.1 中安装 iOS 7.0 和 iOS 8.0 模拟器?

如何在IOS SDK 8.0中获取当前位置的经纬度