Статьи

iOS с нуля с Swift: Основы табличного представления

Табличные представления являются одними из наиболее часто используемых компонентов инфраструктуры UIKit и являются неотъемлемой частью взаимодействия с пользователем на платформе iOS. Табличные представления делают одну вещь, и они делают это очень хорошо, представляя упорядоченный список элементов. Класс UITableView является хорошим местом для продолжения нашего путешествия по инфраструктуре UIKit, поскольку он объединяет несколько ключевых концепций Cocoa Touch и UIKit, включая представления, протоколы и возможность повторного использования.

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

Простой вид таблицы

Класс UITableView отвечает только за представление данных в виде списка строк. Отображаемые данные управляются объектом источника данных табличного представления, доступным через свойство dataSource табличного представления. Источником данных может быть любой объект, который соответствует протоколу UITableViewDataSource протоколу Objective-C. Как мы увидим позже в этой статье, источником данных табличного представления часто является контроллер представления, который управляет представлением, представлением которого является табличное представление.

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

Имея объект источника данных, управляющий его данными, и объект делегата, обрабатывающий взаимодействие с пользователем, табличное представление может фокусироваться на представлении данных. Результатом является многократно используемый и производительный компонент UIKit, который полностью соответствует шаблону MVC (Model-View-Controller), который мы обсуждали ранее в этой серии. Класс UITableView наследуется от UIView , что означает, что он отвечает только за отображение данных приложения.

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

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

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

Экземпляр UITableView состоит из строк, каждая из которых содержит одну ячейку, экземпляр UITableViewCell или его подкласс. В отличие от аналога UITableView в OS X, NSTableView , экземпляры UITableView имеют ширину в один столбец. Вложенные наборы данных и иерархии могут отображаться с помощью комбинации табличных представлений и контроллера навигации ( UINavigationController ). Мы обсудим навигационные контроллеры в следующей статье этой серии.

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

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

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

Новый проект

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

Конфигурация проекта

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

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

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

Добавление представления таблицы

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

Регулировка размеров табличного представления

Добавьте необходимые ограничения макета в табличное представление, чтобы убедиться, что табличное представление охватывает ширину и высоту своего родительского представления. Это должно быть легко, если вы читали предыдущую статью об Auto Layout.

Добавление ограничений макета

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

Табличное представление имеет два стиля по умолчанию, обычный и сгруппированный. Чтобы изменить текущий стиль табличного представления ( Обычный ), выберите табличное представление в раскадровке, откройте инспектор атрибутов и измените атрибут стиля на Сгруппированный . Для этого проекта мы будем работать с простым табличным представлением, поэтому обязательно переключите стиль табличного представления обратно на обычный.

Сгруппированное табличное представление

Вы уже знаете, что табличное представление должно иметь источник данных и делегата. На данный момент табличное представление не имеет источника данных или делегата. Нам нужно соединить dataSource и delegate выходы табличного представления к объекту, который соответствует протоколам UITableViewDataSource и UITableViewDelegate .

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

Подключение источника данных и делегата

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

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

1
2
3
4
5
6
7
import UIKit
 
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
 
  …
 
}

Прежде чем мы начнем реализовывать методы протокола источника данных, нам необходимо отобразить некоторые данные в табличном представлении. Мы будем хранить данные в массиве, поэтому давайте сначала добавим новое свойство в класс ViewController . Откройте ViewController.swift и добавьте свойство fruits типа [String] .

1
2
3
4
5
6
7
8
9
import UIKit
 
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
 
    var fruits: [String] = []
     
    …
 
}

В viewDidLoad() контроллера представления мы заполняем свойство fruits списком имен фруктов, которые мы отобразим в табличном представлении чуть позже. Метод viewDidLoad() автоматически вызывается после того, как представление контроллера представления и его подпредставления загружены в память, отсюда и название метода. Поэтому это хорошее место для заселения массива fruits .

1
2
3
4
5
override func viewDidLoad() {
    super.viewDidLoad()
     
    fruits = [«Apple», «Pineapple», «Orange», «Blackberry», «Banana», «Pear», «Kiwi», «Strawberry», «Mango», «Walnut», «Apricot», «Tomato», «Almond», «Date», «Melon», «Water Melon», «Lemon», «Coconut», «Fig», «Passionfruit», «Star Fruit», «Clementin», «Citron», «Cherry», «Cranberry»]
}

Класс UIViewController , суперкласс класса ViewController , также определяет метод viewDidLoad() . Класс ViewController переопределяет метод viewDidLoad() определенный классом UIViewController . На это указывает ключевое слово override .

