【问题标题】:How to implement UIPageViewController that utilizes multiple ViewControllers如何实现利用多个 ViewController 的 UIPageViewController
【发布时间】:2014-03-05 16:03:30
【问题描述】:

我一直在开发一个简单的测试应用程序来了解 UIPageViewController 的来龙去脉。我有它的工作,但我不相信我的执行是最好的方式。我希望你们中的一些人能指出我正确的方向。

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

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

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

我使用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)提供数组。

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

然后在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: 方法中删除以下 sn-p。

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

【问题讨论】:

  • 你能上传一些关于这个的示例代码吗?谢谢!
  • @fguespe 您应该在这里拥有解决此问题所需的一切...不确定您还在寻找什么...请注意我在问题的底部添加了一个解决方案
  • 这是一个很棒的解决方案。但是我无法获得正确的页面索引以用于其他功能。您是将 NSString * ident 等放在根控制器中还是在模型的 - (NSUInteger)indexOfViewController:(embBaseViewController *)viewController 中?
  • @malaki1974 NSString *ident 行位于 viewControllerBeforeViewController:viewControllerAfterViewController: 委托方法中的根控制器中……。我不确定您要实现什么,但您说您正在尝试获取页面索引以用于其他功能。您也许可以为控制器创建一个currentPageIndex 属性,这样您就可以在其他函数中始终调用self.currentPageIndex 之类的东西……希望对您有所帮助
  • 嗨@Ben,尽管代码对许多人来说是相当不言自明的,但看到一个工作示例会有很大帮助。你能把你的代码发布到 github 上吗?

标签: ios uiviewcontroller uipageviewcontroller


【解决方案1】:

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

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

拥有一组视图控制器并不可怕。毕竟,视图控制器是一个轻量级对象(它是重量级对象的视图)。但是,您的处理方式似乎很笨拙也是对的。

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

每次用户“翻页”时实例化视图控制器绝对没有错。当需要视图控制器时,应该就是这样做的。您可以通过标识符轻松地做到这一点。

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

【讨论】:

  • 马特,你的建议太棒了!令人尴尬的是,我花了几个小时才弄清楚如何把它拉下来。这是因为我没有办法使用 StoryBoard 标识符来确定当前正在使用的视图控制器。直到我准备放弃时,我才偶然发现了恢复标识符。一旦我发现那个小宝石,它就是在公园里散步。感谢您的输入。我觉得我的新执行方式不那么“笨拙”了。
  • 恢复标识符的想法可能比我的想法更优雅,我只是跟踪标识符数组中的当前索引。
  • 嗨!我有类似的情况,但我的控制器的视图是一个 xib 文件。我在视图中设置了恢复 ID,但是当我执行 controller.restorationIdenfier 时,它返回 nil...关于此的任何提示?
  • 马特干得好,你能看看这篇文章吗?你或许可以帮助他。 stackoverflow.com/questions/29745656/…
  • 我有一个问题:UIPageViewControllerDataSource 方法 - ..After..Before 在推送下一个之前弹出当前正在显示的 ViewController..?还是我们必须处理这个问题?(鉴于在 UIPageViewController 中嵌入了 NavigationController)
【解决方案2】:

我在寻找解决同一问题的方法时遇到了这个问题 - 感谢 Matt 提供的指导以及 Ben 的解决方案描述。

我自己构建了一个示例项目来理解这一点,由于我注意到一些 cmets 要求提供示例代码,因此我已将其上传到 GitHub。我的解决方案模仿了 Matt 建议的方法和 Ben 提出的解决方案:

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

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

Sample code on GitHub

旁注:我诚然希望与 UITabBarController 类似,我可以简单地从 Storyboard 中连接所有内容并指定视图控制器的顺序,但遗憾的是,我们似乎还没有到那里(截至Xcode 6 / iOS 8.1)。然而,此解决方案所需的代码非常简单明了。

【讨论】:

    【解决方案3】:

    基本上,我设法获得了一种稍微不同的方法,它基于 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];
    }
    

    【讨论】:

      【解决方案4】:
      @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];
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多