Статьи

Реализация контейнера сдерживания — контроллер скользящего меню

В этом уроке мы реализуем минималистичную версию пользовательского интерфейса в стиле Facebook / Path. Цель состоит в том, чтобы понять, как использовать сдерживание контроллера представления для реализации пользовательского потока в вашем приложении.


Контроллеры представления являются неотъемлемой частью любого приложения iOS, независимо от того, насколько оно маленькое, большое, простое или сложное. Они обеспечивают «клейкую логику» между моделью данных вашего приложения и пользовательским интерфейсом.

Вообще говоря, существует два вида контроллеров представления:

  • Контроллеры представления контента: они отвечают за отображение и управление видимым контентом.
  • Контейнерные контроллеры. Они управляют контроллерами представления контента и отвечают за общую структуру и поток приложения.

Контейнерный контроллер может иметь некоторый собственный видимый компонент, но в основном функционирует как хост для контроллеров представления контента. Контейнерные контроллеры служат для «трафика» приходов и выходов контроллеров представления контента.

UINavigationController , UITabBarController и UIPageViewController являются примерами контроллеров представления контейнера, которые поставляются с iOS SDK. Подумайте, как они различаются с точки зрения потоков приложений, которые они создают. Контроллер навигации отлично подходит для приложений типа детализации, где выбор, который пользователь делает на одном экране, влияет на то, с какими вариантами он представлен на следующем экране. Контроллер панели вкладок отлично подходит для приложений с независимой функциональностью, позволяя удобно переключаться простым нажатием кнопки вкладки. Наконец, контроллер просмотра страниц представляет метафору книги, позволяя пользователю переключаться между страницами контента.

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

До iOS 5 не было средств для объявления иерархических (т.е. родительско-дочерних) отношений между двумя контроллерами представления и, следовательно, не было «правильного» способа реализации пользовательского потока приложения. Нужно было либо обходиться встроенными типами, либо делать это случайным образом, который в основном состоял из залипания представлений, управляемых одним контроллером представления, в иерархию представлений, управляемых другим контроллером представления. Это создаст несоответствия. Например, представление в конечном итоге окажется в иерархии представлений двух контроллеров, при этом ни один из этих контроллеров не распознает другой, что иногда приводит к странному поведению. Сдерживание было введено в iOS 5 и немного улучшено в iOS 6, и оно позволяет формализовать понятие контроллеров родительского и дочернего представлений в иерархии. По сути, правильное содержание контроллера представления требует, чтобы, если представление B является подпредставлением (дочерним) представления A, и если они не находятся под управлением одного и того же контроллера представления, то контроллер представления B должен быть сделан потомком контроллера представления A.

Вы можете спросить, есть ли какая-то конкретная выгода, предлагаемая сдерживанием контроллера представления, помимо преимущества иерархической структуры, которую мы обсуждали. Ответ — да. Помните, что когда контроллер представления появляется на экране или уходит, нам может потребоваться настроить или отключить ресурсы, очистить, извлечь или сохранить информацию из / в файловую систему. Все мы знаем о появлении обратных вызовов. Явно объявляя отношения «родитель-потомок», мы гарантируем, что родительский контроллер будет пересылать обратные вызовы своим дочерним элементам всякий раз, когда он включается или выключается. Ротационные обратные вызовы тоже должны быть перенаправлены. Когда ориентация меняется, все контроллеры представления на экране должны знать, чтобы они могли соответствующим образом настроить свое содержимое.

Что все это означает с точки зрения кода? Контроллеры представления имеют свойство NSArray называется childViewControllers и в наши обязанности входит добавление и удаление дочерних контроллеров представления в этот массив и из него в родительском childViewControllers путем вызова соответствующих методов. Эти методы включают addChildViewController ( removeFromParentViewController у родителя) и removeFromParentViewController ( removeFromParentViewController у потомка), когда мы стремимся создать или разорвать отношения родитель-потомок. Есть также пара уведомлений, отправленных дочернему контроллеру представления в начале и в конце процесса добавления / удаления. Это willMoveToParentViewController: и didMoveToParentViewController: отправленные с соответствующим родительским контроллером в качестве аргумента. Аргумент — nil , если ребенка удаляют. Как мы увидим, одно из этих сообщений будет отправлено для нас автоматически, в то время как другое будет нашей ответственностью за отправку. Это будет зависеть от того, добавляем или удаляем ли мы ребенка. Точную последовательность мы вскоре изучим, когда будем реализовывать вещи в коде. Дочерний контроллер может отвечать на эти уведомления, реализуя соответствующие методы, если ему нужно что-то сделать при подготовке этих событий.