Переопределение метода суперкласса никогда не бывает без риска. Что если класс UIViewController делает некоторые важные вещи в viewDidLoad() ? Как мы можем быть уверены, что ничего не viewDidLoad() при переопределении viewDidLoad() ?

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

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

numberOfSectionsInTableView(_:) представление делает это, вызывая метод numberOfSectionsInTableView(_:) для своего источника данных. Это необязательный метод протокола UITableViewDataSource . Если источник данных табличного представления не реализует этот метод, табличное представление предполагает, что ему нужно отобразить только один раздел. В любом случае мы реализуем этот метод, так как он понадобится нам позже в этой статье.

Вам может быть интересно «Что такое раздел табличного представления?» Раздел табличного представления — это группа строк. Например, приложение «Контакты» на iOS группирует контакты по первой букве имени или фамилии. Каждая группа контактов образует раздел, которому предшествует заголовок раздела вверху раздела и / или нижний колонтитул раздела внизу раздела.

Метод numberOfSectionsInTableView(_:) принимает один аргумент, tableView , который является табличным представлением, которое отправило сообщение объекту источника данных. Это важно, потому что это позволяет объекту источника данных быть источником данных нескольких табличных представлений при необходимости. Как видите, реализация numberOfSectionsInTableView(_:) довольно проста.

1
2
3
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
}

Теперь, когда табличное представление знает, сколько секций ему нужно отобразить, оно запрашивает у источника данных, сколько строк содержит каждая секция. Для каждого раздела в табличном представлении табличное представление отправляет источнику данных сообщение tableView(_:numberOfRowsInSection:) . Этот метод принимает два аргумента: табличное представление, отправляющее сообщение, и индекс раздела, табличное представление которого хочет знать количество строк.

Реализация этого метода довольно проста, как вы можете видеть ниже. Мы начинаем с объявления константы numberOfRows и назначаем ей количество элементов в массиве fruits , вызывая count в массиве. Мы возвращаем numberOfRows в конце метода.

1
2
3
4
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    let numberOfRows = fruits.count
    return numberOfRows
}

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

1
2
3
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return fruits.count
}

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

Нам нужно реализовать tableView(_:cellForRowAtIndexPath:) , еще один метод протокола UITableViewDataSource . Название метода довольно наглядно. Отправляя это сообщение своему источнику данных, табличное представление запрашивает у своего источника данных ячейку табличного представления строки, указанной в indexPath , втором аргументе метода.

Прежде чем продолжить, я бы хотел поговорить о классе NSIndexPath . Как объясняется в документации , «класс NSIndexPath представляет путь к определенному узлу в дереве коллекций вложенных массивов». Экземпляр этого класса может содержать один или несколько индексов. В случае табличного представления он содержит индекс для раздела, в котором находится элемент, и строку этого элемента в разделе.

Таблица никогда не бывает глубиной более двух уровней, первый уровень — это раздел, а второй уровень — строка в разделе. Хотя NSIndexPath является NSIndexPath классом, инфраструктура UIKit добавляет в класс несколько дополнительных методов, облегчающих работу с табличными представлениями. Давайте tableView(_:cellForRowAtIndexPath:) реализацию метода tableView(_:cellForRowAtIndexPath:) .

01
02
03
04
05
06
07
08
09
10
11
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath)
     
    // Fetch Fruit
    let fruit = fruits[indexPath.row]
     
    // Configure Cell
    cell.textLabel?.text = fruit
     
    return cell
}

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

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

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

1
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath)

Источник данных табличного представления запрашивает ячейку табличного представления для табличного представления, отправляя ему сообщение dequeueReusableCellWithIdentifier(_:forIndexPath:) . Этот метод принимает идентификатор повторного использования, который я упоминал ранее, а также путь индекса ячейки табличного представления.

Компилятор скажет вам, что cellIdentifier является «неразрешенным идентификатором». Это просто означает, что мы используем переменную или константу, которую мы еще не объявили. Над объявлением свойства fruits добавьте следующее объявление для cellIdentifier .

01
02
03
04
05
06
07
08
09
10
11
import UIKit
 
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
 
    let cellIdentifier = «CellIdentifier»
     
    var fruits: [String] = []
     
    …
 
}

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

Откройте Main.storyboard , выберите представление таблицы, которое вы добавили ранее, и откройте инспектор атрибутов справа. Поле Prototype Cells в настоящее время установлено в 0 . Создайте ячейку прототипа, установив ее в 1 . Теперь вы должны увидеть прототип ячейки в табличном представлении.

Создание ячейки прототипа

