Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
СЕССИЯ ОТВЕТЫ / iOS сессия ответы.docx
Скачиваний:
21
Добавлен:
25.12.2020
Размер:
14.45 Mб
Скачать

Делегаты

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

Чтобы наглядно понять, что я имею в виду, добавьте новый класс DateSimulator на вашу площадку. Он позволяет вашим двум классам, которые соответствуют Speaker, «пойти на свидание»:

class DateSimulator {

let a:Speaker

let b:Speaker

init(a:Speaker, b:Speaker) {

self.a = a

self.b = b

}

func simulate() {

println("Off to dinner...")

a.Speak()

b.Speak()

println("Walking back home...")

a.TellJoke?()

b.TellJoke?()

}

}

let sim = DateSimulator(a:Vicki(), b:Ray())

sim.simulate()

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

Чтобы это осуществить, создадим протокол с событиями, о которых вы хотите получать уведомления, к примеру(добавьте это до DateSimulator):

protocol DateSimulatorDelegate {

func dateSimulatorDidStart(sim:DateSimulator, a:Speaker, b:Speaker)

func dateSimulatorDidEnd(sim:DateSimulator, a: Speaker, b:Speaker)

}

Затем создайте класс, который подчиняется протоколу (добавьте его сразу после DateSimulatorDelegate):

class LoggingDateSimulator:DateSimulatorDelegate {

func dateSimulatorDidStart(sim:DateSimulator, a:Speaker, b:Speaker) {

println("Date started!")

}

func dateSimulatorDidEnd(sim:DateSimulator, a: Speaker, b: Speaker) {

println("Date ended!")

}

}

Для простоты, вас просто информируют об этих событиях.

Затем, добавим новое свойство для DateSimulator, которое принимает класс, который подчиняется этому протоколу:

var delegate:DateSimulatorDelegate?

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

Вы наверное обратили внимание на то, что мы сделали опционал. Таким образом, DateSimulator должен работать нормально и при созданном делегате, и при отсутствии делегата.

Прямо перед строкой sim.simulate() установите переменную вашему LoggingDateSimulator:

sim.delegate = LoggingDateSimulator()

Наконец, модифицируйте вашу функцию simulate() так, чтобы она могла вызывать делегата и в начале, и в конце метода.

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

Для проверки вашего решения смотрим код ниже:

class DateSimulator {

let a:Speaker

let b:Speaker

var delegate:DateSimulatorDelegate?

init(a:Speaker, b:Speaker) {

self.a = a

self.b = b

}

func simulate() {

delegate?.dateSimulatorDidStart(self, a:a, b: b)

println("Off to dinner...")

a.Speak()

b.Speak()

println("Walking back home...")

a.TellJoke?()

b.TellJoke?()

delegate?.dateSimulatorDidEnd(self, a:a, b:b)

}

}

Файл playground с самого начала и до этого момента.

Теперь, сохраните свой файл и давайте вернемся к площадке, которую мы сохранили раньше для того, чтобы модернизировать наш класс TipCalculatorModel. Самое время вписать его в таблицу!

Таблицы, делегаты и источники данных

Сейчас, вы понимаете концепции протоколов и делегатов, и готовы к использованию таблиц (table views) вашего приложения.

Так уж получилось, что у таблиц (table views) есть свойство delegate, и вы можете установить его для класса, который подчиняется UITableViewDelegates. Это протокол с кучей опциональных методов. К примеру, есть метод, который определяет какой ряд выбран в таблице или, когда запущено ее редактирование.

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

Свойство delegate опционально, но dataSource обязательно. Что ж, начнем с того, что зададим источник данных для вашего калькулятора чаевых.

Одна из крутых штук игровых площадок в том, что вы можете прототипизировать(это слово-опционал 8]) и визуализировать элементы (к примеру UITableView). Это удобный способ посмотреть на работу элемента до того, как интегрировать его в проект.

Вновь убедитесь, что вы на площадке, с обновленным и улучшенным классом TipCalculatorModel. Добавьте этот код в самый низ файла:

// 1

import UIKit

// 2

class TestDataSource : NSObject {

// 3

let tipCalc = TipCalculatorModel(total: 33.25, taxPct: 0.06)

var possibleTips = Dictionary<Int, (tipAmt:Double, total:Double)>()

var sortedKeys:[Int] = []

// 4

override init() {

possibleTips = tipCalc.returnPossibleTips()

sortedKeys = sorted(Array(possibleTips.keys))

super.init()

}

}

Давайте все последовательно разберем.

  • Для использования таких классов UIKit как UITableView, сначала вам нужно импортировать фреймворк UIKit. Если у вас появляется ошибка, то идите в File Inspector (View\Utilities\Show File Inspector) и установите Platform на iOS.

  • Одно из требований реализации UITableViewDataSource в том, что ваш класс расширяет NSObject (непосредственно, либо через промежуточные классы).

  • Здесь инициализируем калькулятор и создаем пустой словарь возможных чаевых, а также массив отсортированных ключей. Обратите внимание, что possibleTips и sortedKeys - переменные, а не константы, потому что пользователю необходимо, чтобы значения менялись.

  • Метод init - присваиваете двум переменным исходные значения. Обратите внимание, что вы должны обозначить метод как override, так как, вы переписываете метод класса NSObject.

У вас есть база, что ж давайте подчиним наш класс протоколу UITableViewDataSource. Чтобы это сделать, мы должны прописать имя-протокол в объявлении класса:

class TestDataSource: NSObject, UITableViewDataSource {

Затем, добавьте вот этот новый метод:

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return sortedKeys.count }

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

Следующим, добавим другой метод, который так же обязателен:

// 1

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

// 2

let cell = UITableViewCell(style: UITableViewCellStyle.Value2, reuseIdentifier: nil)

// 3

let tipPct = sortedKeys[indexPath.row]

// 4

let tipAmt = possibleTips[tipPct]!.tipAmt

let total = possibleTips[tipPct]!.total

// 5

cell.textLabel?.text = "\(tipPct)%:"

cell.detailTextLabel?.text = String(format:"Tip: $%0.2f, Total: $%0.2f", tipAmt, total)

return cell

}

Идем как обычно, секция за секцией:

  • Этот метод вызывается для каждой строки-таблицы. Вы должны вернуть view (элемент), который является рядом таблицы, который в свою очередь, является подклассом UITableViewCell.

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

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

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

  • Встроенный UITableViewCell имеет два встроенных свойства для описания ярлыка или этикетки: textLabel, detailTextLabel. Как раз здесь, вы можете их установить и вернуть ячейку.

И наконец, вы можете протестировать ваш табличный вид приложения, добавив следующий код в конец вашего файла playground (площадки):

let testDataSource = TestDataSource()

let tableView = UITableView(frame:CGRect(x: 0, y: 0, width: 320, height: 320), style:.Plain)

tableView.dataSource = testDataSource

tableView.reloadData()

Это создает вид таблицы заданного размера, и устанавливает источник данных для нового класса. Затем, он вызывает reloadData(), чтобы обновить вид таблицы.

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

Файл playground'а до этого момента

Мы закончили с прототипом вашего нового и улучшенного TipCalculatorModel, и новым табличным видом приложения. Теперь, самое время интегрировать ваш код в проект!

Соседние файлы в папке СЕССИЯ ОТВЕТЫ