Нам также необходимо добавить / удалить представления, связанные с дочерним контроллером представления, в иерархию родителя, используя такие методы, как addSubview: или removeFromSuperview ), включая выполнение любых сопровождающих анимаций. Есть удобный метод (-)transitionFromViewController:toViewController:duration:options:animations:completion: который позволяет нам упростить процесс замены контроллеров дочерних представлений на экране анимациями. Мы рассмотрим точные детали, когда напишем код — что будет дальше!


Создайте новое приложение iOS в XCode на основе шаблона « Пустое приложение ». Сделайте приложение для iOS с поддержкой ARC. Назовите это VCContainmentTut .

Создание нового проекта
Создание нового проекта

Создайте новый класс с именем RootController . Сделайте это подклассом UIViewController . Убедитесь, что все флажки сняты. Это будет наш подкласс контроллера представления контейнера.

newvc

Замените код в RootViewController.h следующим.

1
2
3
4
5
6
7
#import <UIKit/UIKit.h>
 
@interface RootController : UIViewController<UITableViewDataSource, UITableViewDelegate> // (1)
 
— (id)initWithViewControllers:(NSArray *)viewControllers andMenuTitles:(NSArray *)titles;
 
@end

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

Ссылаясь на пункты в коде,

  1. Наш корневой контроллер работает как делегат и источник данных меню (т. Е. Представления таблицы).
  2. Мы предлагаем чрезвычайно простой API (что касается пользователя нашего класса контейнеров), состоящий из одного инициализатора, который принимает список контроллеров представления, который должен содержать наш корневой контроллер, и списка строк, представляющих заголовки для каждого контроллера представления в меню. Это так, чтобы мы могли сосредоточиться на основах. Как только вы поймете это, вы сможете настроить API так, как вам нравится.

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

newvc

Это поможет понять, что наш контроллер представления контейнера очень похож на контроллер представления вкладки. Каждый пункт в меню соответствует независимому контроллеру представления. Разница между нашим контроллером « скользящего меню » и контроллером вкладок по большей части визуальная. Фраза « скользящее меню » немного ошибочна, потому что на самом деле это контроллер представления контента, который скользит, чтобы скрыть или открыть меню внизу.

Переходя к реализации, замените весь код в RootController.m следующим кодом.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#define kExposedWidth 200.0
#define kMenuCellID @»MenuCell»
 
#import «RootController.h»
 
@interface RootController()
 
@property (nonatomic, strong) UITableView *menu;
@property (nonatomic, strong) NSArray *viewControllers;
@property (nonatomic, strong) NSArray *menuTitles;
 
@property (nonatomic, assign) NSInteger indexOfVisibleController;
 
@property (nonatomic, assign) BOOL isMenuVisible;
 
@end
 
 
@implementation RootController
 
— (id)initWithViewControllers:(NSArray *)viewControllers andMenuTitles:(NSArray *)menuTitles
{
    if (self = [super init])
    {
        NSAssert(self.viewControllers.count == self.menuTitles.count, @»There must be one and only one menu title corresponding to every view controller!»);
        NSMutableArray *tempVCs = [NSMutableArray arrayWithCapacity:viewControllers.count];
         
        self.menuTitles = [menuTitles copy];
         
        for (UIViewController *vc in viewControllers) // (2)
        {
            if (![vc isMemberOfClass:[UINavigationController class]])
            {
                [tempVCs addObject:[[UINavigationController alloc] initWithRootViewController:vc]];
            }
            else
                [tempVCs addObject:vc];
                                                    
            UIBarButtonItem *revealMenuBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@»Menu» style:UIBarButtonItemStylePlain target:self action:@selector(toggleMenuVisibility:)];
             
            UIViewController *topVC = ((UINavigationController *)tempVCs.lastObject).topViewController;
            topVC.navigationItem.leftBarButtonItems = [@[revealMenuBarButtonItem] arrayByAddingObjectsFromArray:topVC.navigationItem.leftBarButtonItems];
             
             
        }
        self.viewControllers = [tempVCs copy];
        self.menu = [[UITableView alloc] init];
        self.menu.delegate = self;
        self.menu.dataSource = self;
 
    }
    return self;
}
 
 
 
