Как реализовать 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];
}
Ответы
Ответ 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];
}