UIViewController внутри другого UIViewController или как работать с Container View

container_view_proswift_ru_fmekw

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

Постановка задачи

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

Создание приложения в Xcode

Откройте Xcode, выберите Single View Application

container_view_proswift_ru_f2ng3

Задайте имя и  место размещения проекта

container_view_proswift_ru_flg0a

Перейдите в Main Storyboard и из библиотеки элементов перенесите на контролер три кнопки. Я назвал кнопки «Имена», «Числа» и «Цвета», хотя это и не принципиально. Разместил из по центру контроллера и задал констрейнты таким образом, чтобы кнопки оставались в центре при любом положении и размере экрана.

container_view_proswift_ru_ggyw2

В нижнюю часть нашего контроллера из библиотеки элементов перетащите Container View и при помощи инструментов Auto Layout прикрепите  границы контейнера к границам контроллера.

container_view_proswift_ru_4dycq

Обратите внимание, при добавлении Container View Xcode создал еще один контроллер по размеру такой же как и размер контейнера.

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

container_view_proswift_ru_kfctl

Затем из библиотеки элементов перенесите в Main Storyboard Table View Controller.

container_view_proswift_ru_gxa2k

И последний этап приготовления заключается в том, чтобы вписать вновь добавленный контроллер в контейнер. Для этого ctrl-drag или правой кнопкой мыши перетащите указатель от Container View  к TableViewController и выберите Embed.

container_view_proswift_ru_5dh0c

Размеры табличного контроллера уменьшились до размера контейнера.

Выберете  Segue и задайте идентификатор для этого перехода. Я указал InfoSegue.

container_view_proswift_ru_zwoja

Этот идентификатор нам понадобится немного ниже.

Теперь нужно создать отдельный класс и файл контроллера для нашей таблица и присвоить этот класс в Storyboard.

File -> New file -> Cocoa Touch Class.  Имя можно задать любое. Я выбрал InfoTableViewController. Разумеется, наследоваться он должен от UITableViewController.

container_view_proswift_ru_9ancs

В Storyboard задайте контроллеру только что созданный класс.

container_view_proswift_ru_mpnb5

Также нужно задать идентификатор для ячейки таблицы. Я указал InfoCell.

container_view_proswift_ru_inh5p

И наконец не забудьте создать методы для обработки нажатия на кнопки.

container_view_proswift_ru_rolxo




Манипуляция с данными

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

Перейдите в файл класса 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_proswift_ru_kylc4

Передача данных из основного контроллера в таблицу

Есть два варианта передачи данных из родительского контроллера в контроллер 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() закомментировать строку с добавлением тестовых данных.

Запустите проект заново и посмотрите результат:

container_view_proswift_ru_mrmtr

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

В файл класса  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()
}

Разберем эти три строки

  1. В массив data мы заносим данные, которые хотим передать в таблицу.
  2. Используя свойство childViewControllers нашего ViewController мы получаем доступ к массиву дочерних контроллеров. С учетом того, что в нашем случае он единственный, мы используем индекс [0] для получения ссылки на него.  И в свойство modelArray передаем массив data
  3. Факта передачи данных недостаточно. Таблица уже отображает данные, которые в нее были загружены при инициализации. Хоть в массиве модели уже присутствуют другие данные, их таблица не отобразит. Именно поэтому мы вызываем метод reloadData() у свойства tableView. В этом случае новые данные отобразятся.

Запустите проект и посмотрите результат.

container_view_proswift_ru_vflca

 

P.S.

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




11 Comments on “UIViewController внутри другого UIViewController или как работать с Container View

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

    • Можно. Я допишу статью о распознавании голоса и Speech Kit и сделаю и Ваш пример.
      Если я правильно понял, то у Вас что-то типа опросника. И я не совсем понимаю, зачем для каждого вопроса использовать отдельный ViewController. Это можно, и с точки зрения эффективного использования памяти — нужно реализовать в одном контреллере. Тогда вопрос глобальной переменной решится сам собой.
      В любом случае, спасибо за идею для следующей статьи.

  2. Спасибо, большое за статьи ) Очень помогают выбраться на берег из моря информации, после которой обычно в голове каша. Примеры кода с краткими и не уходящими в теорию объяснениями, отличный формат. Мои краеугольные камни: цикл жизни ViewController и его View, способы передачи данных между ViewController в разных задачах (segue, delegate), очереди-потоки т.ею распараллеливание задач в приложении (вот бы придумать сюжет с трехмерным иллюстрированием всех этих взаимодействий в iOS программировании), утечки памяти в Closure (лечение модификаторами ссылок weak, unown в зависмости от задачи), понимание взаbмосвязи между bounds, layer, frame … Хорошр было бы тренажер ещё по такмим задачам. И конечно есть ещё куча всего о чем я ещё и не слышала. Главное что я хотела сказать, это спасибо за ваш труд проводника к совершенствованию в программировании.

  3. File -> New file -> Cocoa Touch Class. Имя можно задать любое. Я выбрал InfoTableViewCintroller. Разуметтся, наследоваться он должен от UITableViewController.

    опечатка в InfoTableViewCintroller

  4. Спасибо, все удобно! Но упомяните отдельно явно, что еще нужно закомментировать виртуальный метод numberOfSections() в классе InfoTableViewController перед первым запуском проекта ContainerViewTest

    /*
    override func numberOfSections(in tableView: UITableView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    return 0
    }
    */

Добавить комментарий

Ваш адрес email не будет опубликован.

*

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