— (void)viewDidLoad
{
    [super viewDidLoad];
    [self.menu registerClass:[UITableViewCell class] forCellReuseIdentifier:kMenuCellID];
    self.menu.frame = self.view.bounds;
    [self.view addSubview:self.menu];
     
    self.indexOfVisibleController = 0;
    UIViewController *visibleViewController = self.viewControllers[0];
    visibleViewController.view.frame = [self offScreenFrame];
    [self addChildViewController:visibleViewController];
    [self.view addSubview:visibleViewController.view];
    self.isMenuVisible = YES;
    [self adjustContentFrameAccordingToMenuVisibility];
     
     
    [self.viewControllers[0] didMoveToParentViewController:self];
     
}
 
— (void)toggleMenuVisibility:(id)sender // (9)
{
    self.isMenuVisible = !self.isMenuVisible;
    [self adjustContentFrameAccordingToMenuVisibility];
}
 
 
— (void)adjustContentFrameAccordingToMenuVisibility // (10)
{
    UIViewController *visibleViewController = self.viewControllers[self.indexOfVisibleController];
    CGSize size = visibleViewController.view.frame.size;
 
    if (self.isMenuVisible)
    {
        [UIView animateWithDuration:0.5 animations:^{
            visibleViewController.view.frame = CGRectMake(kExposedWidth, 0, size.width, size.height);
        }];
    }
    else
        [UIView animateWithDuration:0.5 animations:^{
            visibleViewController.view.frame = CGRectMake(0, 0, size.width, size.height);
        }];
     
}
 
— (void)replaceVisibleViewControllerWithViewControllerAtIndex:(NSInteger)index // (11)
{
    if (index == self.indexOfVisibleController) return;
    UIViewController *incomingViewController = self.viewControllers[index];
    incomingViewController.view.frame = [self offScreenFrame];
    UIViewController *outgoingViewController = self.viewControllers[self.indexOfVisibleController];
    CGRect visibleFrame = self.view.bounds;
     
     
    [outgoingViewController willMoveToParentViewController:nil];
     
    [self addChildViewController:incomingViewController];
    [[UIApplication sharedApplication] beginIgnoringInteractionEvents];
    [self transitionFromViewController:outgoingViewController // (15)
                      toViewController:incomingViewController
                              duration:0.5 options:0
                            animations:^{
                                outgoingViewController.view.frame = [self offScreenFrame];
                                 
                            }
      
                            completion:^(BOOL finished) {
                                [UIView animateWithDuration:0.5
                                                 animations:^{
                                                     [outgoingViewController.view removeFromSuperview];
                                                     [self.view addSubview:incomingViewController.view];
                                                     incomingViewController.view.frame = visibleFrame;
                                                     [[UIApplication sharedApplication] endIgnoringInteractionEvents];
                                }];
                                [incomingViewController didMoveToParentViewController:self];
                                [outgoingViewController removeFromParentViewController];
                                self.isMenuVisible = NO;
                                self.indexOfVisibleController = index;
   }];
}
 
 
// (19):
 
— (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}
 
— (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.menuTitles.count;
}
 
— (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kMenuCellID];
    cell.textLabel.text = self.menuTitles[indexPath.row];
    return cell;
}
 
— (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [self replaceVisibleViewControllerWithViewControllerAtIndex:indexPath.row];
}
 
— (CGRect)offScreenFrame
{
    return CGRectMake(self.view.bounds.size.width, 0, self.view.bounds.size.width, self.view.bounds.size.height);
}
 
@end

