Что такое delegate в swift
Почему нам нужен Delegate в iOS и WatchOS?
Около двух лет назад кто-то задал мне хороший вопрос: «Почему нам нужны делегаты для UIViewControllers?» Он думал, что Swift многое облегчил, но вся эта штука с делегатами кажется очень сложной. Почему просто нельзя посылать сообщения или инициализации между классами?
Когда я впервые изучал iOS, я признал, что у меня ушли месяцы, чтобы понять, что произошло с делегацией. Я нашел много непонятного кода и немного объяснений. Когда я работал над этим, результата было мало. В большинстве случаев туториалы ссылались на информацию о том, как использовать стандартный делегат Apple, но не показывали, как создавать свой отклик. Эти отклики необходимы для полного понимания делегатов.
Я решил, что время обновить статью и включить два примера с которыми разработчики могут столкнуться: iOS и watchOS версии. Вместе с взрослением watchOS в watchOS 3, я думаю, многие разработчики начнут смотреть в сторону разработки приложений для часов и там могут столкнуться с непонятными вещами.
Что такое Class?
Давайте начнем с начала, что бы все понимали проблему. До тех пор, пока мы используем классы в объектно-ориентированном программировании, стоит хорошо понимать, что они из себя представляют. Class — это коллекция данных, которые мы называем properties (свойства) и действий methods (методы) к properties.
Properties и methods могут быть public (публичными) или private (приватными). Public method видят и используют классы, кроме определяющего. Private значит, что properties или methods видны и используются внутри определяющего класса, а другие классы не в состоянии увидеть или воспользоваться ими. В Swift private делает properties и methods приватными. Вычисление properties это другой способ сделать properties приватными. Кроме того, в Swift существует стандартное состояние, которое делает method или class публичным только для текущей цели.
Многим разработчикам не нравится, когда играются с их кодом, а это тоже хороший опыт программирования. В целом, стоит стремиться к тому, чтобы оставлять публичным необходимый минимум для других классов. Сохранение properties и methods приватными и сокрытие классов называется encapsulation (инкапсуляция).
Инкапсуляция позволяет строить код, как если бы мы строили дом из блоков. Как и у обычного кирпича есть несколько применений, так и у класса есть несколько используемых methods. После этого они могут присоединять множество других кирпичей.
Что такое Model — View — Controller или MVC?
Часто встречаемый термин MVC среди разработчиков, это не эксклюзивный термин для Xcode проектов. Это последовательность программирования, хорошая организация любой программы или приложения в графическом окружении и в целом окружении, которое взаимодействует с пользователем. MVC разделяет главные части приложения. Во первых разделяет данные и взаимодействие с пользователем и добавляет посредника между ними. Почему это так важно? Вы можете написать и опубликовать приложение для iPhone, затем решить, что версия для iPad это неплохая идея, а затем решить добавить версию для часов. С MVC вы поменяете только одну часть полностью — View и, возможно, немного Controller. Код, содержащий данные никогда не меняется между версиями. Это сохранит много времени и сил.
Что такое Model?
Существуют части программы работающие с данными, которые мы хотим обработать. Система заказа пиццы имеет данные о любом заказе, где могут содержаться ссылки на более подробные данные о каждом клиенте и пицце. Это наша Model в системе заказа пиццы: коллекция всех данных, используемых в системе заказов. Она никак не должна контактировать с пользователем, ничего не просит указать и не отображает. Это просто данные. Вот пример простой модели:
Эта модель состояния переключателя. Это данные. Модель имеет два methods: textState() — описывает состояние переключателя, как String, overLoadSwitch() выключает переключатель, если количество пользователей * загрузку больше 100. Есть еще много методов, которые я должен добавить для описания переключателя, но любые методы изменяют или описывают только данные. Тут нет никакого ввода данных от пользователя. Model может выполнять расчеты, но опять же, тут нет никакого взаимодействия с пользователем.
Что такое View?
Что такое Controller?
Сердце MVC объединяет View и Model. Называемый Controller или ViewController, координирует то, что происходит в данных или View. Если пользователь нажимает кнопку, Controller отвечает на это событие. Если этот ответ означает посыл сообщения Model, ViewController делает это. Если ответ требует вывода данных из Model, Controller делает и это тоже. В Xcode, @IBOutlet и @IBAction объединяют файлы содержащие views в Interface Builder и ViewController.
Ключ к MVC — коммуникация. Если быть точным, ее недостаток. MVC воспринимает инкапсуляцию очень серьезно. View и Model никогда не общаются напрямую между собой. Controller может отсылать сообщения View и Controller. View и Controller могут выполнять внутренние действия в ответ на сообщения, как вызов метода или могут возвращать значение Controller’у. Controller никогда не производит изменения напрямую ни в Model, ни в View.
Итак, View, Model и Controller не изменяют свойства друг друга. View и Model могут не общаться друг с другом вообще. View может говорить Controller, что есть изменение в Model. Controller может посылать сообщения в форме вызова методов к View, Model и принимать ответы через этот метод.
Как MVC связывается с другими MVC
Все, что мы сейчас обсудили, касается только одной scene в довольно большом приложении. Предположим, у меня есть подобный watchOS storyboard:
Имеется кнопка Switch, которая загружает другой View, имеющий переключатель. Когда я решу, как должен располагаться переключатель, я нажму Done.
Легкий путь
В Xcode у нас есть segues (сегвей). Сегвеи удобны для указания от одного ViewController к другому. Когда сегвей делает сложные для нас вещи проще и мы передвигаемся от одного Controller к другому, сегвей говорит системе открыть определенный ViewController, а затем открывает View и Model.
Model и View в новой MVC настройке отличаются от вызвавшего. Apple включила метод prepare(for segue:) для iOS, который дает нам шанс установить значения в новом ViewController и соответственно в новых ViewController’s View и Model.
С появлением WatchOS появился слегка другой подход. Вместо доступа к полной Model, watchOS отправляет новым контроллерам единственное значение, называемое context. Так как оно типа Any?, вы можете передавать ему любые значения. Чаще всего разработчики передают dictionaries значений другим контроллерам через contextForSegue метод.
Для идентичных приложений для iPhone и часов я могу нажать кнопку Switch. Она запускает интерфейс переключателя и передает значение переключателю false.
Проблемный путь
Мы можем включать и выключать переключатель довольно легко, но когда мы нажимаем Done, чтобы отправить его обратно оригинальному контроллеру вот тогда и появляется проблема. По правилам MVC нам нужен метод, возвращающий значение. Где в вызываемом instance мы можем вернуться к его вызвавшему его классу?
С инкапсулированным классом мы не можем. В данной ситуации нет возможности отправить пересмотренную Model обратно оригинальному контроллеру без поломки инкапсуляции или MVC. Новый ViewController ничего не знает о классе который его вызвал. Похоже мы застряли. Если мы попытаемся создать reference напрямую к вызвавшему контроллеру, мы вызовем reference loop, который убьет память. Проще говоря, мы не можем отправлять данные обратно.
Эту проблему делегаты и протоколы решают будучи немного проворными. Представьте себе другой класс, который на самом деле является скелетом класса. Этот класс содержит только методы. Он декларирует определенные методы в этом классе, но никогда не использует их. В Swift они называются протоколы. Мы создаем протокол, содержащий один метод. Этот метод это то, что вы делаете когда заканчиваете с переключателем и хотите вернуться к вызвавшему контроллеру. Он имеет несколько параметров, данных для передачи обратно вызвавшему контроллеру. Он может выглядеть так:
Я передал обратно значение переключателя в данном случае.
В контроллере с переключателем мы создаем instance этого протокола и называем его делегат.
Так как мы имеем свойство типа SwitchDelegate, мы можем использовать методы типа SwitchDelegate, например метод didFinishSwitch. Мы можем привязать выполнение данного метода к кнопке Done:
Как только вы сделаете это, вы получите ошибку компиляции, так как метод протокола не существует в классе. В коде, для адаптации класса на примере OrderPizzaViewController, мы используем метод:
Мы возвращаем данные обратно и то, что нам нужно с ними. В этом случае, будет присвоен текст, отображаемый в Label.
Еще один шаг. Во время возврата в назначенный контроллер, я сказал, что делегат является instanc’ом протокола, но я не сказал, где делегат находился. В prepare(for Segue:) я добавил дополнительную строчку vc.delegate = self, говорящий протоколу о вашем контроллере:
В WatchOS все становится немного по другому. У меня есть только один передаваемый context, switchState и делегату. Для нескольких значений разработчики обычно используют dictionary вроде этого:
awake метод имеет код для извлечения dictionary и присвоения значений. Когда мы нажимаем Done на часах или в приложении, запускается метод, он знает, что находится в вызывающем контроллере и будет вызван там, где мы добавили его к оригинальному классу. data это параметр, что бы программа могла с легкостью переносить в контроллер и в Model. Делегаты и протоколы довольно хитрые, но это работает и является одной из самых важных техник при работе с ViewController’ами.
Основной вопрос заключался в том, почему Swift не сделал все это проще. Так как я показал тут все в контексте Swift, не имеет значения какой объектно — ориентированный язык вы используете. Делегация это часть последовательности MVC, которая является важным навыком программирования.
Understanding Delegates and Delegation in Swift 4
I’m going to talk about “delegates” and “delegation.” I’ll lead you through a simple example of implementing the delegation design pattern in Swift 4, with full source code. My intent here is to show you how delegation works without getting bogged down in some crazy complex example. To help you become the best of the best, I’m going to introduce you to one of the greatest design tools to aid in object-oriented software development, UML. I’ll show you a UML diagram that I drew up to design and document the implementation of the delegation design pattern used in the sample app we’ll build together.
I’ll show you how to build a user interface (UI) helper, a class that downloads a file at a specified URL. Most importantly, I’ll show you how, through delegation, a UIViewController subclass can be notified by the helper that an image file has finished downloading, and then the view controller can display the image on screen. For the sake of simplicity and clarity, we’ll pretend that Swift has minimal support for downloading a file from a URL. We’ll manually wire up the notification that the file has finished downloading using the delegation design pattern. Here’s the app we’ll build:
Recommended reading
In order to help you in understanding how I build my sample delegation code herein, you should read the following articles:
Delegation
Let’s start with a layman’s definition of some terms.
The dictionary definition of “delegate” (Cambridge) is “to give a particular job, duty, right, etc. to someone else so that they do it for you.” The dictionary definition of “delegation” (Merriam-Webster) is “the act of empowering to act for another.”
In a nutshell, “Delegation is a design pattern that enables a class or structure to hand off (or delegate) some of its responsibilities to an instance of another type.”
You’ve all probably used delegation. It would be hard not to in iOS.
Here’s an example of delegation and UICollectionViewDelegate (click link).
Delegation is a simple and powerful pattern in which one object in a program acts on behalf of, or in coordination with, another object. The delegating object keeps a reference to the other object–the delegate–and at the appropriate time sends a message to it. The message informs the delegate of an event that the delegating object is about to handle or has just handled. The delegate may respond to the message by updating the appearance or state of itself or other objects in the application, and in some cases it can return a value that affects how an impending event is handled. The main value of delegation is that it allows you to easily customize the behavior of several objects in one central object. …
A delegate is an object that acts on behalf of, or in coordination with, another object when that object encounters an event in a program. The delegating object is often a responder object–that is, an object inheriting from NSResponder in AppKit or UIResponder in UIKit–that is responding to a user event. The delegate is an object that is delegated control of the user interface for that event, or is at least asked to interpret the event in an application-specific manner. …
The programming mechanism of delegation gives objects a chance to coordinate their appearance and state with changes occurring elsewhere in a program, changes usually brought about by user actions. More importantly, delegation makes it possible for one object to alter the behavior of another object without the need to inherit from it. The delegate is almost always one of your custom objects, and by definition it incorporates application-specific logic that the generic and delegating object cannot possibly know itself. …
Requirement: a protocol
In order for me to write sample code implementing the delegation design pattern, I’ll need a “protocol.” Refer to my article on protocols and remember that, as Apple puts it (my emphasis added):
Delegation is a design pattern that enables a class or structure to hand off (or delegate) some of its responsibilities to an instance of another type. This design pattern is implemented by defining a protocol that encapsulates the delegated responsibilities, such that a conforming type (known as a delegate) is guaranteed to provide the functionality that has been delegated. Delegation can be used to respond to a particular action, or to retrieve data from an external source without needing to know the underlying type of that source.
A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements. Any type that satisfies the requirements of a protocol is said to conform to that protocol. …
Explaining the code
Piece: the delegating object
I’m going to build a class that implements an image downloader, the LogoDownloader class, something that, given a URL, can download a file asynchronously and provide notice upon download completion. This will be the “delegating object,” as defined above, that “keeps a reference to the other object–the delegate–and at the appropriate time sends a message to it.”
Piece: the delegate protocol
A protocol needs to be defined. Remember that the delegation “design pattern is implemented by defining a protocol that encapsulates the delegated responsibilities, such that a conforming type (known as a delegate) is guaranteed to provide the functionality that has been delegated” (my emphasis added). This will be the LogoDownloaderDelegate protocol.
Piece: the delegate
UML: how the pieces fit together
I drew a UML diagram before I got very far into the code. UML is the best tool I’ve found for designing object-oriented software. Take a look at my diagram, refer to the legend, and read these references on UML here, here, here, and here. Here’s the diagram:
Code demo
Here’s what the app I’m building does:
Writing the code
Piece: the delegating object
Delegation in Swift
Just like the observer pattern, which we took a look at in the past two posts, the delegate pattern can be implemented in many different ways. This week, let’s take a look at a few of those ways, along with their pros and cons.
Instabug : Whether it’s crashes, slow screen transitions, delayed network calls, or unresponsive UIs — Instabug automatically gives you all of the logs you need to fix bugs and issues, and to ship high-quality apps. Get started now.
When to delegate
The main benefit of delegating certain decisions and behaviors to a type’s owner is that it becomes much easier to support multiple use cases without having to create massive types that themselves need to account for all those use cases.
Protocols
When implementing our own delegate protocols, it’s usually a good idea to try to follow the naming conventions that have been established through Apple’s own use of this pattern. Some quick guidelines that are good to keep in mind:
The advantage of taking the protocol-based route is that it’s an established pattern that most Swift developers are familiar with. It also groups all events that a type ( FileImporter in this case) can emit into one single protocol, and the compiler will give us errors in case something isn’t correctly implemented.
Closures
One way that we can make the above code a bit more predictable is to refactor the decision-making part of our delegate protocol to use a closure instead. That way, our API user will be required to specify the logic used to decide which files that will be imported up-front, removing the ambiguity in our file importer’s logic:
With the above change in place, we can now go ahead and remove the shouldImportFile method from our delegate protocol, leaving us with only methods related to changes in state:
The main advantage of the above is that it now becomes much harder to use our FileImporter class «the wrong way», since it’s now completely valid to use it without even assigning a delegate (which in this case might be useful in case some files should be imported in the background and we’re not really interested in the outcome of the operation).
Configuration types
One way of solving that dilemma is to use a dedicated configuration type. By doing so we can achieve the same nice grouping of events, just like we had with our original delegate protocol, while still enabling a lot of freedom when implementing the various events. We’ll use a struct for our configuration type and add properties for each event, like this:
As a quick side note; by defining struct convenience initializers in extensions instead of on the type itself, we can still keep the default compiler-generated initializer.
We can even create static convenience APIs for common configurations that doesn’t require any parameters, for example a variant that simply imports all files:
Which we can then use using Swift’s really elegant dot syntax, making for an incredibly easy to use API, that still offers a lot of customization and flexibility:
Support Swift by Sundell by checking out this sponsor:
Instabug : Whether it’s crashes, slow screen transitions, delayed network calls, or unresponsive UIs — Instabug automatically gives you all of the logs you need to fix bugs and issues, and to ship high-quality apps. Get started now.
Conclusion
Using delegate protocols provide a familiar and solid pattern that is a good default for many use cases. Closures add more flexibility, but can also lead to more complicated code (not to mention accidental retain cycles if the delegating object ends up capturing its owner in one of its closures). Configuration types can provide a nice middle ground, but also require a bit more code (although, like we’ve seen, with the right convenience APIs in place our code can actually end up becoming a lot simpler).
Thanks for reading! рџљЂ
More on similar topics
Swift clip: Managing URLs and endpoints
5: “Escape into the open source world” with special guest Louis D’hauwe
Swift Часть 3: Кортежи, протоколы, делегаты и табличный формат
Swift Часть 3: Кортежи, протоколы, делегаты и табличный формат
Поехали!
Сейчас, ваш калькулятор предлагает посчитанные чаевые, для каждого учтенного с них процента. Однако, как только вы выберете необходимое количество, которое хотите оставить, то вам будет необходимо посчитать общий счет, с учетом этих чаевых. Вот тут-то и вскипает голова!
Было бы неплохо, если бы ваш метод calcTipWithTipPct() возвращал два значения: количество чаевых и общий счет с чаевыми.
Безымянные кортежи
Давайте создадим что-то, что называется «безымянным кортежем». Впишите следующее:
Тут, вы группируете два значения типа Double (tip и total) в одном значении кортежа. Здесь работает вывод типа, так как компилятор может вывести тип по значениям, которые вы присвоили. Альтернативно вы можете явно указать типы:
Для доступа к определенному элементу кортежа вы можете использовать индекс элемента или его имя. Добавим следующий код:
Вы должны увидеть 4.0 и 25.19 в сайдбаре вашей площадки. Используйте доступ по индексу только в крайнем случае, так как более читабельным вариантом является доступ по имени. Добавьте следующий код для проверки:
Такой синтаксис позволяет создать новую константу, которая ссылается на каждый элемент кортежа по имени.
Названные кортежи
Безымянные кортежи работают нормально, но требуют дополнительного кода для доступа к их значениям.
Чаще всего удобнее использовать «названные кортежи», где вы даете имена вашим элементам кортежа во время объявления. Для теста давайте напишем еще несколько строк кода:
Как видите, такой вариант более удобный, так что мы будем использовать его до конца этого туториала.
Напомню вам, что мы использовали синтаксис выведения типов для объявления tipAndTotalNamed, но если вы хотите указать типы явно, то это будет выглядеть вот так:
Не забывайте, что когда вы используете явное указание типов, то давать имена элементам кортежа необязательно.
Возвращение кортежей
Сейчас вы уже понимаете основу кортежей, давайте посмотрим, как мы можем использовать их в нашем калькуляторе чаевых.
Добавьте следующий код к себе на площадку (playground):
Это тот же самый метод calcTipWithTipPct с которым вы работали, за исключением того, что сейчас он возвращает кортеж (tipAmt:Double, total:Double), вместо Double.
Можете скачать файл площадки, включащий весь код до этого момента. Теперь удалите все, что вы сделали в вашей площадке и начнем с чистого листа.
Теперь, вы готовы использовать все выученное, в своем классе TipCalculatorModel.
До того, как вы начнете изменять свой проект Xcode TipCalculator, давайте определим изменения на площадке!
Если вы хотите проверить свои силы, то самое время! Но если у вас что-то не получилось, то сравните с кодом ниже:
Заметка
Мне пришлось переключиться с короткой записи формата словаря ([x: y]()) на длинную (Dictionary()) потому, что компилятор не поддерживает кортеж в кортежах, в короткой форме.
Теперь, давайте сохраним эту площадку и откроем новую. Но мы вернемся обратно чуточку позже.
Следующим шагом, является создание прототипа таблицы для нашего приложения. Но до того, как мы приступим, нам предстоит разобраться еще с двумя понятиями: протоколы и делегаты. Начнем с протоколов:
Этот протокол объявляет единственный метод Speak.
Любой класс, который соответствует этому протоколу, должен имплементировать этот метод. Давайте попробуем это сделать, добавив два класса, которые соответствуют протоколу Speaker:
И кстати, если вы не включите функцию Speak, то вы получите ошибку компиляции.
Теперь, давайте попробуем то же самое, только добавим еще наследование от другого класса:
В этом примере, Dog наследует от Animal, так что когда вы объявляете Dog, вы должны поставить двоеточие, после чего идет имя класса от которого наследуют, а затем список протоколов. Вы можете наследовать только от одного класса в Swift, но вы можете соответствовать любому числу протоколов.
Опциональные протоколы
Вы можете обозначить метод в протоколе как опциональный. Чтобы попробовать, замените протокол Speaker следующим кодом:
Если у вас произошла ошибка, то добавьте следующую строку на самый верх вашей площадки:
Если вы хотите создать протокол с опциональными методами, то вам нужно создать его с префиксом @objc (даже, если ваш класс никак не связан с Objective-C). После этого вы можете ставить префикс optional у любого метода, который должен быть опциональным.
Обратите внимание, что сейчас нет ошибки компилятора, для вашего класса Person или класса Dog.
В этом примере, Ray и Vicki расскажут шутку. Так что имлементируем этот метод только для двух классов:
Обратите внимание, что когда вы имплементируете протокол, то при необходимости, ваш класс может иметь больше одного метода, а не только тот, что указан в протоколе. Здесь класс Ray имеет один дополнительный метод.
Кстати, а вы не догадались случайно до ответов на эти шуточные вопросы?
Q: What did Sushi A say to Sushi B? A: Wasabi!
Q: Whats the object-oriented way to become wealthy? A: Inheritence!
Использование протоколов
На данный момент, вы уже создали протокол и несколько классов, которые их используют, давайте теперь попробуем использовать их. Добавьте следующий код на площадку:
Также обратите внимание, что вы можете присвоить speaker Vicki, так как Vicki, так же подчиняется Speaker.
Теперь, добавьте эти строки для эксперимента с опциональным методом:
Делегаты
Чтобы наглядно понять, что я имею в виду, добавьте новый класс DateSimulator на вашу площадку. Он позволяет вашим двум классам, которые соответствуют Speaker, «пойти на свидание»:
Представьте, что вы хотите иметь возможность сообщать другому классу о том, что «свидание» началось или закончилось. Это было бы полезно, если был бы какой-либо индикатор, который мог появляться или исчезать, к примеру, в зависимости от статуса.
Чтобы это осуществить, создадим протокол с событиями, о которых вы хотите получать уведомления, к примеру(добавьте это до DateSimulator):
Затем создайте класс, который подчиняется протоколу (добавьте его сразу после DateSimulatorDelegate):
Для простоты, вас просто информируют об этих событиях.
Затем, добавим новое свойство для DateSimulator, которое принимает класс, который подчиняется этому протоколу:
Вы наверное обратили внимание на то, что мы сделали опционал. Таким образом, DateSimulator должен работать нормально и при созданном делегате, и при отсутствии делегата.
Прямо перед строкой sim.simulate() установите переменную вашему LoggingDateSimulator:
Наконец, модифицируйте вашу функцию simulate() так, чтобы она могла вызывать делегата и в начале, и в конце метода.
Попробуйте сделать это самостоятельно, так как это жутко полезная практика! Не забывайте, что прежде чем использовать делегат, вы должны проверить, установлен ли он. Я советую вам использовать последовательность опционала.
Для проверки вашего решения смотрим код ниже:
Файл playground с самого начала и до этого момента.
Теперь, сохраните свой файл и давайте вернемся к площадке, которую мы сохранили раньше для того, чтобы модернизировать наш класс TipCalculatorModel. Самое время вписать его в таблицу!
Таблицы, делегаты и источники данных
Сейчас, вы понимаете концепции протоколов и делегатов, и готовы к использованию таблиц (table views) вашего приложения.
Так уж получилось, что у таблиц (table views) есть свойство delegate, и вы можете установить его для класса, который подчиняется UITableViewDelegates. Это протокол с кучей опциональных методов. К примеру, есть метод, который определяет какой ряд выбран в таблице или, когда запущено ее редактирование.
Так же, таблицы имеют дополнительное свойство dataSource, которое вы так же можете установить для класса, который подчиняется UITableViewDataSource. Разница в том, что это свойство не уведомляет класс, а запрашивает данные. Например, сколько рядов в таблице.
Свойство delegate опционально, но dataSource обязательно. Что ж, начнем с того, что зададим источник данных для вашего калькулятора чаевых.
Одна из крутых штук игровых площадок в том, что вы можете прототипизировать(это слово-опционал 8]) и визуализировать элементы (к примеру UITableView). Это удобный способ посмотреть на работу элемента до того, как интегрировать его в проект.
Вновь убедитесь, что вы на площадке, с обновленным и улучшенным классом TipCalculatorModel. Добавьте этот код в самый низ файла:
Давайте все последовательно разберем.
У вас есть база, что ж давайте подчиним наш класс протоколу UITableViewDataSource. Чтобы это сделать, мы должны прописать имя-протокол в объявлении класса:
Затем, добавьте вот этот новый метод:
Это один из двух необходимых методов, которые мы должны реализовать для подчинения протоколу UITableViewDataSource. Он запрашивает у вас количество рядов в каждой секции этой таблицы. Эта таблица, будет иметь всего одну секцию, так что вы возвращаете количество значений в sortedKeys (количество возможных процентов).
Следующим, добавим другой метод, который так же обязателен:
Идем как обычно, секция за секцией:
И наконец, вы можете протестировать ваш табличный вид приложения, добавив следующий код в конец вашего файла playground (площадки):
Это создает вид таблицы заданного размера, и устанавливает источник данных для нового класса. Затем, он вызывает reloadData(), чтобы обновить вид таблицы.
Наведите вашу мышь на боковую панель площадки, на последнюю строку, и нажмите на появившийся глаз. Вы должны увидеть симпатичное превью вашей таблицы, с вашими значениями чаевых!
Мы закончили с прототипом вашего нового и улучшенного TipCalculatorModel, и новым табличным видом приложения. Теперь, самое время интегрировать ваш код в проект!
Последние штрихи
Откройте ваш проект TipCalculator и скопируйте в него TipCalculatorModel, поверх вашей существующей реализации класса.
Только не копируйте строчки нашего текста, которые в конце.
Откройте Main.storyboard, выберите ваш Table View (зона результатов) и удалите ее. Из библиотеки перетащите Table View (а не Table View Controller) в ваш View (в схеме документа). Установите X=0, Y=192, Width=600, Height=408.
Перенастройте расположение элементов, нажав на третью кнопку нижней правой части Interface Builder’а и нажмите Clear Constraints, потом Add Missing Constaints to View Controller.
Наконец, выделите ваш табличный вид (или table view) и выберите шестую вкладку инспектора (connection inspector). Вы увидите dataSource: перетащите кружочек на Tip Calculator в схеме документа.
Теперь, повторите тот же самый процесс для delegate: перетащите кружочек на Tip Calculator в схеме документа.
Теперь, ваш view controller (Tip Calculator) настроен не только как источник данных, но и как делегат, подобно тому, как вы ранее устанавливали тестовый класс в виде источника данных таблицы.
Убедитесь, что Assistant Editor открыт и показывает ViewController.swift. Перетащите с зажатым ctrl от table view в ViewController.swift под ваш последний outlet.
Выскочит окошко. В графе Name впишите tableView и нажмите Connect. Теперь вы можете обращаться к table view в вашем коде.
Октройте ViewController.swift и поставьте ваш класс как подчиняющийся UITableViewDataSource:
Затем, добавьте две переменные ниже tipCalc:
Замените calculateTapped() следующим кодом:
Это устанавливает possibleTips и sortedKeys, после чего срабатывает перезагрузка table view.
Удалите строку которая устанавливает resultsTextView в refreshUI().
Скопируйте два метода table view с вашей площадки (playground) в ваш класс, и удалите все комментарии из класса.
Конечный вариант ViewController.swift:
Вот и все! Запустите свое приложение и наслаждайтесь новым видом вашего калькулятора чаевых!
Дальше, вы можете продолжить изучать наши туториалы по мере их появления, а также, параллельно читать перевод официальной книги по языку программирования Swift. И, для более подробного изучения языка, вы можете пройти наши курсы!