ios 如何实现使用多个 ViewController 的 UIPageViewController

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21641373/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 23:23:31  来源:igfitidea点击:

How to implement UIPageViewController that utilizes multiple ViewControllers

iosuiviewcontrolleruipageviewcontroller

提问by Ben

I've been working on a simple test app to learn the ins and outs of the UIPageViewController. I have it working but I'm not convinced my execution is the best way. I hope some of you can point me in the right direction.

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

To get a basic understanding I used this tutorial as a starting point. http://www.appcoda.com/uipageviewcontroller-storyboard-tutorial/

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

The tutorial creates an app that uses one viewControllerfor each of the pages presented by the UIPageViewController. However I need to utilize the UIPageViewController to scroll thru pages that have completely different layouts. Therefore to take the tutorial a step further I created an master-detail application that uses the UIPageViewController in the detail view to display three different view controllers. I stuck with just displaying images and labels for this test app, but the app I am currently building has three viewControllers that will contain either a tableview, imageView and textViews, or some textFields.

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

Here is the storyboard for my test app.

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

Storyboard

故事板

I use the DetailViewControlleras the data source for the PageViewController. In viewDidLoadof the DVC I establish the labels and images that will be used in the three content view controllers firstViewController, secondViewController, and thirdViewControllerin this manner.

我将DetailViewController用作PageViewController. 在viewDidLoad该DVC我的建立将在三个内容视图控制器中使用的标签和图像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

Below is the method to instantiate the viewControllers

下面是实例化 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

The code in viewDidLoadis one area I feel I am doing unnecassary work. I don't believe I need to instantiate all three view controllers upon loading the DVC, but I didn't know how else to provide an array for the UIPageViewControllerDataSource protocol methods (viewControllerBeforeViewControllerand viewControllerAfterViewController).

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

Here is the viewControllerBefore..method.

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

In summary it seems that I am unnecassarily recreating the view controllers with every swipe from from one page to another. Is this just how the pageViewController works or have I way over complicated the process. Any input would be great!

总之,每次从一个页面滑动到另一个页面时,我似乎都在不必要地重新创建视图控制器。这就是 pageViewController 的工作方式还是我让过程变得复杂。任何输入都会很棒!

SOLUTION

解决方案

Matt suggested an incredibly simple solution in using identifiers. In my Storyboard i simply checked the box that uses my already implemented Storyboard identifier as the Restoration Identifier

Matt 提出了一个非常简单的使用identifiers 的解决方案。在我的故事板中,我只是选中了使用我已经实现的故事板标识符作为恢复标识符的框

RestorationId

恢复ID

Then in viewDidLoadrather than creating an array of viewControllers, simply create an array of strings that match the restoration identifiers.

然后,viewDidLoad与其创建一个 viewController 数组,不如创建一个与恢复标识符匹配的字符串数组。

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

Finally to determine the index in the delegate methods do this rather than what I was doing before:

最后要确定委托方法中的索引,请执行此操作,而不是我之前所做的:

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

It now works without having to unnecessarily instantiate the view controllers.

它现在可以工作,而不必不必要地实例化视图控制器。

As a last note if anyone is using exactly what I have here you can get rid of the following snippet from the viewControllerAtIndex:method.

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

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

回答by matt

First of all, you are absolutely right that the view controllers that constitute the "pages" of the UIPageViewController can be completely different in nature. Nothing whatever says that they have to be instances of the sameview controller class.

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

Now let's get to the actual problem, which is that you very sensibly need a way to provide the next or previous view controller given the current view controller. That is, indeed, the main issue when using a page view controller.

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

It would not really be terrible to hold an array of view controllers. After all, a view controller is a lightweight object (it is the view that is the heavyweight object). However, you are also right that the way you're handling this seems clumsy.

持有一组视图控制器并不是很糟糕。毕竟,视图控制器是一个轻量级对象(视图是重量级对象)。但是,您处理这种情况的方式似乎很笨拙也是对的。

My suggestion is: if you are going to hold the view controller instances in a storyboard, then why not just keep an array of their identifiers? Now you've got an array of three strings. How simple can you get? You will also need a single instance variable that keeps track of which identifier corresponds to the view controller that having its view used as the currentpage (so that you can work out which one is "next" or "previous"); this could just be an integer indexing into the array.

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

There is then absolutely nothing wrong with instantiating a view controller each time the user "turns the page". That is what you are supposedto do when a view controller is needed. And you can readily do this by identifier.

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

Finally, note that if you use the scroll style of page view controller, you won't even have to do that, because the page view controller caches the view controllers and stops calling the delegate methods (or, at least, calls them less).

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

回答by Derek Lee

Came across this question as I was looking for a solution to this same problem - thanks to Matt for the guidance that he provided and to Ben for the solution description.

遇到这个问题是因为我正在寻找同一问题的解决方案 - 感谢 Matt 提供的指导和 Ben 提供的解决方案描述。

I built a sample project to understand this myself and since I noticed some comments asking for sample code I've uploaded this to GitHub. My solution mimics Matt's suggested approach & Ben's stated solution by:

我自己构建了一个示例项目来理解这一点,因为我注意到一些评论要求提供示例代码,所以我已将其上传到 GitHub。我的解决方案通过以下方式模仿 Matt 建议的方法和 Ben 提出的解决方案:

  • Setting the restoration IDs in the Storyboard for each of the view controllers that are part of the PageViewController.
  • Using an NSArray of NSString objects which contain the above mentioned RestorationIDs.
  • Instantiating the appropriate view controller based on this configuration.
  • 在 Storyboard 中为属于 PageViewController 的每个视图控制器设置恢复 ID。
  • 使用包含上述 RestorationID 的 NSString 对象的 NSArray。
  • 基于此配置实例化适当的视图控制器。

Additionally, the implementation problem I was trying to solve required the ability to navigate backwards/forwards from the child view controllers, so this sample project also supports that functionality by asking the root view controller to go to the previous or next page (this could also be applied to go to a specific page).

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

Sample code on GitHub

GitHub 上的示例代码

Side note: I was admittedly hoping that similar to a UITabBarController that I could simply wire everything up from within the Storyboard and specify the order of the view controllers but alas it doesn't seem like we're there yet (as of Xcode 6 / iOS 8.1). The code required for this solution however is pretty minimal and straightforward.

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

回答by Darius Miliauskas

Basically, I managed to get a slightly different way which is based on the template provided by XCode 6.4 (Page-Based Application) methods and the insights from other authors (including this, @Ben from other answers):

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

回答by Raja Bhuma

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