UIViewController внутри другого UIViewController или как работать с Container View
Недавно я делал приложение, в котором мне понадобилось использовать такой элемент интерфейса как Container View. Если этот элемент разместить на контроллере, то он создал еще один контроллер, который будет вписан в размеры Container View. С этим контроллером можно делать все что и с обыкновенным контроллером, только он будет вписан в другой контроллер. Мне нужно было передавать информацию из одного из родительского контроллера во вписанный, и я с удивлением обнаружил, что примеров использования с описанием процесса передачи информации крайне мало. Поэтому решил выложить здесь результаты своих экспериментов.
Постановка задачи
Наша задача создать приложение с тремя кнопками, по нажатию на которые в нижней части экрана будет заполнятся таблица различным содержанием. Таблица будет представлена отдельным UITableViewController, который мы впишем в Container View.
Создание приложения в Xcode
Откройте Xcode, выберите Single View Application
Задайте имя и место размещения проекта
Перейдите в Main Storyboard и из библиотеки элементов перенесите на контролер три кнопки. Я назвал кнопки «Имена», «Числа» и «Цвета», хотя это и не принципиально. Разместил из по центру контроллера и задал констрейнты таким образом, чтобы кнопки оставались в центре при любом положении и размере экрана.
В нижнюю часть нашего контроллера из библиотеки элементов перетащите Container View и при помощи инструментов Auto Layout прикрепите границы контейнера к границам контроллера.
Обратите внимание, при добавлении Container View Xcode создал еще один контроллер по размеру такой же как и размер контейнера.
Так как для выполнения поставленной задачи необходима таблица, нужно выбрать вновь созданный View Controller и удалить его.
Затем из библиотеки элементов перенесите в Main Storyboard Table View Controller.
И последний этап приготовления заключается в том, чтобы вписать вновь добавленный контроллер в контейнер. Для этого ctrl-drag или правой кнопкой мыши перетащите указатель от Container View к TableViewController и выберите Embed.
Размеры табличного контроллера уменьшились до размера контейнера.
Выберете Segue и задайте идентификатор для этого перехода. Я указал InfoSegue.
Этот идентификатор нам понадобится немного ниже.
Теперь нужно создать отдельный класс и файл контроллера для нашей таблица и присвоить этот класс в Storyboard.
File -> New file -> Cocoa Touch Class. Имя можно задать любое. Я выбрал InfoTableViewController. Разумеется, наследоваться он должен от UITableViewController.
В Storyboard задайте контроллеру только что созданный класс.
Также нужно задать идентификатор для ячейки таблицы. Я указал InfoCell.
И наконец не забудьте создать методы для обработки нажатия на кнопки.
Манипуляция с данными
Для данного примера абсолютно не принципиально какие данный будут использованы в проекте. Но данные нам все равно понадобятся.
Перейдите в файл класса InfoTableViewController и добавьте массив со строками, который будет моделью данных для таблицы.
// // proSwift.ru // var modelArray = [String]()
В этом же файле добавьте/раскомментируйте/измените методы UITableViewDataSource.
// // proSwift.ru // // MARK: - Table view data source override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return modelArray.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "InfoCell", for: indexPath) cell.textLabel?.text = modelArray[indexPath.row] return cell }
Тут ничего необычного. Количество строк в таблице будет равно количеству элементов массива, а в каждую строку таблицы будем заносить элементы из массива.
Для тестирования в метод viewDidLoad() добавтье строку с добавлением тестовых данных в массив модели.
// // proSwift.ru // override func viewDidLoad() { super.viewDidLoad() modelArray = ["Гена", "Чебурашка", "Апельсины", "Фиксики", "Роботы", "Машинки", "Маша", "И Медведь", "Матроскин", "Шарик", "Печкин", "Доктор Хаус"] }
Запустите проект и вы увидите приложение с набором тестовых данных.
Передача данных из основного контроллера в таблицу
Есть два варианта передачи данных из родительского контроллера в контроллер Container View.
Передача данных на этапе инициализации.
Как и для других контроллеров, контроллер в контейнере перед инициализацией вызывает метод prepare(for segue:) у контроллера, который его отображает. Поэтому в этом методе можно передать данные в массив для отображения.
Давайте разбирать на практике.
Перейдите в файл класса ViewController и добавьте метод:
// // proSwift.ru // override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "InfoSegue" { let infoTabVC = segue.destination as? InfoTableViewController infoTabVC?.modelArray = ["proSwift.ru", "Данные", "переданные", "в", "контейнер", "при", "инициализации", "в", "методе", "родительского", "контроллера"] } }
Давайте еще раз разберем что тут происходит.
Основной ViewController для отображения содержания Container View совершает переход segue к контроллеру InfoTableViewController. Это означает, что перед переходом вызывается метод prepare(for segue:), в котором мы можем получить доступ к контроллеру, в который совершается переход. После получения ссылки на этот контроллер, мы передаем массив с данными в массив модели таблицы.
Внимание! Для проверки работоспособности этой передачи информации не забудьте в классе InfoTableViewController в методе viewDidLoad() закомментировать строку с добавлением тестовых данных.
Запустите проект заново и посмотрите результат:
Ну и наконец давайте запрограммируем передачу данных в таблицу по нажатию на кнопки.
В файл класса ViewController в методах, привязанных к кнопкам, добавьте данные следующие строчки:
// // proSwift.ru // @IBAction func nameButtonTapped(_ sender: UIButton) { let data = ["Андрей", "Сергей", "Юлия", "Елена", "Павел", "Антон", "Наташа", "Дмитрий", "Александр", "Михаил"] (childViewControllers[0] as? InfoTableViewController)?.modelArray = data (childViewControllers[0] as? InfoTableViewController)?.tableView.reloadData() } @IBAction func numberButtonTapped(_ sender: UIButton) { let data = ["Первый", "Второй", "Третий", "Седьмой", "Тринадцатый"] (childViewControllers[0] as? InfoTableViewController)?.modelArray = data (childViewControllers[0] as? InfoTableViewController)?.tableView.reloadData() } @IBAction func colorButtonTapped(_ sender: UIButton) { let data = ["Красный", "Белый", "Желтый", "Зеленый", "Синий", "Оранжевый", "Черный"] (childViewControllers[0] as? InfoTableViewController)?.modelArray = data (childViewControllers[0] as? InfoTableViewController)?.tableView.reloadData() }
Разберем эти три строки
- В массив data мы заносим данные, которые хотим передать в таблицу.
- Используя свойство childViewControllers нашего ViewController мы получаем доступ к массиву дочерних контроллеров. С учетом того, что в нашем случае он единственный, мы используем индекс [0] для получения ссылки на него. И в свойство modelArray передаем массив data
- Факта передачи данных недостаточно. Таблица уже отображает данные, которые в нее были загружены при инициализации. Хоть в массиве модели уже присутствуют другие данные, их таблица не отобразит. Именно поэтому мы вызываем метод reloadData() у свойства tableView. В этом случае новые данные отобразятся.
Запустите проект и посмотрите результат.
P.S.
На страницах нашего сайта нет примера разработки приложения на основе элемента UITableView, хотя изучение разработки мобильных приложений под iOS начинают именно с этого. Однако подобного контента очень много в интернете. Но если читателям понадобится подобная статья именно нашего авторства, то она обязательно появится. Не забывайте рассказывать о нашем ресурсе друзьям, и оставлять свое мнение в комментариях.
Автор молодец! Очень интересно, я добавил в закладки
Автор, красава, круто буду использовать в работе!
спасибо, отличная статья.. а можно практический пример о том, как использовать общие переменные для большого количества ViewController.
Например я пишу программку для тестирования, где у меня каждый ViewController это label с вопросом и с кнопками вариантов ответа на него, которые ведут к следующему ViewController, а в конце должен быть выдан результат на основе подсчитанных правильных ответов.
Можно. Я допишу статью о распознавании голоса и Speech Kit и сделаю и Ваш пример.
Если я правильно понял, то у Вас что-то типа опросника. И я не совсем понимаю, зачем для каждого вопроса использовать отдельный ViewController. Это можно, и с точки зрения эффективного использования памяти — нужно реализовать в одном контреллере. Тогда вопрос глобальной переменной решится сам собой.
В любом случае, спасибо за идею для следующей статьи.
Спасибо за отзывчивость.
Если еще не видели, то вот:
Глобальные переменные при программировании в iOS
спасибо за статью. сильно помогла
Спасибо, большое за статьи ) Очень помогают выбраться на берег из моря информации, после которой обычно в голове каша. Примеры кода с краткими и не уходящими в теорию объяснениями, отличный формат. Мои краеугольные камни: цикл жизни ViewController и его View, способы передачи данных между ViewController в разных задачах (segue, delegate), очереди-потоки т.ею распараллеливание задач в приложении (вот бы придумать сюжет с трехмерным иллюстрированием всех этих взаимодействий в iOS программировании), утечки памяти в Closure (лечение модификаторами ссылок weak, unown в зависмости от задачи), понимание взаbмосвязи между bounds, layer, frame … Хорошр было бы тренажер ещё по такмим задачам. И конечно есть ещё куча всего о чем я ещё и не слышала. Главное что я хотела сказать, это спасибо за ваш труд проводника к совершенствованию в программировании.
File -> New file -> Cocoa Touch Class. Имя можно задать любое. Я выбрал InfoTableViewCintroller. Разуметтся, наследоваться он должен от UITableViewController.
опечатка в InfoTableViewCintroller
Спасибо, поправил
Спасибо, все удобно! Но упомяните отдельно явно, что еще нужно закомментировать виртуальный метод numberOfSections() в классе InfoTableViewController перед первым запуском проекта ContainerViewTest
/*
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 0
}
*/