如何实现利用多个ViewControllers的UIPageViewController

时间:2014-02-08 03:01:40

标签: ios uiviewcontroller uipageviewcontroller

我一直在研究一个简单的测试应用程序来学习UIPageViewController的细节。我有它工作,但我不相信我的执行是最好的方式。我希望你们中的一些人能指出我正确的方向。

为了获得基本的理解,我以本教程为出发点。 http://www.appcoda.com/uipageviewcontroller-storyboard-tutorial/

本教程会为viewController创建的每个页面创建一个使用UIPageViewController的应用。但是我需要利用UIPageViewController来滚动具有完全不同布局的页面。因此,为了使教程更进一步,我创建了一个主 - 详细信息应用程序,它在详细视图中使用UIPageViewController来显示三个不同的视图控制器。我坚持只为这个测试应用程序显示图像和标签,但我正在构建的应用程序有三个viewControllers,它们将包含tableview,imageView和textViews,或者一些textFields。

以下是我的测试应用的故事板。

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

以下是实例化viewControllers

的方法
- (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)提供数组。 / p>

以下是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在使用 标识符 时提出了一个非常简单的解决方案。在我的故事板中,我只是选中了使用我已经实现的故事板标识符作为恢复标识符的框

RestorationId

然后在viewDidLoad而不是创建一个viewControllers数组,只需创建一个与恢复标识符匹配的字符串数组。

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

4 个答案:

答案 0 :(得分:40)

首先,构成UIPageViewController“页面”的视图控制器本质上可以完全不同,这是绝对正确的。什么都没说,他们必须是相同的视图控制器类的实例。

现在让我们来看看实际问题,即在给定当前视图控制器的情况下,您非常明智地需要一种方法来提供下一个或上一个视图控制器。这确实是使用页面视图控制器时的主要问题。

持有一组视图控制器并不是真的很糟糕。毕竟,视图控制器是一个轻量级对象(它是重量级对象的视图)。但是,你处理这个问题的方式也很笨拙,你也是对的。

我的建议是:如果您要在故事板中保存视图控制器实例,那么为什么不保留其标识符的数组呢?现在你有一个包含三个字符串的数组。你有多简单?您还需要一个单独的实例变量来跟踪哪个标识符与视图控制器相对应,该视图控制器将其视图用作当前页面(以便您可以确定哪个是“下一个”或“以前”);这可能只是一个整数索引到数组。

每次用户“翻页”时,实例化视图控制器绝对没有错。当需要视图控制器时,这就是假设要执行的操作。您可以通过标识符轻松完成此操作。

最后,请注意,如果使用页面视图控制器的滚动样式,您甚至不必这样做,因为页面视图控制器缓存视图控制器并停止调用委托方法(或者至少调用他们少了。)

答案 1 :(得分:8)

遇到这个问题时我正在寻找解决同样问题的方法 - 感谢Matt提供的指导以及Ben提供的解决方案说明。

我自己构建了一个示例项目以了解这一点,因为我发现了一些请求示例代码的注释,我已将其上传到GitHub。我的解决方案模仿了Matt的建议方法& Ben的声明解决方案:

  • 在Storyboard中为每个属于PageViewController的视图控制器设置恢复ID。
  • 使用包含上述RestorationID的NSString对象的NSArray。
  • 根据此配置实例化相应的视图控制器。

此外,我试图解决的实现问题需要能够从子视图控制器向后/向前导航,因此该示例项目还通过要求根视图控制器转到上一页或下一页来支持该功能(这也适用于转到特定页面。

Sample code on GitHub

旁注:我当然希望类似于UITabBarController,我可以简单地从Storyboard中连接所有内容并指定视图控制器的顺序但是唉它们似乎还没有我们在那里(截至Xcode 6 / iOS 8.1)。但是,这个解决方案所需的代码非常简单直接。

答案 2 :(得分: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];
}

答案 3 :(得分: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];
}
相关问题