Выберите ячейку прототипа и посмотрите на Инспектора Атрибутов справа. Стиль в настоящее время установлен на Пользовательский . Измените это на Основной . Базовая ячейка табличного представления — это простая ячейка табличного представления, содержащая одну метку. Это хорошо для приложения, которое мы создаем. Прежде чем мы вернемся к классу ViewController , установите Identifier на CellIdentifier . Это значение должно совпадать со значением, присвоенным константе cellIdentifier мы объявили минуту назад.

Конфигурирование прототипа Cell

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

Аргумент indexPath метода tableView(_:cellForRowAtIndexPath:) содержит эту информацию. Как я упоминал ранее, у него есть несколько дополнительных методов, облегчающих работу с табличными представлениями. Одним из таких методов является row , который возвращает строку для ячейки табличного представления. Мы выбираем правильный фрукт, запрашивая массив fruits для item в indexPath.row , используя удобный синтаксис Swift.

1
2
// Fetch Fruit
let fruit = fruits[indexPath.row]

Наконец, мы устанавливаем текст свойства textLabel ячейки табличного представления textLabel названию фрукта, которое мы извлекли из массива fruits . Класс UITableViewCell является подклассом UIView и имеет несколько подпредставлений. Одним из этих подпредставлений является экземпляр UILabel и мы используем этот ярлык для отображения названия плода в ячейке табличного представления. Независимо от того, имеет textLabel свойство textLabel nil зависит от стиля UITableViewCell . Вот почему свойство textLabel сопровождается знаком вопроса. Это больше известно как необязательная цепочка.

1
2
// Configure Cell
cell.textLabel?.text = fruit

Метод tableView(_:cellForRowAtIndexPath:) ожидает, что мы вернем экземпляр класса UITableViewCell (или его подкласса), и это то, что мы делаем в конце метода.

1
return cell

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

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

Если мы хотим добавить разделы в табличное представление, текущего массива имен фруктов будет недостаточно. Вместо этого данные должны быть разбиты на разделы, а фрукты в каждом разделе отсортированы в алфавитном порядке. Нам нужен словарь. Начните с объявления нового свойства alphabetizedFruits типа [String: [String]] в классе ViewController .

01
02
03
04
05
06
07
08
09
10
11
12
import UIKit
 
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
 
    let cellIdentifier = «CellIdentifier»
     
    var fruits: [String] = []
    var alphabetizedFruits = [String: [String]]()
     
    …
 
}

В viewDidLoad() мы используем массив fruits для создания словаря фруктов. Словарь должен содержать массив фруктов для каждой буквы алфавита. Мы опускаем букву из словаря, если для этой конкретной буквы нет фруктов.

1
2
3
4
5
6
7
8
override func viewDidLoad() {
    super.viewDidLoad()
     
    fruits = [«Apple», «Pineapple», «Orange», «Blackberry», «Banana», «Pear», «Kiwi», «Strawberry», «Mango», «Walnut», «Apricot», «Tomato», «Almond», «Date», «Melon», «Water Melon», «Lemon», «Coconut», «Fig», «Passionfruit», «Star Fruit», «Clementin», «Citron», «Cherry», «Cranberry»]
     
    // Alphabetize Fruits
    alphabetizedFruits = alphabetizeArray(fruits)
}

Словарь создается с помощью вспомогательного метода alphabetizeArray(_:) . Он принимает массив fruits в качестве аргумента. На первый взгляд метод alphabetizeArray(_:) может показаться немного сложным, но его реализация на самом деле довольно проста.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// MARK: —
// MARK: Helper Methods
private func alphabetizeArray(array: [String]) -> [String: [String]] {
    var result = [String: [String]]()
     
    for item in array {
        let index = item.startIndex.advancedBy(1)
        let firstLetter = item.substringToIndex(index).uppercaseString
         
        if result[firstLetter] != nil {
            result[firstLetter]!.append(item)
        } else {
            result[firstLetter] = [item]
        }
    }
     
    for (key, value) in result {
        result[key] = value.sort({ (a, b) -> Bool in
            a.lowercaseString < b.lowercaseString
        })
    }
     
    return result
}

Мы создаем изменяемый словарь, result типа [String: [String]] , который мы используем для хранения массивов фруктов в алфавитном порядке, по одному массиву для каждой буквы алфавита. Затем мы перебираем элементы array , первый аргумент метода, и извлекаем первую букву имени элемента, делая его заглавными. Если result уже содержит массив для письма, мы добавляем элемент в этот массив. Если это не так, мы создаем массив с элементом и добавляем его в result .

Предметы теперь сгруппированы по первой букве. Тем не менее, группы не в алфавитном порядке. Вот что происходит во втором цикле for . Мы перебираем result и сортируем каждый массив по алфавиту.

