Статьи

Изучение панели вкладок контроллера

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


Класс UITabBarController является еще UIViewController подклассом UIViewController . В то время как контроллеры навигации управляют стеком связанных контроллеров представления, контроллеры панели вкладок управляют массивом контроллеров представления, которые не имеют явной связи друг с другом.

Приложения Clock и Music на iOS являются двумя яркими примерами контроллеров панели вкладок. Как и любой другой подкласс UIViewController , контроллер панели вкладок управляет экземпляром UIView .

Представление контроллера панели вкладок состоит из двух подпредставлений:

  • панель вкладок в нижней части представления
  • представление одного из контроллеров представления, которым управляет контроллер панели вкладок

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

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

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

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


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

Приложение, которое мы будем создавать в этом уроке, будет называться « Библиотека с вкладками» и будет основано на классе UITabBarController . В то время как мы создаем приложение с библиотекой вкладок, вы заметите, что использование контроллера панели вкладок вынуждает приложение к очень специфической парадигме пользовательского интерфейса, которая допускает небольшую гибкость. Контроллеры панели вкладок невероятно полезны, но вы должны признать, что они в некоторой степени накладывают ограничения на пользовательский интерфейс вашего приложения.

Откройте XCode, создайте новый проект (« Файл»> «Создать»> «Проект …» ) и выберите пустой шаблон приложения .

Назовите проект « Библиотека с вкладками» , присвойте название организации и идентификатор компании и установите « Устройства» на iPhone . Сообщите Xcode, где вы хотите сохранить проект, и нажмите « Создать» .

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


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

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

Загрузите исходный код из предыдущей статьи и откройте проект Xcode, который включен в исходные файлы в новом окне Finder. Найдите TSPAuthorsViewController , TSPBooksViewController и TSPBookCoverViewController и перетащите их в новый проект. Обязательно скопируйте файлы в новый проект, установив флажок Копировать элементы в папку целевой группы (если необходимо), и не забудьте добавить файлы в целевую библиотеку с вкладками .

В дополнение к этим трем классам нам также необходимо скопировать папку ресурсов, содержащую Books.plist и файлы изображений, в наш новый проект. Перетащите папку с именем Resources в наш проект и используйте те же настройки, которые мы использовали для копирования файлов классов. Теперь мы готовы создать экземпляр контроллера панели вкладок приложения и заполнить его первым контроллером представления.


Добавить контроллер панели вкладок легко. Откройте основную раскадровку проекта Main.storyboard . Подожди минуту. Где моя раскадровка? Поскольку мы выбрали шаблон пустого приложения , XCode не дал нам раскадровку.

Выберите « Создать»> «Файл …» в меню « Файл» и выберите « Раскадровка» в категории « Интерфейс пользователя iOS» слева.

Установите для семейства устройств iPhone и назовите раскадровку Main , Нет необходимости добавлять расширение .storyboard . XCode добавит это для вас.

Мы также должны сообщить Xcode, что ему необходимо использовать Main.storyboard в качестве основного интерфейса приложения. Выберите проект в Навигаторе проекта , выберите целевой объект « Библиотека с вкладками» в списке целей и установите для « Main Interface» значение « Main» или « Main.storyboard» .

Прежде чем мы начнем работать с раскадровкой, нам нужно обновить текущую реализацию application:didFinishLaunchingWithOptions: в TSPAppDelegate класс. Откройте TSPAppDelegate.m и обновите реализацию, как показано ниже.

1
2
3
— (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    return YES;
}

Вы можете создавать приложения для iOS без раскадровок, но это означает, что вам нужно явно создать экземпляр окна вашего приложения, которое вы видели в реализации application:didFinishLaunchingWithOptions: Однако при работе с раскадровками вы просто возвращаете YES а раскадровка делает все остальное.

Откройте Main.storyboard и перетащите UITabBarController из библиотеки объектов справа. По умолчанию контроллер панели вкладок поставляется с двумя контроллерами представления. Однако я хочу проиллюстрировать, как можно вручную добавить контроллеры представления в контроллер панели вкладок, поэтому выберите контроллеры представления, а не контроллер панели вкладок, и удалите их из раскадровки.