Теперь для объяснения кода. Части, которые я выделил для акцента, особенно актуальны для реализации сдерживания.

  1. Сначала инициализатор выполняет простую проверку, чтобы убедиться, что каждому контроллеру представления присвоен заголовок меню. Мы не делали никакой проверки типов, чтобы гарантировать, что каждый из двух массивов, передаваемых инициализатору, содержит правильный тип объектов, соответственно типы UIViewController и NSString . Вы могли бы рассмотреть возможность сделать это. Обратите внимание, что мы поддерживаем массивы для каждого из них, называемые viewControllers и menuTitles .
  2. Мы хотим, чтобы была кнопка, которая при нажатии отображает или скрывает меню. Мое простое решение относительно того, где должна быть кнопка, — поместить каждый контроллер представления, который мы получили от инициализатора, в контроллер навигации. Это дает нам бесплатную панель навигации, к которой можно добавить кнопку, если только переданный в поле зрения контроллер уже не был контроллером навигации, в этом случае мы не делаем ничего лишнего.
  3. Мы создаем элемент панели кнопок, который вызывает появление или скрытие меню, сдвигая контроллер вида, который в данный момент виден. Мы добавляем его на панель навигации, перемещая любые существующие кнопки на панели навигации вправо. Мы делаем это, потому что добавленный контроллер представления уже является контроллером навигации с уже существующими кнопками панели.
  4. Мы создаем наше меню как табличное представление и назначаем сам корневой контроллер в качестве делегата и источника данных.
  5. В viewDidLoad после настройки и добавления представления таблицы меню в представление нашего корневого контроллера мы включаем в наше приложение первый контроллер представления в массиве viewControllers . Отправляя сообщение addChildViewController: нашему корневому контроллеру, мы выполняем нашу первую обязанность, связанную с защитой. Вы должны знать, что это вызывает сообщение willMoveToParentViewController: вызываться на дочернем контроллере.
  6. Обратите внимание, что нам явно необходимо добавить представление нашего дочернего контроллера в иерархию представления родителя!
  7. Сначала мы устанавливаем меню видимым и вызываем метод, который настраивает видимый кадр контроллера представления контента с учетом видимости меню. Мы рассмотрим детали этого метода в ближайшее время.
  8. Как только представление дочернего контроллера представления удобно размещается в иерархии представления родителя, мы отправляем сообщение didMoveToParentViewController добавленному дочернему контроллеру, используя self , экземпляр RootController, в качестве аргумента. В нашем дочернем контроллере мы можем реализовать этот метод, если нам нужно.
  9. Простой метод, связанный с действием кнопки панели меню, который переключает видимость меню, соответствующим образом настраивая вид контроллера наложения.
  10. Как видно из названия, adjustContentFrameAccordingToMenuVisibility позволяет нам настроить фрейм контроллера представления содержимого, чтобы сообщить нам, скрыто ли меню или нет. Если да, то он перекрывает супервизор. В противном случае он сдвигается вправо на kExposedWidth . Я установил это на 200 баллов.
  11. Опять же, как ясно указано в имени, replaceVisibleViewControllerWithViewControllerAtIndex позволяет нам менять контроллеры представлений и соответствующие представления в иерархии. Чтобы осуществить нашу анимацию, которая состоит в том, чтобы сдвинуть замененный контроллер представления за пределы экрана вправо и затем ввести заменяющий контроллер из того же места, мы определяем несколько прямоугольных рамок.
  12. willMoveToParentViewController с nil . Как только мы закончим этот шаг, этот контроллер представления перестанет получать обратные вызовы появления и вращения от родителя. Это имеет смысл, потому что это больше не активная часть приложения.
  13. Мы добавляем входящий контроллер представления как дочерний к корневому контроллеру, подобно тому, что мы делали в начале
  14. Мы начинаем игнорировать события взаимодействия с пользователем, чтобы позволить нашему контроллеру представления плавно переключаться.
  15. Этот удобный метод позволяет нам анимировать удаление исходящего контроллера и прибытие входящего контроллера при выполнении необходимой последовательности событий, связанных с процессом добавления и удаления контроллера дочернего представления. Мы анимируем вид исходящего VC, чтобы скользить за пределы экрана вправо, и после завершения анимации мы удаляем его из иерархии представлений. Затем мы анимируем входящий контроллер вида, чтобы скользить из того же места за кадром и занимать место, ранее занимаемое видом исходящего контроллера.
  16. Мы разрешаем нашему приложению принимать входящие события взаимодействия, так как наш обмен контроллерами представления завершен.
  17. Мы уведомляем входящий контроллер представления о том, что он был перемещен в контроллер контейнера, отправив ему сообщение didMoveToParentViewController с self в качестве аргумента.
  18. Мы удаляем исходящий контроллер из контроллера контейнера, отправляя ему сообщение removeFromParentViewController . Вы должны знать, что didMoveToParentViewController: с nil в качестве аргумента будет отправлено для вас.
  19. Мы реализуем методы протокола делегата и источника данных таблицы меню, которые довольно просты. Это включает в себя запуск шага замены контроллера представления (11), когда ячейка нового элемента -tableView:didSelectRowAtIndexPath: в меню через -tableView:didSelectRowAtIndexPath: метод.

