如何将UIPageControl元素放置在UIPageViewController中滑动页面的顶部?

61
关于这个AppCoda的教程,介绍如何使用UIPageViewController实现应用程序,我想在页面顶部使用自定义页面控件元素,而不是在底部。
当我将页面控制元素放置在将要显示的单个视图的顶部时,逻辑结果是控制元素随着页面视图从屏幕上滚动消失。
怎样才能将控制元素放置在视图的顶部,使得页面视图全屏(就像图片一样),以便用户可以看到固定控制元素下面的视图?
我附上一个示例截图 - 感谢AppCoda和Path

enter image description here

7个回答

55

我没有足够的声望来评论这个初始答案,但我真的很喜欢它。我改进了代码并将其转换为Swift,适用于下面的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()
    }
}

干净并且运行良好。无需维护任何东西,只需在Storyboard中将您的页面视图控制器实例化为此类的一个实例,或者让您的自定义页面视图控制器类继承自此类。


当我把这个for循环放在ViewDidLoad中时,我可以找到scrollView但找不到UIPageControl视图。我有什么遗漏吗? - Hakim
@Hakim 或许控制器缺少 UIPageViewController 类型?没有确切情况的示例很难说。 - Eric Ferreira
1
我会把这个收藏起来,因为它非常多才多艺且有用。非常感谢! - Suman Roy

51

在进一步调查和搜索后,我在stackoverflow上找到了一个解决方案

关键是向自定义UIPageControl元素发送以下消息:

[self.view bringSubviewToFront:self.pageControl];

AppCoda的教程是此解决方案的基础

RootViewController上方添加一个UIPageControl元素 - 带有箭头的视图控制器。

ViewController.m中创建相关的IBOutlet元素。

viewDidLoad方法中,您应该在添加所有子视图后调用以下代码作为最后一个方法。

[self.view bringSubviewToFront:self.pageControl];

为了基于当前内容视图的 pageIndex 分配当前页面,您可以在 UIPageViewControllerDataSource 方法中添加以下内容:

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

2
对我来说很奇怪,在增加/减少索引之前设置pageControl页面有效。我从该方法获取的视图控制器是新视图控制器而不是转换前的视图控制器 - Gokul
这一点并不奇怪,这是预期的。这些方法被调用在当前视图控制器上,以确定接下来会发生什么。 - Wayne
@sn3ek 如何创建相关的 IBOutlet 元素。 - VJVJ
@elavarasan 哪个元素?例如,self.pageControl 是视图上的 Page Control 元素的 IBOutlet... - sn3ek
2
在数据源方法中处理pageControl并不是一个好的建议。作为数据源,你唯一需要做的就是返回所请求的数据(避免其他逻辑)- 你无法控制这些方法何时被调用 - 这取决于实现。例如,在滚动之后,您可能会观察到PageViewController提前“预取”下一个相邻页面。这会完全干扰您可能潜入的任何检查逻辑(也可能解释了为什么@Gokul与@sn3ek得到不同的结果)。 (更好的方法:使用代理) - wardw

27

sn3ek你的回答帮了我很多。虽然我没有使用viewControllerCreation方法来设置当前页面。

我将我的ViewController也设为UIPageViewController的委托。然后我在那个方法中设置了PageControlCurrentPage。使用在原始文章中提到的ContentViewController中维护的pageIndex

- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
  APPChildViewController *currentViewController = pageViewController.viewControllers[0];
  [self.pageControl setCurrentPage:currentViewController.pageIndex];
}
不要忘记将这个添加到viewDidLoad
self.pageViewController.delegate = self;

为了跟进PropellerHead的评论,ViewController的接口将采用以下形式:

@interface ViewController : UIViewController <UIPageViewControllerDataSource, UIPageViewControllerDelegate>

2
只是为了明确一下,UIPageViewController的代理是UIPageViewControllerDelegate,因此您的ViewController接口将类似于这样: @interface ViewController : UIViewController <UIPageViewControllerDataSource, UIPageViewControllerDelegate> - PropellerHead

3

通过子类化UIPageViewController并如下覆盖viewDidLayoutSubviews方法,可以简单地实现相同的效果:

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

那么就不需要维护单独的UIPageControl之类的东西。

2
我实际上很喜欢这个答案。它运行良好。直到我把运行iOS 7.1的iPhone 4S放上去。此时它变成了无限循环。不确定为什么,但要小心。 - Travis Griggs

2
你需要实现自定义的 UIPageControl 并将其添加到视图中。正如其他人提到的那样,必须调用 view.bringSubviewToFront(pageControl)
我有一个 示例 视图控制器,其中包含设置使用故事板创建的自定义 UIPageControl(与 UIPageViewController 一起使用)的所有代码。
有两个方法需要实现以设置当前页面指示器。
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
        }
    }
}

1
这是我在查看其他回复后整理出来的 RxSwift/RxCocoa 答案。
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)
}

0
这是基于@zerotool上面的答案的Swifty 2答案。只需子类化UIPageViewController,然后添加此覆盖以查找滚动视图并调整其大小。然后获取页面控件并将其移动到其他所有内容的顶部。您还需要将页面控件的背景颜色设置为透明。最后两行代码可以放在您的应用程序委托中。
override func viewDidLayoutSubviews() {

    super.viewDidLayoutSubviews()

    var sv:UIScrollView?
    var pc:UIPageControl?

    for v in self.view.subviews{

        if v.isKindOfClass(UIScrollView) {

            sv = v as? UIScrollView

        }else if v.isKindOfClass(UIPageControl) {

            pc = v as? UIPageControl
        }
    }

    if let newSv = sv {

        newSv.frame = self.view.bounds
    }

    if let newPc = pc {

        self.view.bringSubviewToFront(newPc)
    }
}

let pageControlAppearance = UIPageControl.appearance()
pageControlAppearance.backgroundColor = UIColor.clearColor()

顺便说一下 - 我没有注意到任何无限循环,如上所述。


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