Если вы запускаете приложение в iOS Simulator, вы должны иметь панель вкладок внизу и черный фон. Это может показаться неважным, но показывает, как работает контроллер панели вкладок. Контроллер панели вкладок управляет массивом контроллеров представления, подобно тому, как контроллер навигации управляет стеком контроллеров представления.

Нам нужно добавить несколько контроллеров представления в раскадровку и добавить их в свойство viewControllers контроллера панели вкладок. Посмотрим, как это работает.

Перетащите экземпляр UITableViewController из библиотеки объектов в рабочую область и установите для его класса значение TSPAuthorsViewController в Инспекторе удостоверений . Выберите представление таблицы контроллера представления и установите количество ячеек прототипа равным 0 в инспекторе атрибутов, как мы делали в предыдущем уроке.

Чтобы добавить контроллер представления авторов в массив контроллеров представления панели вкладок, перетащите курсор из контроллера панели вкладок в контроллер представления авторов, удерживая клавишу « Control . Выберите вид контроллеров под категорией Отношения отношений — из меню, которое появляется.

Контроллер панели вкладок с одной вкладкой не так полезен, поэтому давайте добавим еще один контроллер представления в микс. Перетащите другой экземпляр UITableViewController из библиотеки объектов , установите для его класса значение TSPBooksViewController и установите количество ячеек прототипа TSPBooksViewController 0 . Создайте отношения, как мы делали для контроллера представления авторов.

Добавьте экземпляр UIViewController в рабочую область и установите для его класса значение TSPBookCoverViewController в Identity Inspector . Добавьте экземпляр UIImageView в контроллер представления, как мы делали в предыдущей статье, и подключите его к bookCoverView контроллера представления. Создайте связь с контроллером панели вкладок, как мы это делали для контроллеров табличного представления.

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

Создайте и запустите приложение. На данный момент панель вкладок содержит три вкладки. При нажатии на вкладку отображается контроллер представления, связанный с вкладкой.

Вы пытались нажать на имя автора в контроллере представления авторов? И почему контроллер представления книг показывает нам пустое представление таблицы? Пришло время для некоторой отладки.

Когда вы нажимаете на имя автора в контроллере представления авторов, приложение вылетает. Первое, что вы должны сделать, когда столкнулись с аварией, это осмотреть консоль Xcode. Вот что это говорит мне:

1
2014-03-27 12:42:07.964 Tabbed Library[1943:60b] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘Receiver (<TSPAuthorsViewController: 0x8ca22e0>) has no segue with identifier ‘BooksViewController»

Сообщение, которое Xcode отображает в консоли, не сложно расшифровать. Мы говорим контроллеру представления выполнить переход с идентификатором BooksViewController , но этот переход не существует. Результатом является сбой.

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

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

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

Приложения Clock и Music на iOS являются хорошим примером того, как следует использовать контроллер панели вкладок. Контроллеры представления, которыми управляет контроллер панели вкладок в приложении Music, не имеют отношения друг к другу, за исключением того, что они показывают песни.

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


Давайте добавим четвертый контроллер представления к контроллеру панели вкладок, который показывает каждую книгу в Books.plist . Создайте новый подкласс UITableViewController и назовите его TSPAllBooksViewController .

Этот контроллер представления извлечет все книги из Books.plist и отобразит их в алфавитном порядке в табличном представлении. Откройте TSPAllBooksViewController.m и добавьте новые books свойств типа NSArray в расширение класса вверху.

1
2
3
4
5
6
7
#import «TSPAllBooksViewController.h»
 
@interface TSPAllBooksViewController ()
 
@property NSArray *books;
 
@end

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

1
2
3
4
5
6
7
8
9
— (void)viewDidLoad {
    [super viewDidLoad];
 
    // Set Title
    self.title = @»Books»;
 
    // Extract Books
    self.books = [self extractBooks];
}