Не беспокойтесь, если реализация alphabetizeArray(_:) не совсем понятна. В этом уроке мы фокусируемся на табличных представлениях, а не на создании алфавитного списка фруктов.

С новым источником данных первое, что нам нужно сделать, это обновить реализацию numberOfSectionsInTableView(_:) . В обновленной реализации мы просим словарь alphabetizedFruits для его ключей. Это дает нам массив, который содержит каждый ключ словаря. Количество элементов в keys равно количеству разделов в табличном представлении. Это так просто.

1
2
3
4
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    let keys = alphabetizedFruits.keys
    return keys.count
}

Нам также нужно обновить tableView(_:numberOfRowsInSection:) . Как мы сделали в numberOfSectionsInTableView(_:) , мы запрашиваем у alphabetizedFruits ключи и сортируем результат. Сортировка массива ключей важна, потому что пары ключ-значение словаря неупорядочены. В этом ключевое отличие массивов от словарей. Это то, что часто сбивает с толку людей, плохо знакомых с программированием.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    let keys = alphabetizedFruits.keys
     
    // Sort Keys
    let sortedKeys = keys.sort({ (a, b) -> Bool in
        a.lowercaseString < b.lowercaseString
    })
     
    // Fetch Fruits
    let key = sortedKeys[section]
     
    if let fruits = alphabetizedFruits[key] {
        return fruits.count
    }
     
    return 0
}

Затем мы sortedKeys ключ из sortedKeys , соответствующий section , второй параметр tableView(_:numberOfRowsInSection:) . Мы используем ключ, чтобы получить массив фруктов для текущего раздела, используя необязательную привязку. Наконец, мы возвращаем количество элементов в полученном массиве фруктов.

Изменения, которые мы должны внести в tableView(_:cellForRowAtIndexPath:) , аналогичны. Мы только изменяем способ получения имени фрукта, которое ячейка табличного представления отображает в его метке.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath)
     
    // Fetch and Sort Keys
    let keys = alphabetizedFruits.keys.sort({ (a, b) -> Bool in
        a.lowercaseString < b.lowercaseString
    })
     
    // Fetch Fruits for Section
    let key = keys[indexPath.section]
     
    if let fruits = alphabetizedFruits[key] {
        // Fetch Fruit
        let fruit = fruits[indexPath.row]
         
        // Configure Cell
        cell.textLabel?.text = fruit
    }
     
    return cell
}

Если бы вы запустили приложение, вы бы не увидели заголовков разделов, подобных тем, которые вы видите в приложении «Контакты». Это потому, что нам нужно указать табличному представлению, что оно должно отображать в заголовке каждого раздела.

Наиболее очевидным вариантом является отображение названия каждого раздела, то есть буквы алфавита. Самый простой способ сделать это — реализовать tableView(_:titleForHeaderInSection:) , еще один метод, определенный в протоколе UITableViewDataSource . Посмотрите на его реализацию ниже. Это похоже на реализацию tableView(_:numberOfRowsInSection:) . Запустите приложение, чтобы увидеть, как выглядит табличное представление с разделами.

1
2
3
4
5
6
7
8
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String?
    // Fetch and Sort Keys
    let keys = alphabetizedFruits.keys.sort({ (a, b) -> Bool in
        a.lowercaseString < b.lowercaseString
    })
     
    return keys[section]
}

В дополнение к протоколу UITableViewDataSource платформа UIKit также определяет протокол UITableViewDelegate протокол, которому должен соответствовать делегат табличного представления.

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

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

Реализовать это поведение легко. Все, что нам нужно сделать, это реализовать метод tableView(_:didSelectRowAtIndexPath:) протокола UITableViewDelegate .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
// MARK: —
// MARK: Table View Delegate Methods
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    // Fetch and Sort Keys
    let keys = alphabetizedFruits.keys.sort({ (a, b) -> Bool in
        a.lowercaseString < b.lowercaseString
    })
     
    // Fetch Fruits for Section
    let key = keys[indexPath.section]
     
    if let fruits = alphabetizedFruits[key] {
        print(fruits[indexPath.row])
    }
}

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

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

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

Этот пример является еще одной хорошей иллюстрацией разделения проблем модели Model-View-Controller (MVC), которую мы видели ранее в этой серии. Представления ничего не знают о данных приложения, кроме как их отображать. Если вы хотите писать надежные и надежные приложения для iOS, очень важно знать и соблюдать это разделение обязанностей.

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

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

Если у вас есть какие-либо вопросы или комментарии, вы можете оставить их в комментариях ниже или обратиться ко мне в Twitter .