Возможно, вы нашли последовательность вызовов, связанных с защитой контроллера, немного запутанной. Это помогает обобщить.

    При добавлении дочернего контроллера представления к родителю:

  • Вызовите addChildViewController: для родителя с дочерним addChildViewController: в качестве аргумента. Это приводит к тому, что сообщение willMoveToParentViewController: будет отправлено willMoveToParentViewController: с родителем в качестве аргумента.
  • Добавьте представление ребенка как подпредставление представления родителя.
  • Явно вызовите didMoveToParentViewController: для потомка с родителем в качестве аргумента.
    При удалении дочернего контроллера представления от его родителя:

  • Вызовите willMoveToParentViewController: для дочернего willMoveToParentViewController: с nil в качестве аргумента.
  • Уберите взгляд ребенка из его суперпредставления.
  • Отправьте removeFromParentViewController ребенку. Причины вызывает сообщение didMoveToParentViewController с nil в качестве аргумента, который будет отправлен ребенку от вашего имени.

Давайте проверим различные типы контроллеров представления, добавленных к нашему корневому контроллеру! Создайте новый подкласс UIViewController именем ViewController , оставив все параметры без изменений.

Замените код в ViewController.m следующим кодом.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#import «ViewController.h»
 
@interface ViewController ()
 
@end
 
@implementation ViewController
 
— (void)willMoveToParentViewController:(UIViewController *)parent
{
    NSLog(@»%@ (%p) — %@», NSStringFromClass([self class]), self, NSStringFromSelector(_cmd));
}
 
— (void)didMoveToParentViewController:(UIViewController *)parent
{
    NSLog(@»%@ (%p) — %@», NSStringFromClass([self class]), self, NSStringFromSelector(_cmd));
}
 
— (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    NSLog(@»%@ (%p) — %@», NSStringFromClass([self class]), self, NSStringFromSelector(_cmd));
}
 
— (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    NSLog(@»%@ (%p) — %@», NSStringFromClass([self class]), self, NSStringFromSelector(_cmd));
}
 
— (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
    NSLog(@»%@ (%p) — %@», NSStringFromClass([self class]), self, NSStringFromSelector(_cmd));
}
 
— (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
    NSLog(@»%@ (%p) — %@», NSStringFromClass([self class]), self, NSStringFromSelector(_cmd));
}
@end

В самом контроллере представления нет ничего особенного, кроме того, что мы переопределили различные обратные вызовы, чтобы мы могли регистрировать их всякий раз, когда наш экземпляр ViewController становится дочерним по отношению к нашему корневому контроллеру и возникает событие появления или поворота.

Во всем предыдущем коде _cmd ссылается на селектор, соответствующий методу, в котором находится наше выполнение. NSStringFromSelector() преобразует его в строку. Это быстрый и простой способ получить имя текущего метода, не вводя его вручную.

Давайте добавим навигационный контроллер и контроллер вкладок в микс. На этот раз мы будем использовать раскадровки.

Создайте новый файл и в iOS> Пользовательский интерфейс выберите раскадровку . Установите семейство устройств на iPhone и назовите его NavStoryBoard .

раскадровка

Из библиотеки объектов перетащите объект Navigation Controller на холст. Перетащите элемент кнопки панели в левую часть панели навигации в контроллере табличного представления, обозначенном как « Root View Controller ». Это содержит табличное представление на холсте. Дайте ему любое имя. Я назвал это » Левый «. Его целью является проверка кода, который мы написали, чтобы кнопка «Скрыть / открыть» панели меню заняла свое место как крайняя левая кнопка на панели навигации, нажимая все уже имеющиеся кнопки вправо. Наконец, перетащите экземпляр View Controller и поместите его справа от контроллера под названием « Root View Controller » на холсте.

Щелкните там, где написано « Table View » в центре второго контроллера, и в инспекторе атрибутов измените содержимое с « Dynamic Prototype » на « Static Cells ».

Изменение типа содержимого ячейки с динамического на статический
Изменение типа содержимого ячейки с динамического на статический

Это приведет к появлению трех статических ячеек табличного представления в конструкторе интерфейса. Удалите все ячейки табличного представления, кроме одной, и, удерживая нажатой клавишу «Control» , щелкните и перетащите курсор из оставшейся ячейки в контроллер представления в крайнем правом углу и отпустите. Выберите « нажать » под Sege выбора . Все, что это делает, это вызывает переход к правильному контроллеру представления, когда вы нажимаете на одиночную ячейку из представления таблицы. Если вы хотите, вы можете поместить UILabel в ячейку таблицы, чтобы дать ей некоторый текст. Ваша раскадровка должна выглядеть примерно так, как на фото ниже.

NavStoryBoard
NavStoryBoard