Давайте extractBooks реализацию extractBooks . Мы начнем с создания изменяемого массива, в который мы будем добавлять книги каждого автора в списке свойств. Следующие две строки должны быть знакомы, если вы читали предыдущую статью. Мы запрашиваем у пакета приложения путь к файлу Books.plist и используем его для загрузки содержимого Books.plist в массив с именем authors . Затем мы перебираем массив авторов и добавляем книги каждого автора в изменяемый массив, который мы создали ранее. Чтобы отсортировать массив книг, мы создаем дескриптор сортировки с ключом Title . После того, как мы отсортируем книги по названию, создается новый массив, в result сортировки изменяемого массива с использованием дескриптора сортировки. Возвращаем отсортированный список книг.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
— (NSArray *)extractBooks {
    // Buffer
    NSMutableArray *buffer = [[NSMutableArray alloc] init];
 
    // Load Authors
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@»Books» ofType:@»plist»];
    NSArray *authors = [NSArray arrayWithContentsOfFile:filePath];
 
    for (int i = 0; i < [authors count]; i++) {
        NSDictionary *author = [authors objectAtIndex:i];
 
        // Add Books to Buffer
        [buffer addObjectsFromArray:[author objectForKey:@»Books»]];
    }
 
    // Sort Books Alphabetically
    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@»Title» ascending:YES];
    NSArray *result = [buffer sortedArrayUsingDescriptors:@[sortDescriptor]];
 
    return result;
}

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

Реализации метода протокола UITableViewDataSource очень похожи на то, что мы видели ранее в этой серии. Найдите минутку, чтобы проверить реализацию каждого метода ниже.

1
2
3
— (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}
1
2
3
— (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [self.books count];
}
01
02
03
04
05
06
07
08
09
10
11
12
— (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    // Dequeue Reusable Cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
     
    // Fetch Book
    NSDictionary *book = [self.books objectAtIndex:[indexPath row]];
     
    // Configure Cell
    [cell.textLabel setText:[book objectForKey:@»Title»]];
     
    return cell;
}

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

1
2
3
@implementation TSPAllBooksViewController
 
static NSString *CellIdentifier = @»Cell Identifier»;
01
02
03
04
05
06
07
08
09
10
11
12
— (void)viewDidLoad {
    [super viewDidLoad];
     
    // Set Title
    self.title = @»Books»;
     
    // Extract Books
    self.books = [self extractBooks];
     
    // Register Class for Cell Reuse
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:CellIdentifier];
}

Когда новый класс контроллера представления готов к использованию, вернитесь к раскадровке, перетащите экземпляр UITableViewController из библиотеки объектов и установите для его класса значение TSPAllBooksViewController . С выбранным табличным представлением установите количество ячеек прототипа равным 0 в инспекторе атрибутов . Прежде чем запускать приложение в iOS Simulator, создайте взаимосвязь между контроллером панели вкладок и контроллером табличного представления, который мы только что добавили.

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


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

При запуске приложения « Библиотека с вкладками» по умолчанию выбирается первая вкладка. Пока четвертая вкладка не выбрана пользователем или программно, нет необходимости загружать представление четвертого контроллера представления. В результате метод viewDidLoad не вызывается до тех пор, пока не будет выбрана четвертая вкладка, что, в свою очередь, означает, что заголовок не устанавливается до тех пор, пока не будет выбрана четвертая вкладка.

Решение простое. Лучший подход — установить заголовок контроллера представления при инициализации контроллера представления. Если мы устанавливаем заголовок в методе init класса, мы можем быть уверены, что заголовок контроллера представления установлен во времени.

Откройте TSPAllBooksViewController.m и добавьте метод с именем initWithCoder: как показано ниже. Этот метод вызывается операционной системой для создания экземпляра класса.

01
02
03
04
05
06
07
08
09
10
— (id)initWithStyle:(UITableViewStyle)style {
    self = [super initWithStyle:style];
     
    if (self) {
        // Set Title
        self.title = @»Books»;
    }
     
    return self;
}

Это также хорошее время для проверки последовательности типичного метода init . Метод инициализации обычно начинается с вызова метода init суперкласса, в этом случае initWithCoder: Я подчеркнул, почему это важно, когда мы обсуждали метод viewDidLoad ранее в этой серии. Результат [super initWithCoder:aDecoder] присваивается self , экземпляру класса, с которым мы работаем.

