Приложение-справочник с UITableViewController

Приложение-справочник с UITableViewController

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

Создадим новый проект под названием Apple Gallery со следующими параметрами:

  • Project template: Single View Application;
  • Product name: Apple Gallery;
  • Language: Swift.
  • Devices: iPhone.

Интерфейс

Сразу же откроем сториборд, отключим опцию Use Size Classes и приступим к созданию интерфейса. Xcode уже любезно создал контроллер для нас, но он нам не понадобится. Удалите его и перетащите UINavigationController из Object library. Как мы уже знаем, Xcode автоматически добавит контроллер навигации с вложенным UITableViewController. Выделите первый контроллер и в Attributes inspector включите опцию Is Initial View Controller:

Приложение-справочник с UITableViewController

Включение опции Is Initial View Controller

Запустите приложение и убедитесь, что приложение отображает пустую таблицу.

Мы еще не научились делать приложения с «гибким» ннтерфейсом, но в будущем обязательно это сделаем. Но на данный момент, в качестве модели симулятора, используйте iPhone 5 или 5s.

Теперь выделим ячейку в контроллере Root View Controller и всё в том же Attributes inspector зададим полю Identifier значение «Cell». Также в опции Style выберите опцию Subtitle. В результате ячейка должна иметь следующий вид:

Приложение-справочник с UITableViewController

Ячейка со стилем Subtitle

Также изменим title контроллера UITableViewController с Root View Controller на Apple Gallery.

Добавим один стандартный контроллер представления справа от UITableViewController. Поместим на него:

  1. UIImageView с параметрами:
    1. Attributes inspector:
      1. Mode: Aspect fill.
    2. Size inspector:
      1. X: 20.
      2. Y: 76.
      3. Width: 80.
      4. Height: 80.
  2. UILabel:
    1. Attributes inspector:
      1. Font: System Light 21.0.
      2. Lines: 0.
    2. Size inspector:
      1. X: 108.
      2. Y: 76.
      3. Width: 192.
      4. Height: 80.
  3. UITextView:
    1. Attributes inspector:
      1. Font: System 17.0.
      2. Editable: No.
      3. Selectable: No.
    2. Size inspector:
      1. X: 20.
      2. Y: 164.
      3. Width: 280.
      4. Height: 384.

В результате контроллер должен иметь следующий вид:

Приложение-справочник с UITableViewController

Теперь нужно связать между собой UITableViewController и только что созданный контроллер представления. Нам нужно, чтобы приложение открывало UIViewController при нажатии на ячейку в Root View Controller, поэтому связь должна быть не от контроллера к контроллеру, а от ячейки к контроллеру:

Приложение-справочник с UITableViewController

Добавление связи между ячейкой и контроллером

В качестве последнего штриха в построении интерфейса, нам осталось дать идентификатор связи. Выделите segue и в Attributes inspector в поле Identifier укажите «goDetail». Также изменим title контроллера представления на Info.

Заметьте, что при выделении segue, Xcode подсветит его источник для Вас. В нашем случае это ячейка таблицы.

Архитектура приложения

Завершив построение интерфейса, мы можем перейти к написанию архитектуры самого приложения. У нас есть два новых контроллера: UITableViewController и UIViewController, значит необходимо создать им классы и связать их между собой. Добавьте два новых класса так, как мы это делали в прошлых уроках:

  1. ListTVC (Subclass of: UITableViewController).
  2. DetailVC (Subclass of: UIViewController).

Перед тем как мы начнем имплементацию работы интерфейса, добавим еще один класс: Product (Subclass of: NSObject). Он будет выступать в качестве модели данных, которую мы будем использовать для отображения данных о продуктах. Пусть Product будет содержать следующие атрибуты:

Также для удобства добавим еще два метода, один из которых поможет быстро инициализировать класс со всеми атрибутами, а второй будет возвращать нам объект UIImage согласно значению переменной imagePath:

Модель готова, так что можем двигаться дальше.

Asset каталог

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

  • iPhone 6S;
  • iPad Pro;
  • iMac;
  • Macbook Pro;
  • Apple Watch.

Постарайтесь подобрать изображения равные по пропорциям (1:1, 4:3 и т.д.)