Наконец, давайте добавим контроллер панели вкладок. Как и раньше, создайте файл раскадровки и назовите его TabStoryBoard . Перетащите элемент контроллера панели вкладок из библиотеки объектов на холст. Он поставляется с предварительно настроенными двумя вкладками, и, если хотите, вы можете изменить цвет фона двух контроллеров представления с вкладками, щелкнув представление, соответствующее любому контроллеру представления, и изменив параметр « background » в Инспекторе атрибутов . Таким образом, вы можете проверить, что выбор контроллера представления через вкладку работает правильно.

TabStoryBoard
Ваша история должна выглядеть следующим образом.

Теперь пришло время настроить все в AppDelegate .

Замените код в AppDelegate.m следующим кодом.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#import «AppDelegate.h»
#import «RootController.h»
#import «ViewController.h»
 
@implementation AppDelegate
 
— (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    UIStoryboard *tabStoryBoard = [UIStoryboard storyboardWithName:@»TabStoryboard» bundle:nil];
    UIStoryboard *navStoryBoard = [UIStoryboard storyboardWithName:@»NavStoryboard» bundle:nil];
    UINavigationController *navController = [navStoryBoard instantiateViewControllerWithIdentifier:@»Nav Controller»];
    UITabBarController *tabController = [tabStoryBoard instantiateViewControllerWithIdentifier:@»Tab Controller»];
    ViewController *redVC, *greenVC;
    redVC = [[ViewController alloc] init];
    greenVC = [[ViewController alloc] init];
     
    redVC.view.backgroundColor = [UIColor redColor];
    greenVC.view.backgroundColor = [UIColor greenColor];
     
    RootController *menuController = [[RootController alloc]
                                      initWithViewControllers:@[tabController, redVC, greenVC, navController]
                                      andMenuTitles:@[@»Tab», @»Red», @»Green», @»Nav»]];
    self.window.rootViewController = menuController;
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

Все, что мы сделали, это создали экземпляры ViewController и ViewController экземпляр контроллера навигации и вкладок из двух раскадровок. Мы передали их в массиве экземпляру нашего RootController . Это контроллер контейнера, который мы реализовали в начале. Мы сделали это вместе с массивом строк для именования контроллеров представления в меню. Теперь мы просто обозначим наш инициализированный экземпляр Root Controller как свойство rootViewController окна.

Создайте и запустите приложение. Вы только что внедрили сдерживание контейнера! Нажмите на различные ячейки таблицы в меню, чтобы заменить видимый слайд на новый, скользящий справа. Обратите внимание, что для экземпляра контроллера навигации (в меню с именем « NavC ») кнопка « Влево » сместилась на одну позицию вправо, а кнопка строки меню заняла крайнее левое положение. Вы можете изменить ориентацию на ландшафт и убедиться, что все выглядит правильно.

Симулятор Снимки экрана
Снимки экрана симулятора

В этом вводном уроке мы рассмотрели, как реализована защита контроллера представления в iOS 6. Мы разработали простую версию пользовательского интерфейса приложения, которая приобрела большую популярность и часто встречается в таких часто используемых приложениях, как Facebook и Path. Наша реализация была настолько простой, насколько это было возможно, поэтому мы смогли легко ее проанализировать и получить правильные основы. Существует много сложных реализаций этого типа контроллера с открытым исходным кодом, которые вы можете скачать и изучить. Быстрый поиск в Google включает JASidePAnels и SWRevealViewController , среди других.

Вот несколько идей для работы.

  • Сделайте реализацию более гибкой, а API — более настраиваемым.
  • Сделайте интерфейс красивее. Вы можете настроить внешний вид ячейки табличного представления или позволить представлению контроллера представления бросить тень на меню, чтобы придать интерфейсу некоторую глубину.
  • Заставьте интерфейс лучше адаптироваться к ориентации. Помните, что ваши дочерние контроллеры представления будут получать уведомления о ротации, так что вот с чего вы начнете!
  • Реализуйте распознавание жестов, чтобы вы могли перетаскивать контроллер представления влево и вправо по экрану в качестве альтернативы нажатию на кнопку меню.
  • Проектируйте и разрабатывайте совершенно новый и новый поток приложений и реализуйте пользовательский интерфейс в коде. Скорее всего, вам нужно использовать сдерживание контроллера представления!

Одна вещь, которую я хотел бы здесь упомянуть, это то, что в Xcode 4.5 и более поздних версиях появился новый объект конструктора интерфейса под названием « Контейнерное представление », который может отображать содержимое контроллера представления и, таким образом, использоваться для реализации сдерживания непосредственно в вашей раскадровке! Удачного кодирования!