如何实现使用多个ViewControllers的UIPageViewController

35

我一直在开发一个简单的测试应用程序,以了解UIPageViewController的各个方面。我的应用程序可以正常工作,但我不确定我的执行方式是否最佳。希望你们中的一些人能指导我正确的方向。

为了获得基本的理解,我使用了这个教程作为起点。 http://www.appcoda.com/uipageviewcontroller-storyboard-tutorial/

该教程创建了一个应用程序,每个页面由UIPageViewController呈现一个viewController。然而,我需要利用UIPageViewController滚动浏览具有完全不同布局的页面。因此,为了进一步学习,我创建了一个主细节应用程序,在详细信息视图中使用UIPageViewController来显示三个不同的视图控制器。我仅为此测试应用程序显示图片和标签,但我当前正在构建的应用程序有三个视图控制器,其中每个控制器将包含一个表格视图、图像视图和文本视图,或一些文本字段。

这是我的测试应用程序的故事板。

Storyboard

我将DetailViewController作为PageViewController的数据源。在DVC的viewDidLoad中,我以这种方式建立标签和图像,这些标签和图像将用于三个内容视图控制器firstViewControllersecondViewControllerthirdViewController

if ([[self.detailItem description] isEqualToString:@"F14's"]) {
    //Here the page titles and images arrays are created 
    _pageTitles = @[@"Grim Reapers", @"Breakin the Barrier!", @"Top Gun"];
    _pageImages = @[@"F14_Grim.jpg", @"F14boom.jpg", @"F14_topgun.jpg"];

    //Here I call a method to instantiate the viewControllers 
    FirstController *selectedController = [self viewControllerAtIndex:0];
    SecondController *nextController = [self viewControllerAtIndex:1];
    ThirdController *lastController = [self viewControllerAtIndex:2];
    [_vc addObject:selectedController];
    [_vc addObject:nextController];
    [_vc addObject:lastController];
    _vc1 = @[selectedController];


} else if ([[self.detailItem description] isEqualToString:@"F35's"]){
    //code is above is repeated

以下是实例化视图控制器的方法

- (UIViewController *)viewControllerAtIndex:(NSUInteger)index
{
    if (([self.pageTitles count] == 0) || (index >= [self.pageTitles count])) {
        return nil;
    }

    // Create a new view controller and pass suitable data.
    if (index == 0) {
        FirstController *fvc = [self.storyboard instantiateViewControllerWithIdentifier:@"FirstPageController"];
        fvc.imageFile = self.pageImages[index];
        fvc.titleText = self.pageTitles[index];
        fvc.pageIndex = index;
        if ([_vc count]) {
             //Here I have to replace the viewController each time it is recreated
             [_vc replaceObjectAtIndex:0 withObject:fvc];
        }
        return fvc;
    } else if (index == 1) {
//Code is repeated for remaining viewControllers

viewDidLoad中的代码是我感觉自己正在做不必要的工作的一个领域。我认为加载DVC时没有必要实例化所有三个视图控制器,但我不知道如何提供一个数组给UIPageViewControllerDataSource协议方法(viewControllerBeforeViewControllerviewControllerAfterViewController)。

这是viewControllerBefore..方法。

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{

    NSUInteger index = [_vc indexOfObject:viewController];

    if ((index == 0) || (index == NSNotFound)) {
        return nil;
    }

    index--;

    //notice here I call my instantiation method again essentially duplicating work I have already done!
    return [self viewControllerAtIndex:index];
}

总之,似乎我每次从一页滑到另一页时都不必要地重新创建视图控制器。这只是pageViewController的工作方式还是我过于复杂了。任何建议都将不胜感激!

解决方案

Matt提出了一个非常简单的解决方案,使用标识符。在我的Storyboard中,我只需勾选使用我已经实现的Storyboard标识符作为还原标识符的框。

RestorationId

然后在viewDidLoad中,不再创建一个视图控制器数组,而是创建一个与还原标识符匹配的字符串数组。

if ([[self.detailItem description] isEqualToString:@"F14's"]) {
    _pageTitles = @[@"Grim Reapers", @"Breakin the Barrier!", @"Top Gun"];
    _pageImages = @[@"F14_Grim.jpg", @"F14boom.jpg", @"F14_topgun.jpg"];
    FirstController *selectedController = [self viewControllerAtIndex:0];
    [_vc addObject:@"FirstPageController"];
    [_vc addObject:@"SecondPageController"];
    [_vc addObject:@"ThirdPageController"];
    _vc1 = @[selectedController];

最后,要确定代理方法中的索引,请执行以下操作而不是我之前所做的事情:

NSString * ident = viewController.restorationIdentifier;
NSUInteger index = [_vc indexOfObject:ident];

现在它可以在不必要地实例化视图控制器的情况下正常工作。

最后要注意的是,如果有人正在使用我在这里提供的内容,可以从viewControllerAtIndex:方法中删除以下代码片段。

if ([_vc count]) {
     //Here I have to replace the viewController each time it is recreated
     [_vc replaceObjectAtIndex:0 withObject:fvc];
}

@fguespe 你应该在这里找到解决这个问题所需的一切...不确定你还需要什么...请注意,我在我的问题底部添加了一个解决方案。 - Ben
这是一个很棒的解决方案。但我在获取其他函数中使用的正确页面索引时遇到了麻烦。您是将NSString * ident等放在根控制器中还是放在模型的-(NSUInteger)indexOfViewController:(embBaseViewController *)viewController中? - malaki1974
4
嗨@Ben,尽管对于许多人来说,代码已经相当易于理解了,但看到一个工作示例会很有帮助。你能否在GitHub上发布你的代码呢? - Matt Privman
为什么要将图像和字体添加到数组中?一个不够吗? - Darius Miliauskas
根据这篇元帖:您应该将问题回滚到其原始形式。如果您愿意,可以自己回答问题或接受答案。永远不要在问题中包含答案。 - mfaani
显示剩余5条评论
4个回答

39
首先,你是完全正确的,构成UIPageViewController“页面”的视图控制器可以完全不同。 没有任何东西能说明它们必须是相同的视图控制器类的实例。
现在让我们来谈谈实际问题,也就是您非常明智地需要一种方法来提供给定当前视图控制器的下一个或上一个视图控制器。 也就是说,在使用页面视图控制器时,这确实是主要问题。
持有视图控制器数组可能并不会真正糟糕。 毕竟,视图控制器是轻量级对象(重量级对象是视图)。 但是,您处理方式似乎很笨拙。
我的建议是:如果您将视图控制器实例保留在storyboard中,为什么不只保留它们的标识符数组?现在你有一个包含三个字符串的数组。 多简单啊? 您还需要一个单一的实例变量,用于跟踪哪个标识符对应于其视图用作当前页面的视图控制器(以便您可以计算出哪个是“下一个”或“上一个”); 这可以是一个索引到数组中的整数。
然后,每次用户“翻页”时实例化视图控制器根本没有问题。在需要视图控制器时,这就是你要做的。 而且,您可以通过标识符轻松实现这一点。
最后,请注意,如果您使用页面视图控制器的滚动样式,则甚至不必这样做,因为页面视图控制器缓存视图控制器并停止调用委托方法(或者至少调用它们较少)。

Matt,你的建议太棒了!尴尬的是我花了几个小时才想出如何实现它。这是因为我知道无法使用Storyboard标识符来确定当前正在使用的视图控制器。直到我即将放弃时,我偶然发现了恢复标识符。一旦我发现了这个小宝石,就变得轻而易举了。感谢你的建议。我感觉我的新执行方式要少得多“笨拙”。 - Ben
恢复标识符的想法可能比我的想法更优雅,我的想法只是跟踪标识符数组中当前索引的位置。 - matt
你好!我有一个类似的情况,但我的控制器视图是一个xib文件。我在视图中设置了恢复ID,但当我执行controller.restorationIdentifier时,它返回nil... 有什么提示吗? - Nuno Gonçalves
干得好,Matt。你能看一下这篇帖子吗?也许你可以帮他。http://stackoverflow.com/questions/29745656/uipageviewcontroller-instantiating-viewcontrollers-with-storyboard-id-objectiv/29745910#29745910 - Timo Cengiz
我有一个问题:在使用UIPageViewControllerDataSource协议时,方法..After..Before是否会在推入下一视图控制器之前弹出当前正在显示的视图控制器。。?还是我们需要自己处理这个问题?(假设在UIPageViewController内嵌了一个NavigationController - Dravidian
@matt 你有使用不同控制器的Swift示例,其中uiPageviewController在另一个控制器中的例子吗? - Iraniya Naynesh

8
我在寻找解决同样问题的方案时发现了这个问题-感谢Matt提供的指导和Ben提供的解决方案描述。
我建立了一个示例项目来理解这个问题,因为我注意到有些评论要求提供示例代码,所以我将其上传到了GitHub上。我的解决方案通过以下方式模仿了Matt提出的方法和Ben的陈述解决方案:
  • 在Storyboard中为PageViewController的每个视图控制器设置恢复ID。
  • 使用包含上述恢复ID的NSString对象的NSArray。
  • 根据此配置实例化适当的视图控制器。
此外,我尝试解决的实施问题需要能够从子视图控制器向后/向前导航,因此该示例项目还支持通过请求根视图控制器转到上一页或下一页(也可以应用于转到特定页面)来支持该功能。 GitHub上的示例代码

顺便说一下:我本来希望能像UITabBarController一样,可以在Storyboard中简单地连接所有内容并指定视图控制器的顺序,但不幸的是,似乎我们还没有达到那个程度(截至Xcode 6 / iOS 8.1)。然而,这种解决方案所需的代码非常简洁明了。


1
基本上,我成功地找到了一种略微不同的方式,它基于XCode 6.4提供的模板(基于页面的应用程序)方法和其他作者的见解(包括this,@Ben来自其他答案)。
- (void)viewDidLoad {
    [super viewDidLoad];

    self.viewControllersArray = @[@"FirstViewController", @"SecondViewController"];
...
}

...

- (UIViewController *)viewControllerAtIndex:(NSUInteger)index {

    UIViewController *childViewController = [self.storyboard instantiateViewControllerWithIdentifier:[self.viewControllersArray objectAtIndex:index]];
    //childViewController.index = index;
    return childViewController;

}

- (NSUInteger)indexOfViewController:(UIViewController *)viewController {
    NSString *restorationId = viewController.restorationIdentifier;
    return [self.viewControllersArray indexOfObject:restorationId];
}

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {

    NSUInteger index = [self indexOfViewController:(UIViewController *)viewController];
    if ((index == 0) || (index == NSNotFound)) {
        return nil;
    }

    index--;
    return [self viewControllerAtIndex:index];
}

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {

    NSUInteger index = [self indexOfViewController:(UIViewController *)viewController];
    if (index == NSNotFound) {
        return nil;
    }

    index++;
    if (index == [self.viewControllersArray count]) {
        return nil;
    }
    return [self viewControllerAtIndex:index];
}

0
@interface ViewController ()
{
    NSMutableArray * StoryboardIds;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    StoryboardIds = [[NSMutableArray alloc]init];
    [StoryboardIds addObject:@"vc1"];
    [StoryboardIds addObject:@"vc2"];

    UIViewController *selectedController = [self viewControllerAtIndex:0];

    self.ProfilePageViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"PageControl"];
    self.ProfilePageViewController.dataSource = self;

    _viewcontrollers = [NSMutableArray new];

    [_viewcontrollers addObject:selectedController];

    [self.ProfilePageViewController setViewControllers:_viewcontrollers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];

    // Change the size of page view controller
    self.ProfilePageViewController.view.frame = CGRectMake(0, 0, self.bodypage.frame.size.width, self.bodypage.frame.size.height);

    [self addChildViewController:self.ProfilePageViewController];
    [self.ProfilePageViewController.view setFrame:self.bodypage.bounds];
    [self.bodypage addSubview:self.ProfilePageViewController.view];
    [self.ProfilePageViewController didMoveToParentViewController:self];

}
- (UIViewController *)viewControllerAtIndex:(NSUInteger)index
{
    if (([StoryboardIds count] == 0) || (index >= [StoryboardIds count])) {
        return nil;
    }

    if (index == 0) {
        vc1 *fvc = [self.storyboard instantiateViewControllerWithIdentifier:@"vc1"];
        fvc.Pageindex = index;
        if ([_viewcontrollers count]) {
            [_viewcontrollers replaceObjectAtIndex:0 withObject:fvc];
        }
        return fvc;
    }
    else
    {
        vc2 *fvc = [self.storyboard instantiateViewControllerWithIdentifier:@"vc2"];
        fvc.Pageindex = index;
        if ([_viewcontrollers count]) {
            [_viewcontrollers replaceObjectAtIndex:0 withObject:fvc];
        }
        return fvc;
    }

}

-(NSUInteger)indexofViewController
{
    UIViewController *currentView = [self.ProfilePageViewController.viewControllers objectAtIndex:0];

    if ([currentView isKindOfClass:[vc2 class]]) {
        return 1;
    }
    else{
        return 0;
    }

}


- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {

    NSUInteger index = [self indexofViewController];

    if ((index == 0) || (index == NSNotFound)) {
        return nil;
    }

    index--;
    return [self viewControllerAtIndex:index];
}

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {

    NSUInteger index = [self indexofViewController];

    if (index == NSNotFound) {
        return nil;
    }

    index++;

    if (index == [StoryboardIds count]) {
        return nil;
    }
    return [self viewControllerAtIndex:index];
}

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