Добавим картинки в проект, вот только на этот раз сделаем это правильно. Начиная с iOS 7 у нас имеется возможность хранить все изображения в специальном каталоге. Xcode уже создал его для Вас. В навигаторе откройте Assets.xcassets. Здесь имеется возможность хранить все иконки приложения, сплэш скрины и изображения. Главной особенностью является не только каталогизация всех изображений в одном месте, но и возможность указывать для какого девайса с каким размером экрана данная картинка будет использоваться.

Добавьте новый набор изображений, нажав на «+» в нижней части экрана и выбрав вариант New image set:

Приложение-каталог с UITableViewController

Добавление нового набора изображений (image set)

Теперь в правой части asset-каталога мы видим группу Image. В данную группу можно задать 3 изображения — для каждой резолюции экрана. При разработке приложений, в качестве якорей размеров выступают точки (pt), в то время как само изображение на экране реального устройства измеряется в пикселях (px). Поэтому все устройства Apple условно делятся на 3 вида согласно типу экрана:

  • 1x — устройства со стандартным экраном. Здесь 1pt = 1px.
  • 2х — устройства с Retina-дисплеем (iPhone 4+, The New iPad+ и т.д.). У этих девайсов 1pt = 2px.
  • 3х — к данным устройствам, на момент написания урока, относятся iPhone 6(s) Plus и iPad Pro.

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

Переименуйте только что созданный набор в im_iphone. Выделите его, нажмите Enter или дважды медленно кликните и введите нужное название. Теперь перетащите загруженное изображение из Finder в ячейку 2х. Повторите то же самое с оставшимися картинками. Для избежания расхождения в коде, назовите их следующим образом:

  • im_iphone;
  • im_ipad;
  • im_imac;
  • im_macbook;
  • im_apple_watch.

UITableViewDataSource & UITableViewDelegate

Откроем ListTVC в Editor’е и посмотрим на его содержимое. Здесь мы видим гараздо большее количество методов, чем при создании класса UIViewController. При рассмотрении всех основных контроллеров представления в iOS, мы упоминали о том, что работа UITableViewController строится на таких понятиях как DataSource и Delegate.

По-сути, UITableViewController — это класс, который стоит за имплементацией работы UITableView. Класс весьма «гибкий» и у него нет какой-либо привязки к типу данных, поэтому в таблице Вы можете отображать всё что душе угодно. Что и как — зависит только от Вас. Соответственно, эти данные необходимо передавать из класса в таблицу. Здесь на помощь и приходит UITableViewDataSource — набор методов, с помощью которых UITableView получает эти самые данные. Весь дата сурс таблицы «стоит на трёх китах» — трёх обязательных методах:

  • numberOfSectionsInTableView: — запрашивает количество секций, которые должны быть отображены;
  • tableView:numberOfRowsInSection: — запрашивает количество ячеек, которые должны отображаться у каждой секции;
  • tableView:cellForRowAtIndexPath: — запрашивает саму ячейку, для конкретной секции.

Первые два метода вызываются таблицей только при загрузке данных: при инициализации контроллера и при вызове функций типа reloadData(). Последний метод из этого списка вызывается каждый раз, когда какая-либо ячейка должна отобразиться: при загрузке данных, при скролле и т.д.

Метод tableView:cellForRowAtIndexPath: закомментирован по-умолчанию, поэтому раскомментируйте его (удалите символы /* перед методом, и символы */ после).

Помимо Data Source, упоминалось и такое понятие как делегирование. Делегирование используется при работе с событиями и, в своей простейшей форме, это просто механизм обратного вызова (callback). UITableViewDelegate позволяет «отлавливать» различные события, которые происходят с таблицей. К примеру, вот тройка из них:

  • tableView:didSelectRowAtIndexPath: — вызывается когда юзер совершил нажатие на ячейку таблицы;
  • tableView:willBeginEditingRowAtIndexPath: — вызывается в тот момент, когда ячейка только переходит в режим редактирования;
  • tableView:heightForRowAtIndexPath: — вызывается когда таблице нужно знать высоту ячейки.

UITableViewController

Теперь, когда все ресурсы на месте и мы знаем что такое Data Source и Delegate — перейдем непосредственно к реализации класса ListTVC. Создадим источник данных. Объявим массив объектов Product под названием allData:

В методе viewDidLoad() заполним allData контентом:

Обратите внимание, что для атрибута imagePath мы указываем имя, которое дали наборам изображений в asset каталоге.

Вся информация для атрибутов info взята с официального сайта Apple.

Заполним методы UITableViewDataSource следующим образом:

Вы могли заметить, что при работе с таблицами мы сталкиваемся с объектом indexPath. Его классом является NSIndexPath, который хранит в себе два значения: section (номер секции) и row (номер) ячейки в таблице. Нумерация начинается с 0. Т.е. 0 = 1, 1 = 2, 2 = 3  и т.д.

Запустите приложение и посмотрите всё ли у Вас работает. В результате апп должен отображать 5 продуктов с краткой информацией:

Приложение-справочник с UITableViewController

Список продуктов Apple Gallery

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

Откройте DetailVC и добавьте связи между контроллером и добавленными объектами интерфейса (UILabel, UITextView, UIImage) следующим образом:

Объявите переменную Product:

Теперь добавим метод контроллера представления viewWillAppear() и напишем в нем отображение данных в интерфейсе:

На этом контроллере всё готово, теперь имплементируем саму передачу объекта Product из ListTVC в DetailVC. Откройте контроллер с таблицей и опуститесь вниз к методу prepareForSegue:. Раскомментируйте его. Данный метод срабатывает в тот момент, когда юзер осуществляет переход с одной сцены на другую с помощью segue. Здесь мы и передадим нужный объект Product:

Запустите приложение и проверьте всё ли работает правильно:

Приложение-справочник с UITableViewController

Подробная информация о выбранном продукте

Поздравляем! Вы только что написали своё первое iOS приложение-справочник для iPhone и iPod Touch с использованием UITableViewController. Для того чтобы закрепить результат, попробуйте модифицировать приложение, добавив больше продуктов и больше полей с информацией.

  • abara_kedavra

    на последнем пункте что-то пошло не так: «value of type ‘DetailVC’ has no member «allData»»…

    • VladislavKovalyov

      Здравствуйте! Убедитесь, что Вы работаете над функцией prepareForSegue (prepareFor в Swift 3) в контроллере ListVC. Судя по ошибке, Вы пытаетесь передать объект продукта из DetailVC.

      Имплементация функции prepareForSegue должна происходить в контроллере-родителе, так как именно здесь указывается «что» и «куда» передать. В нашем случае это передача product из ListVC в DetailVC.

      • abara_kedavra

        Спасибо за ответ! Действительно, не оттуда передавал. Теперь build проходит успешно, но при попытке перехода с таблички в info вылетает с ошибкой «thread 1: signal SIGBRT». В AppDelegate.swift стоит красная метка на строчке «class AppDelegate: UIResponder, UIApplicationDelegate «. Жаль, что по дебаггингу нет урока =)

        • VladislavKovalyov

          Что Xcode пишет в логах во время краша?

          • abara_kedavra

            загуглил ошибку из лога «terminating with uncaught exception of type NSException»- как я понял, такое бывает когда обнаруживаются какие-то не действующие связи. Проверил все что есть в connections — ничего не нашел. После этого стал проверять все объекты, и обнаружил, что подкласс у DetailVC не UIViewController, а UITableViewController. Досадная ошибка =) Пересоздал класс и все заработало. Спасибо, отличные уроки. Надеюсь, что будет продолжение!

  • Роман Нирка

    Подскажите пожалуйста, где находятся следующие методы:

    В методе viewDidLoad() заполним allData контентом: ???

    Заполним методы UITableViewDataSource следующим образом: ???

    https://uploads.disquscdn.com/images/b46898ba40cb0bfab24038126e5a9ccfeee7b2af4c0f011b9477988f3aba177d.png

  • Sergey Pinigin

    Здравствуйте. Подскажите, как в данном примере мы обращаемся к ячейке через имена полей
    // Заполняем ячейку данными
    cell.imageView?.image = product.getImage()
    cell.textLabel?.text = product.name
    cell.detailTextLabel?.text = product.type
    Как задаются эти имена полей cell.imageView, cell.textLabel, cell.detailTextLabel. Я выполнил этот урок, после сел писать что-то своё и никак теперь не могу понять, как обратиться к полям ячейки.