Подтвердить что ты не робот

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

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

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

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

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

Storyboard

Я использую 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];
}
4b9b3361

Ответ 1

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

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

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

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

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

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

Ответ 2

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

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

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

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

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

Боковое замечание: я, по общему признанию, надеялся, что, похоже на UITabBarController, я мог бы просто подключить все из раскадровки и указать порядок контроллеров вида, но, увы, похоже, что мы еще там (начиная с 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];
}