Как реализовать UIPageViewController, который использует несколько ViewController

Я работал над простым тестовым приложением, чтобы изучить все тонкости UIPageViewController. У меня это работает, но я не уверен, что мое исполнение - лучший способ. Я надеюсь, что некоторые из вас могут указать мне правильное направление.

Чтобы получить базовое понимание, я использовал этот учебник в качестве отправной точки. http://www.appcoda.com/uipageviewcontroller-storyboard-tutorial/

Учебник создает приложение, которое использует один viewController для каждой из страниц, представленных UIPageViewController, Однако мне нужно использовать UIPageViewController для прокрутки страниц, которые имеют совершенно разные макеты. Поэтому, чтобы продвинуть учебник дальше, я создал приложение master-detail, которое использует UIPageViewController в подробном представлении для отображения трех различных контроллеров представления. Я придерживался только отображения изображений и меток для этого тестового приложения, но приложение, которое я сейчас создаю, имеет три viewController, которые будут содержать либо tableview, imageView и textViews, либо некоторые textFields.

Вот раскадровка для моего тестового приложения.

Раскадровка

Я использую DetailViewController в качестве источника данных для PageViewController, В viewDidLoad из DVC я устанавливаю метки и изображения, которые будут использоваться в трех контроллерах представления контента firstViewController, secondViewController, а также thirdViewController таким образом.

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 (viewControllerBeforeViewController а также viewControllerAfterViewController).

Здесь 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 или я слишком усложнил процесс. Любой вклад был бы великолепен!

РЕШЕНИЕ

Мэтт предложил невероятно простое решение в использовании идентификаторов. В моей раскадровке я просто установил флажок, который использует мой уже реализованный идентификатор раскадровки в качестве идентификатора восстановления

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 ответа

Решение

Прежде всего, вы абсолютно правы в том, что контроллеры представлений, которые составляют "страницы" UIPageViewController, могут быть совершенно разными по своей природе. Ничто не говорит о том, что они должны быть экземплярами одного и того же класса контроллера представления.

Теперь давайте перейдем к реальной проблеме, которая заключается в том, что вам очень разумно нужен способ предоставить следующий или предыдущий контроллер представления, учитывая текущий контроллер представления. Это, действительно, главная проблема при использовании контроллера просмотра страниц.

Не было бы ужасно держать массив контроллеров представления. В конце концов, контроллер представления - это легкий объект (это представление, которое является тяжелым объектом). Тем не менее, вы также правы, что то, как вы справляетесь с этим, кажется неуклюжим.

Я предлагаю следующее: если вы собираетесь хранить экземпляры контроллера представления в раскадровке, то почему бы просто не сохранить массив их идентификаторов? Теперь у вас есть массив из трех строк. Насколько просто вы можете получить? Вам также понадобится одна переменная экземпляра, которая отслеживает, какой идентификатор соответствует контроллеру представления, чье представление используется в качестве текущей страницы (чтобы вы могли определить, какой из них "следующий" или "предыдущий"); это может быть просто целочисленная индексация в массиве.

Тогда нет абсолютно ничего плохого в создании экземпляра контроллера представления каждый раз, когда пользователь "переворачивает страницу". Это то, что вы должны делать, когда нужен контроллер представления. И вы можете легко сделать это по идентификатору.

Наконец, обратите внимание, что если вы используете стиль прокрутки контроллера представления страницы, вам даже не придется этого делать, потому что контроллер представления страницы кэширует контроллеры представления и прекращает вызывать методы делегата (или, по крайней мере, вызывает их меньше),

Столкнулся с этим вопросом, когда я искал решение этой же проблемы - спасибо Мэтту за предоставленное им руководство и Бену за описание решения.

Я создал пример проекта, чтобы понять это сам, и так как я заметил некоторые комментарии, просящие образец кода, я загрузил это в GitHub. Мое решение имитирует предложенный Мэттом подход и заявленное Беном решение:

  • Установка идентификаторов восстановления в раскадровке для каждого из контроллеров представления, которые являются частью PageViewController.
  • Использование NSArray объектов NSString, которые содержат вышеупомянутые RestorationIDs.
  • Создание соответствующего контроллера представления на основе этой конфигурации.

Кроме того, проблема с реализацией, которую я пытался решить, требовала возможности переходить назад / вперед от контроллеров дочерних представлений, поэтому этот пример проекта также поддерживает эту функцию, запрашивая контроллер корневого представления перейти на предыдущую или следующую страницу (это также может применить, чтобы перейти на определенную страницу).

Пример кода на GitHub

Примечание: я надеялся, что, как и в UITabBarController, я мог бы просто соединить все в Storyboard и указать порядок контроллеров представления, но, увы, похоже, что мы еще не достигли этого (с Xcode 6 / iOS 8.1). Код, необходимый для этого решения, однако, довольно минимален и прост.

По сути, мне удалось получить немного другой способ, основанный на шаблоне, предоставленном методами XCode 6.4 (Page-Based Application), а также на опыте других авторов (включая @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];
}
Другие вопросы по тегам