如何实现使用多个ViewController的UIPageViewController

我一直在一个简单的testing应用程序来学习UIPageViewController的来龙去脉。 我有它的工作,但我不相信我的执行是最好的方法。 我希望你们中的一些人能把我指向正确的方向。

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

本教程将创build一个应用程序,该应用程序为viewController提供的每个页面使用一个UIPageViewController 。 不过,我需要利用UIPageViewController来滚动具有完全不同布局的页面。 因此,为了让本教程更进一步,我创build了一个在详细视图中使用UIPageViewController来显示三个不同视图控制器的主 – 细节应用程序。 我坚持只显示这个testing应用程序的图像和标签,但我目前正在build设的应用程序有三个viewController将包含tableview,imageView和textViews,或一些textFields。

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

故事板

我使用DetailViewController作为PageViewController的数据源。 在DVC的viewDidLoad中,我以这种方式build立将在三个内容视图控制器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 )的数组。

这里是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]; } 

总之,似乎我不必要地重新创build视图控制器从每一个页面刷到另一个。 这仅仅是pageViewController的工作原理,或者让我的方法复杂的过程。 任何input将是伟大的!

马特build议使用标识符非常简单的解决scheme。 在我的故事板中,我只是选中了使用我已经实现的故事板标识符作为恢复标识符的框

RestorationId

然后在viewDidLoad而不是创build一个viewController数组,只需创build一个匹配恢复标识符的string数组。

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

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

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

举行一个视图控制器arrays并不是很可怕。 毕竟,视图控制器是一个轻量级的对象(这是视图是重量级的对象)。 但是,你也是对的,你处理这个方式似乎很笨拙。

我的build议是:如果您要在视图控制台中保存视图控制器实例,那么为什么不保留一个标识符数组呢? 现在你已经有了三个string的数组。 你有多简单? 你还需要一个单独的实例variables来跟踪哪个标识符对应于视图控制器,这个视图控制器将其视图用作当前页面(这样就可以计算出哪一个是“下一个”或“上一个”); 这可能只是一个整数索引到数组中。

每次用户“翻页”时,实例化一个视图控制器是完全没有问题的。 这就是当需要视图控制器时你应该做的事情。 你可以很容易地通过标识符做到这一点。

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

遇到这个问题时,我正在寻找解决这个问题的方法 – 感谢Matt提供的指导,并感谢Ben的解决scheme描述。

我自己构build了一个示例项目来理解这个,因为我注意到一些评论要求我已经把这个代码上传到了GitHub。 我的解决scheme模仿马特build议的方法和本的解决scheme:

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

此外,我试图解决的实现问题需要从子视图控制器向后/向前导航的能力,所以这个示例项目也通过要求根视图控制器转到上一页或下一页来支持该function(这也可以被应用去到一个特定的页面)。

GitHub上的代码示例

附注:我承认希望类似于一个UITabBarController,我可以简单地从故事板内的所有东西,并指定视图控制器的顺序,但可惜它似乎并不像我们那里(Xcode 6 / iOS 8.1)。 这个解决scheme所需的代码是相当简单和直接的。

基本上,我设法得到了一个基于XCode 6.4(基于页面的应用)方法提供的模板和其他作者的见解(包括来自其他答案的@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]; } 
 @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]; }