На следующем шаге мы проверяем, что self не равно nil . Если установлено значение self , а не nil пришло время для дальнейшей настройки экземпляра класса, self . Метод инициализации всегда должен возвращать либо экземпляр класса, либо nil если что-то пошло не так.

В операторе if в initWithCoder: мы устанавливаем свойство title контроллера представления для решения проблемы, которую я обсуждал минуту назад.

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


В предыдущем уроке я писал, что каждый контроллер представления в стеке навигации содержит ссылку на контроллер навигации, управляющий стеком. То же самое верно для контроллеров представления, которыми управляет контроллер панели вкладок. Контроллер представления, управляемый контроллером панели вкладок, сохраняет ссылку на контроллер панели вкладок в своем tabBarController .

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

Откройте TSPAuthorsViewController.m и добавьте метод с именем initWithCoder: как показано ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
— (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
     
    if (self) {
        // Set Title
        self.title = @»Authors»;
         
        // Set Tab Bar Item
        self.tabBarItem = [[UITabBarItem alloc] initWithTitle:@»Authors» image:[UIImage imageNamed:@»icon-authors»] tag:0];
    }
     
    return self;
}

Мы создаем элемент панели вкладок и присваиваем его свойству tabBarItem контроллера представления. Инициализатор класса UITabBarItem принимает заголовок ( NSString ), изображение ( UIImage ) и тег ( NSInteger ). Перед созданием и запуском проекта загрузите исходные файлы этого урока и перетащите в свой проект icon -hors.png и icon-authors@2x.png . Как вы, возможно, помните, файл с суффиксом @ 2x предназначен для устройств с дисплеем сетчатки, тогда как файл без суффикса @ 2x предназначен для устройств без сетчатки.

Обратите внимание, что нет необходимости указывать расширение файла изображения при использовании метода класса imageNamed: of UIImage . Как правило, вам не нужно указывать, какую версию файла — сетчатки или не сетчатки — использовать. На основе имени файла и аппаратного обеспечения устройства операционная система решает, какую версию оно использует.

Я также перенес назначение заголовка в метод initWithCoder: как мы делали в классе TSPAllBooksViewController . Запустите приложение еще раз, чтобы увидеть результат.

Мы можем сделать то же самое для класса TSPAllBooksViewController . Откройте MTAllBooksViewController.m и обновите метод initWithCoder: как показано ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
— (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
     
    if (self) {
        // Set Title
        self.title = @»Books»;
         
        // Set Tab Bar Item
        self.tabBarItem = [[UITabBarItem alloc] initWithTabBarSystemItem:UITabBarSystemItemContacts tag:1];
         
        // Set Badge Value
        [self.tabBarItem setBadgeValue:@»12″];
    }
     
    return self;
}

В дополнение к установке заголовка контроллера представления, мы устанавливаем его свойство tabBarItem . Однако на этот раз мы используем initWithTabBarSystemItem:tag: для настройки элемента панели вкладок. Вы можете использовать этот метод, если хотите использовать элемент панели вкладок, предоставленный системой. Первый аргумент этого метода, UITabBarSystemItem , определяет как заголовок, так и изображение элемента панели вкладок.

Также возможно присвоить элементу панели вкладок значение значка, как показано в приведенной выше реализации initWithCoder: Ожидается, что значение значка будет экземпляром NSString .

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


Прежде чем закончить эту статью, я хотел бы показать вам, как выглядит панель вкладок, когда контроллер панели вкладок управляет более чем пятью контроллерами представления. Как я упоминал ранее, одновременно отображаются только пять вкладок, но контроллер панели вкладок обеспечивает поддержку управления более чем пятью дочерними контроллерами представления.

Откройте основную раскадровку и добавьте еще два экземпляра UITableViewController . Создайте взаимосвязь для каждого контроллера табличного представления и запустите приложение в iOS Simulator, чтобы увидеть результат.

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


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

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