Использование блоков в iOS. Часть 1
В процессе изучения Objective-C и iOS-разработки не могла понять принципы работы блоков. С толку сбивало, что их можно передавать как параметры в методы. Наткнулась на статью, которая показалась мне крайне интересной, так как рассматривались не только блоки, но и процесс разработки приложения. Пост адаптирован под xCode 7.3.1.
Предисловие
Блоки — невероятно мощное добавление к C/Objective-C, позволяющее «обернуть» куски кода в отдельные единицы и оперировать ими в качестве объектов. Все больше и больше API требуют использование блоков в iOS, поэтому очень важно их понимать. Однако, их синтаксис и некоторые тонкие аспекты зачастую сбивают с толку начинающих. Но бояться не стоит — этот урок будет весьма полезным.
В двух частях этого урока мы создадим небольшой iOS проект «iOS Diner». Приложение само по себе простое: пользователь выбирает блюдо из меню для создания заказа, как будто бы собираясь пообедать. Осторожно: в процессе создания обостряется чувство голода!
В первой части разработаем UI нашего приложения, заодно рассмотрев Storyboard (дословно — раскадровка — Прим. пер.), включая небольшую памятку по созданию и использованию веб-сервисов для загрузки меню в формате JSON.
Примечание. Если вы уже довольно хорошо разбираетесь со Storyboard и Interface Builder, можете пропустить первую часть и сразу перейти ко второй части, где мы начнем использовать блоки. Эта часть фокусируется только на Storyboard и Interface Builder.
Во второй части будет показано всестороннее использование блоков для программирования логики приложения, асинхронной обработки данных, фоновых задач, использования в качестве альтернативы многим стандартным API, и многое другое.
Начинаем
Открываем Xcode и создаем новый проект.
В названии проекта укажите «iOSDiner».
Запустите проект. Конечно, он еще пустой, и на симуляторе будет пустой белый экран.
В настройках проекта в Deployment Info убираем галочку Portrait.
Первое, что мы сделаем, — настроим представление. Для этого понадобится немного графики. Скачать можно отсюда. Это нужно будет добавить в ресурсы проекта.
Если честно, мне никогда не нравилось, как xCode обрабатывает совпадение файлов в проекте и файловой системе, поэтому обычно я добавляю ресурсы вручную в файловой системе. В Finder откройте папку проекта и в ней создайте папку «Resources». В ней создайте папку «Images».
Скопируйте графику из загруженного ZIP-файла в папку Images, затем перетащите папку Resources в папку IOSDiner в Xcode, как показано на скриншоте ниже.
Теперь можно увидеть папку Resources в Xcode, в которой есть подпапка Images с загруженными изображениями, — как и в файловой системе.
Добавление изображений
Откройте Main.storyboard.
Если вы не видите вторую колонку с названием «View Controller Scene», внизу кликните на кнопку Expand.
Итак, мы собираемся добавить изображения в Storyboard в виде UIImageViews и UIButtons. Чтобы было проще, откройте боковую панель Utilities и выберите Media Library.
Здесь мы видим все раннее добавленные в проект изображения. Наверняка вы заметили, что у каждой картинки есть своя копия с «@2x» в конце названия. Она используется для retina-версии.
Мы заинтересованы только в обычной картинке. Вы можете проверить, какая это версия картинки, кликнув и нажав клавишу пробел. Перетащите “bg_wall.png” на root-view, как показано ниже. Если вы не уверены, что изображение поставлено корректно, можете переключиться в Size Inspector для изменения его X и Y координат.
Теперь проделайте то же самое со следующими изображениями:
- person.png
- sign_theiOSdiner.png
- chalkboard.png
- bg_counter.png
- total_field.png
- food_box.png
Вау! Почти похоже на приложение! Далее, добавим части пользовательского интерфейса. На панели Utilities переключитесь на Object Library.
Перетащите Button в центр нашего представления, над монитором. Дважды кликните на только что добавленную кнопку и напишите «-1».
Round Rect Button
В оригинале использовались Round Rect Button, которые отсутствуют в xCode 7.3.1. На Stack Overflow есть решение, как их использовать.
Убедитесь, что кнопка выделена. В Attributes Inspector установите атрибут Background «button_silver.png».Нажмите и удерживайте клавишу Alt и перетащите кнопку -1 вправо. Так создастся копия объекта. Измените ее текст на «+1».
Перетащите еще одну кнопку на левый край монитора. Установите Button Type в Custom, удалите заголовок кнопки и из Media Library перетащите на кнопку «button_arrow_left.png»
Скопируйте эту кнопку и поменяйте фоновое изображение на «button_arrow.png»
И осталась последняя кнопка. Разместите ее под доской и установите фоновое изображение «total_field.png». Запустите проект.
Выглядит вполне симпатично. Следующее, что мы добавим, — лейблы и окошко предварительного просмотра.
Снова идем в Object Library. Перетаскиваем UILabel на доску и растягиваем его по размеру доски.
В Atrributes Inspector установите Lines в 0 (это сделает лейбл многострочным), поменяйте Text Color на белый, и Font — на Marker Felt 17.0. Обычно использование Marker Felt я считаю преступлением, но в нашем случае он вполне подходит.
Перетащите UIImageView на монитор и растяните по его размеру.
В Attributes Inspector поменяйте Mode на Aspect Fit.
Перетащите еще один UILabel на табличку в правом нижнем углу. Сделайте его по размеру серой зоны таблички и установите Alignment в Center.
Устанавливаем IBOutlets и IBActions
Далее необходимо установить связи между пользовательским интерфейсом, который мы только что создали, и кодом. Для этого и нужны IBOutlets и IBActions. IB означает Interface Builder, который используется для создания UI в xCode.
- IBOutlet, в основном, — это связь элемента UI (кнопки или лейбла) с его ссылкой в коде.
- IBAction — это действие (или метод, как удобнее) в коде, которое можно подключить к некоторому событию (нажатие кнопки, например) в разработанном интерфейсе.
Закройте вкладку Utilities и откройте Assistant editor. В зависимости от того, как он настроен, экран может выглядеть не так, как на скриншоте. Если вы хотите изменить отображение Assistant editor, загляните в его меню.
Начнем с кнопок. Выберите кнопку «-1», удерживайте клавишу Ctrl и перетащите ее в код. Это автоматизирует создание IBOutlet для кнопки.
Для этого объекта все, что нужно, — назвать его. Мне нравятся префиксы «ib», так как в xCode становится легко найти все элементы по автозаполнению. Назовите этот объект «ibRemoveItemButton» и кликните Connect.
Аналогично делаем с остальными кнопками.
Точно так же устанавливаем лейблы.
Теперь ViewController.m должен выглядеть так:
#import "ViewController.h" @interface ViewController () @property (weak, nonatomic) IBOutlet UIButton *ibRemoveItemButton; @property (weak, nonatomic) IBOutlet UIButton *ibAddItemButton; @property (weak, nonatomic) IBOutlet UIButton *ibPreviousItemButton; @property (weak, nonatomic) IBOutlet UIButton *ibNextItemButton; @property (weak, nonatomic) IBOutlet UIButton *ibTotalOrderButton; @property (weak, nonatomic) IBOutlet UILabel *ibChalkboardLabel; @property (weak, nonatomic) IBOutlet UIImageView *ibCurrentItemImageView; @property (weak, nonatomic) IBOutlet UILabel *ibCurrentItemLabel; @end @implementation ViewController - (void)viewDidLoad < [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. >- (void)didReceiveMemoryWarning < [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. >@end
Теперь мы должны добавить IBActions для UIButtons. Это методы, которые вызываются в ответ на определенные события (Touch Up Inside, Touch Up Outside, Touch Cancel, и т.д.). Для кнопок чаще всего используется Touch Up Inside.
Выберем кнопку «-1». Снова с помощью клавиши Ctrl перетаскиваем ее в ViewController.m. Так как это IBAction, используем префикс «iba». Повторяем для всех кнопок.
Устанавливаем веб-сервис
Прежде чем приступить к коду, настроим веб-сервис. Я не собираюсь объяснять все в мельчайших подробностях, так как для этого уже существует несколько уроков (How To Write A Simple PHP/MySQL Web Service for an iOS App и How to Write an iOS App That Uses a Web Service).
Код ниже показывает, как будет выглядеть PHP код для веб-сервиса.
'Continue', 101 => 'Switching Protocols', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => '(Unused)', 307 => 'Temporary Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Requested Range Not Satisfiable', 417 => 'Expectation Failed', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported' ); return (isset($codes[$status])) ? $codes[$status] : ''; > // Helper method to send a HTTP response code/message function sendResponse($status = 200, $body = '', $content_type = 'text/html') < $status_header = 'HTTP/1.1 ' . $status . ' ' . getStatusCodeMessage($status); header($status_header); header('Content-type: ' . $content_type); echo $body; >class InventoryAPI < function getInventory() < $inventory = array( array("Name"=>"Hamburger","Price"=>0.99,"Image"=>"food_hamburger.png"), array("Name"=>"Cheeseburger","Price"=>1.20,"Image"=>"food_cheeseburger.png"), array("Name"=>"Fries","Price"=>0.69,"Image"=>"food_fries.png"), array("Name"=>"Onion Rings","Price"=>0.69,"Image"=>"food_onion-rings.png"), array("Name"=>"Soda","Price"=>0.75,"Image"=>"food_soda.png"), array("Name"=>"Shake","Price"=>1.20,"Image"=>"food_milkshake.png") ); sendResponse(200, json_encode($inventory)); > > sleep(5); $api = new InventoryAPI; $api->getInventory(); ?>
Этот веб-сервис представляет собой вполне простой PHP-скрипт, который возвращает JSON-массив. В массиве находится так называемый ассоциативный массив (PHP), или словарь(Objective-C), в котором содержится название, цена и имя изображения для предмета. Единственное, что следует отметить в вышеуказанном коде, — это строчка sleep(5); .
Я использую ее для симуляции медленного веб-сервиса, чтобы лучше показать, как блоки могут помочь с асинхронными операциями.
Вы можете скопировать этот код в файл с расширением .php и разместить его на хостинге, или просто используйте мой.
Что делать дальше
Проект этой части урока можно скачать здесь. Что ж, здесь было много всяких вещей, никак не связанных с блоками. Но сейчас, установив view и веб-сервис, мы можем перейти к кодингу, где и будем использовать блоки, чтобы сделать наше приложение полностью функционирующим.
- ios development
- objective-c
- перевод
@IBOutlet и @IBAction в Swift
Очень хотел бы понять что происходит под капотом, когда мы создаем для, например, UITableView @IBOutlet (и заодно @IBAction ) в нашем классе, по умолчанию, в single view app , это ViewController . Использую XCode 9, Swift 4.
Отслеживать
15.8k 1 1 золотой знак 18 18 серебряных знаков 35 35 бронзовых знаков
задан 16 июл 2018 в 13:35
123 3 3 серебряных знака 14 14 бронзовых знаков
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
Пример для объяснения аналогичный Вашему, но вместо UITableView разместил UIButton и UILabel . Создал для них IBOutlet и для кнопки IBAction :
class ViewController: UIViewController < @IBOutlet weak var someLabel: UILabel! @IBOutlet weak var someButton: UIButton! @IBAction func someButtonAction(_ sender: UIButton) < print(#function) >>
Чтобы посмотреть содержимое Storyboard , необходимо в меню выбрать Open As -> Source Code :
Это обычный xml . Далее содержимое с пояснениями:
По сути, storyboard является эдаким DI контейнером.
Начало разработки iOS: использование Interface Builder
Добро пожаловать в третий выпуск видео-серии «Начало разработки iOS». В этом уроке мы рассмотрим Интерфейсный Разработчик и как мы можем использовать его в проектах iOS.
Примечания ниже дополняют видео. Если у вас есть какие-либо вопросы относительно чего-либо, упомянутого в видео, пожалуйста, не стесняйтесь спрашивать в разделе комментариев.
Начиная
Сначала рассмотрим интерфейс Interface Builder. После открытия XIB вы увидите 4 разных окна. Окно «Библиотека», «Вид / Окно», «Окно документа» и окно «Атрибуты», «Размер», «Соединения и личность». Если вы не видите ни одного, ни одного из этих окон, убедитесь, что Интерфейсный Разработчик работает, заново открыв окна из меню Инструменты.
Библиотека
В этой библиотеке все объекты UIKit доступны для использования в вашем проекте. Простое перетаскивание объекта в окно добавит его в представление. Библиотека разделена на 3 раздела.
- Контроллеры. Это объекты, которые управляют набором связанных представлений в определенном контексте контента. Примером может служить Контроллер сообщений, показывающий сообщения, доступные пользователю.
- Представления данных: когда данные должны отображаться пользователю, эти представления будут соответствовать большинству типов мультимедиа. Таблицы, изображения, веб-страницы, карты и прокрутка содержимого обрабатываются объектами в этой категории.
- Входные данные и значения: когда пользователю требуется ввести информацию или предоставить выбор, эти объекты предоставляют множество различных опций.
Использование контроллеров, а не объектов пользовательского интерфейса
Если вы хотите использовать UITabBar в своем приложении, вы не перетаскиваете UITabBar в представление. Как вы используете UITabBar, UINavigationBar и панель поиска, с помощью контроллера. Контроллер выходит из этих объектов и автоматически обрабатывает основные функциональные возможности этих объектов. Использование контроллера UITabBar будет автоматически обрабатывать переход и управление различными UIViewControllers.
Расширения UIView
Все основные объекты UIKit происходят от класса UIView. В Какао все развивается в наследственной древовидной форме. Никакая функциональность не дублируется, если она может быть унаследована. Под этим я подразумеваю, что объект UIButton, например, является потомком класса UIView. Из-за этого UIButton наследует альфа-канал, режим содержимого, размер и другие характеристики, которые имеет класс UIView, но также распространяется на функции с дополнительной функциональностью, например, в качестве кнопки.
На приведенном выше снимке экрана Interface Builder показано, что класс UIButton наследуется от класса UIControl, который, в свою очередь, наследуется от класса UIView и так далее. Все это основано на классе NSObject, который вы найдете в качестве основы любого класса Какао. Каждый из этих классов, унаследованных друг от друга, приносит функциональность друг другу, а затем расширяется.
Атрибуты, связи, размер и идентичность
Это окно позволяет изменить свойства и настройки для выбранного объекта UIView. В зависимости от объекта пользовательского интерфейса вы можете изменить настройки шрифта, стиль объекта, цвета фона или прозрачность.
В разделе соединений будут показаны все доступные действия, на которые может реагировать объект, а также любые связанные розетки или действия.
Размер раздела делает, как вы ожидаете. Вы можете изменить координаты X & Y объекта, а также его физические размеры. Сюда также входят параметры автоматического изменения размера, которые управляют изменением размера элемента в случае поворота экрана или изменения размера.
Раздел идентичности обычно не используется, но в нем есть несколько интересных полей. Если вы расширили объект UIKit и хотите использовать собственный класс вместо стандартного, то здесь вы можете переназначить базовый класс объектов. Есть несколько опций, связанных с доступностью и внутренними полями Interface Builder, такими как заметки и разрешение именования объектов.
Autosizing
Когда экран поворачивается на устройстве iOS, вы можете управлять тем, как объекты пользовательского интерфейса будут изменять свое положение или растягиваться, чтобы заполнить новое окно. Всем этим можно управлять с помощью этого простого небольшого инструмента в Интерфейсном Разработчике.
Поле слева – это место, где вы решаете, как будет масштабироваться объект, а поле справа – это анимированная визуализация того, как будет работать объект при изменении фрейма макета.
Внешний блок слева – это то место, где вы устанавливаете «якоря» или то, с каких сторон объект будет блокироваться. Любая выбранная сторона заставит объект придерживаться этой стороны при изменении размеров экрана. Внутренний квадрат имеет элементы управления для растяжения. Когда объект заблокирован с двух сторон и экран меняет свою ширину, объект может быть растянут вместе с экраном. Стоит поиграть с этими настройками и посмотреть результаты разных конфигураций.
IBActions и IBOutlets
Когда вам нужно соединить ваш код с объектом в Интерфейсном Разработчике, вы используете IBAction и IBOutlet. Эти ключевые слова являются флагом для Интерфейсного Разработчика, чтобы знать, что есть свойство, доступное для соединения с контроллером. Затем вы соединяете соответствующий объект с действием или розеткой. IBAction – это то, как объект может связаться с вашим кодом, когда происходит событие. Когда нажата кнопка, вы используете IBAction для вызова метода обратно в вашем контроллере. IBOutlet – это обратный способ взаимодействия вашего контроллера с объектом в Интерфейсном Разработчике и вашим представлением в любое время.
Создать IBOutlet так же просто, как добавить флаг, когда вы определяете свойство объекта в вашем контроллере.
@interface HelloInterfaceBuilderViewController: UIViewController IBOutlet UILabel * myText; > @property (nonatomic, retain) IBOutlet UILabel * myText;
IBAction похож. При определении метода в заголовочном файле вы добавляете флаг IBAction в качестве возвращаемого типа.
@interface HelloInterfaceBuilderViewController: UIViewController // . IBOutlets и свойства . > - (IBAction) кнопкаPressed: (id) отправитель;
Соединить их – простая задача в Интерфейсном Разработчике.
При создании IBAction: нажмите на объект в окне, удерживайте элемент управления, перетащите к «Владельцу файлов» в окне документа, отпустите кнопку мыши и затем управляйте. Выберите действие для ссылки в раскрывающемся списке.
При создании IBOutlet: щелкните «Владелец файлов» в окне документа, удерживайте элемент управления, перетащите элемент «Владелец файлов» к объекту в окне, отпустите кнопку мыши, а затем элемент управления. Выберите розетку из раскрывающегося списка.
Плюсы и минусы Interface Builder и XIB
В этом вопросе нет сплошных уй или нет. По общему мнению, использование Interface Builder и XIB не более интенсивно, чем создание объектов в вашем контроллере. Есть некоторые побочные эффекты от того или иного пути.
При использовании XIB легко баловать представления объектами и не учитывать последствия для памяти. В большинстве случаев беспокоиться не о чем, но в чувствительных частях приложения UITableView является одной из этих чувствительных областей, перегрузка интерфейса может вызвать замедление работы приложения. Помните, что каждый объект в представлении имеет свое собственное распределение памяти и создается, когда представление загружается в приложение, является ключевым.
Программирование без XIB позволяет размещать объекты в тот самый момент, когда объект требуется, и освобождать, как только он не нужен. Это сохраняет приложение легким и отзывчивым, пока ничего не просачивается (забудьте освободить объект). Но поскольку все это делается в коде, визуальное представление того, что было создано, отсутствует, пока приложение не находится в симуляторе. Интерфейсный Разработчик позволяет интерактивно создавать представление, редактор WYSIWYG. Таким образом, в результате некоторые представления без XIB могут выглядеть как роботизированные или мягкие, потому что разработчик не украсил дизайн.
Нет определенного решения. Если какой-то способ чувствует себя лучше, тогда это совершенно нормально. В большинстве случаев между ними нет заметных различий в производительности.
Пояснение: в скринкасте упоминается, что XIB преобразуются из XML в интерфейс на устройстве во время выполнения. Это не вариант. XIB компилируются в NIB при создании приложения и используются в приложении в качестве NIB. Тип файла XIB был создан, чтобы позволить службам управления исходным кодом, таким как SVN и Git, работать лучше с файлом на основе XML (чтобы можно было выполнять diff), а не скомпилированным двоичным файлом.
Iboutlet swift что это
Доброго времени суток, друзья!
Сегодня мы рассмотрим передачу данных между ViewControllers. На самом деле передача данных между ViewControllers может показаться тривиальной задачей, но если учесть, что любое реальное приложение для iOS будет иметь много ViewControllers, то коммуникация становится их важной частью. Неправильное понимание этого может привести к трудному исправлению ошибок.
В целом существует много способов сделать это, но лишь некоторые из них являются лучшей практикой.
В этой статье я покажу вам такие практики передачи данных:
- между ViewController с использованием segues
- между ViewController без segues
- передача через unwind segue
- передача при помощи делегата
- обратная передача при помощи замыканий (closures)
Передача данных вперед
Передача данных происходит каждый раз, когда на экране появляется новый ViewController.
Это может произойти через segue или программно.
Передача данных вперед между ViewController с использованием segues
Создаем 2 контроллера в сториборде: FirstViewController и SecondViewController .
В FirstViewController добавляем кнопку, и от кнопки перетаскиваем segue на SecondViewController . И даем имя segue (это очень важно!).
В SecondViewController создаем UILabel , привязываем наш @IBOutlet и создаем переменную с именем name . И подготовка окончена.
Главный метод, с которым Вы будете работать — это prepare(for segue:) .
Всегда нужно проверять соответствует ли segue.identifier названию вашего segue, (guard segue.identifier == “showSegue”) который мы задавали ранее. Если все в порядке, то мы идем дальше и устанавливаем destination , наш “пункт назначения”.
Берем наш segue вызываем destination и обязательно кастим до нужного ViewController , иначе ничего не получиться (guard let destination = segue.destination as? SecondViewController) И если Вы все правильно сделали, то написав destination , Вы получите все свойства и методы SecondViewController . Давайте передадим в переменную name имя Андрей ( destination.name = “Aндрей” ).
Готово! Теперь при нажатии на кнопку, мы будет передавать имя в следующий контроллер!
class FirstViewController: UIViewController < @IBOutlet var username: UILabel! override func viewDidLoad() < super.viewDidLoad() >override func prepare(for segue: UIStoryboardSegue, sender: Any?) < guard segue.identifier == "showSecond" else < return >guard let destination = segue.destination as? SecondViewController else < return >destination.name = "Андрей" > >
class SecondViewController: UIViewController < var name = "" @IBOutlet var username: UILabel! override func viewDidLoad() < super.viewDidLoad() username.text = name >>
Заметка
Всегда правильно задавайте идентификатор для segue. Хорошей практикой будет называть идентификатор тем, что он делает. Потому что может быть ситуации когда Вам нужно передать 2 и более segue в один контроллер. В дальнейшем это поможет свободно ориентироваться в проекте.
Передача данных вперед между ViewController без segues
Иногда вы можете подключить ViewController программно, а не использовать segue.
Первый пример:
Создаем свойство storyboard , где name — это имя storyboard , в котором находится необходимый ViewController (let storyboard = UIStoryboard(name: “Main”, bundle: nil) .
Далее создаем необходимый ViewController . Для этого используем метод instantiateViewController(identifier: String) и обязательно кастим до требуемого SecondViewController (guard let secondViewController = storyboard.instatiateViewController(identifier: “SecondViewController”) as? SecondViewController else < return >)
Теперь мы также можем достучаться до переменной name и задать ей нужное значение. И так же не забываем вызвать метод show(vc: UIViewController, sender: Any?) , который как раз и откроет нам данный контролер.
И готово! Так как в SecondViewController не поменялся код, показываем код FirstViewController :
class FirstViewController: UIViewController < @IBAction func passData() < let storyboard = UIStoryboard(name: "Main", bundle: nil) guard let secondViewController = storyboard.instantiateViewController(identifier: "SecondViewController") as? SecondViewController else < return >secondViewController.name = "Ivan" show(secondViewController, sender: nil) > >
Как видите поскольку нет никакого segue , метод prepare (for: sender:) также не вызывается.
Если вы работаете с xib файлами, то можно тоже довольно легко и просто передавать данные.
Создаем ThirdViewController и оставляю галочку, чтобы с контроллером создался xib файл. Перетягиваем UILabel . Подготовка окончена.
В этом примере я покажу, не простую передачу данных, как мы делали, а через инициализатор.
Создаем свойство text: String , и создаем инициализатор к нему init(text: String) . Так как это xib файл, то давайте добавим еще 2 свойства nibName: String?, bundle: Bundle? , это позволит нам инициализировать ThirdViewController через тот самый xib файл.
Дальше все по стандарту присуждаем self.text = text . И вызываем super.init(nibName: String, bundle: Bundle?) , так как без инициализации “дизайна” контроллера ничего не получится. И исправляем ошибку с required init?(coder: NSCoder) . Наш ThirdViewController — готов для получения данных!
class ThirdViewController: UIViewController < let text: String init(text: String, nibName: String?, bundle: Bundle?) < self.text = text super.init(nibName: nibName, bundle: bundle) >required init?(coder: NSCoder) < fatalError("init(coder:) has not been implemented") >@IBOutlet var textLabel: UILabel! override viewDidLoad() < super.viewDidLoad() textLabel.text = text >>
Возвращаемся к нашему SecondViewController . Перетаскиваем кнопку и создаем @IBAction . В нем создаем экземпляр класса и инициализируем по нашему кастомному инициализатору, который мы создали ранее (let thirdVC = ThirdViewController(text: “Alexey”, nibName: “ThirdViewController”, bundle: nil) . Прошу заметить, что nibName — это название xib файла, поэтому если вы создаете xib файл отдельно от контроллера, имейте это ввиду.
И после этого вызываем метод show(vc: UIViewController, sender: Any?) .
Наш SecondViewController готов, для передачи данных.
class SecondViewController: UIViewController < var name = "" @IBOutlet var username: UILabel! override func viewDidLoad() < super.viewDidLoad() username.text = name >@IBAction func goToThirdVC() < let thirdVC = ThirdViewController(text: "Alexey", nibName: "ThirdViewController", bundle: nil) show(thirdVC, sender: nil) >>
Передача данных в обратном направлении
Передача данных назад в приложении iOS так же важна, как и их перемещение вперед. Пользователи часто возвращаются к предыдущему экрану, который они посетили.
Когда пользователь взаимодействует с вашим приложением, вы должны обновлять эти предыдущие экраны. Это не происходит автоматически, поэтому вы можете использовать разные методы.
Передача данных в обратном направлении через unwind segue
Для этого примера создадим новые FirstViewController и SecondViewController .
Начинаем настраивать SecondViewController . Создаем UILabel и UIButton в сториборде и привязываем к контроллеру. В контроллере создаем переменную text . И добавляем метод prepare (for: sender:) , где меняем значение переменной text на “Data was passed” .
Теперь настраиваем FirstViewController . Так же создаем UILabel и UIButton в сториборде и привязываем к контроллеру. Перетягиваем от кнопки segue на SecondViewController . И создаем @IBAction для нашей кнопки, но с параметром (_ unwindSegue: UIStoryboardSegue) , который и является главное фишкой данного примера.
Далее все по стандарту: делаем проверку на segue.identifier , и кастим наш SecondViewController . Только обратите внимание, что при получении данных вместо свойства destination , мы работаем с source . Таким образом мы сообщаем, что SecondViewController будет источником данных. Ну и присуждаем нашему UILabel переменную text из SecondViewController(textLabel.text = source.text) .
И главное — это в сториборде в SecondViewController от нашего UIButton перетягиваем segue , не на FirstViewController , а на кнопку Exit , который находится рядом с кнопкой First Responder выше контролера. И не забываем про segue.identifier .
Готово! Вот примерный код, который должен у Вас получиться.
class FirstViewController: UIViewController < @IBOutlet var textLabel: UILabel! @IBAction func saveData(_ unwindSegue: UIStoryboardSegue) < guard unwindSegue.identifier == "passDataToFirstVC" else < return >guard let source = unwindSegue.source as? SecondViewController else < return >textLabel.text = source.text > >
class SecondViewController: UIViewController < var text = "" @IBOutlet var textLabel: UILabel! override func prepare(for segue: UIStoryboardSegue, sender: Any?) < text = "Data was passed" >>
Передача данных в обратном направлении при помощи делегата
Иногда техники, которые Вы видели, все еще недостаточны.
Данные, которые вы хотите передать, могут быть временными и не принадлежать общему состоянию приложения.
Когда пользователь возвращается в UINavigationController , segue не запускается. Возможно, вы захотите, чтобы передача происходила в любой момент, а не только при переходе. Правильным решением будет делегирование.
Делегирование позволяет нам создать ссылку на предыдущий контроллер представления, не зная его типа. Мы делаем это с помощью протокола, который определяет интерфейс, с которым нам нужно взаимодействовать.
Создаем протокол FirstViewControllerDelegate и подписываем его под class (что означает AnyObjects ). Это позволит в будущем создать слабую ( weak ) ссылку на протокол.
Любое свойство delegate должно быть слабым ( weak ), чтобы избежать сильных ( strong ) ссылочных циклов. Если вы не знаете, что это такое, то я советую прочитать статью для более подробной информации о слабых и сильных ссылках.
В протоколе создаем метод update с принимающим параметром text: String (update(text: String) , который будет обновлять наш UILabel . Далее подписываем FirstViewController под протокол и выполняем этот метод (textLabel.text = text)
Далее переходим в SecondViewController , создаем ту самую слабую ссылку на протокол weak var delegate: FirstViewControllerDelegate и создаем @ IBAction , где указываем, что должно произойти при нажатии этой кнопки. В нашем случаем будет происходить обновление текста на предыдущем контроллере без перехода назад (delegate?.update(text: “Text was changed”) .
Если сейчас запустим приложение, нажмем кнопку и вернемся на предыдущий экран, то ничего не случится, так как мы не подписались на делегат, который находится в SecondViewController . Это частая ошибка всех программистов, поэтому если у Вас что-то не получается, проверяйте подписан ли принимающий контролер на делегат контроллера-отправителя.
Так как мы осуществляем переход на следующий контроллер через segue (при нажатии на UIButton ), то подпишемся именно здесь. В методе prepare (for: sender 🙂 получаем destination к SecondViewController и именно там подписываемся на делегат ( destination.delegate = self ). Таким образом FirstViewController получает доступ к выполнению реализации протокола в SecondViewController .
Готово! Нам удалось получить данные обратно, независимо будем ли мы возвращаться на предыдущий экран или нет.
Весь код из примера, который должен получиться:
protocol FirstViewControllerDelegate: class < func update(text: String) >class FirstViewController: UIViewController, FirstViewControllerDelegate < @IBOutlet weak var textLabel: UILabel! override func prepare(for segue: UIStoryboardSegue, sender: Any?) < guard let destination = segue.destination as? SecondViewController else < return >destination.delegate = self > func update(text: String) < textLabel.text = text >>
class SecondViewController: UIViewController < weak var delegate: FirstViewControllerDelegate? @IBOutlet weak var textLabel: UILabel! @IBAction func changeDataInFirstVC() < delegate?.update(text: "Text was changed") >>
Заметка
Техника передачи данных через делегирование является мощным инструментом в работе с передачей данных между контроллерами и также работает без segue.
Главное — это подписывать принимающий контроллер на delegate контроллера-отправителя.
Для закрепления можете сами попробовать сделать это, взяв примеры из 1.2 Передача данных вперед между ViewController без segues.
Продвинутые техники
Замена делегирования на замыкания (closures) Swift
Некоторые разработчики используют замыкания ( closures ) Swift для передачи данных назад между ViewControllers. Этот метод похож на делегирование, но более гибкий. Это также причина, почему я обычно рекомендую не использовать его.
Используя замыкания, вы можете определить интерфейс через свойства хранения, содержащие замыкания. Поэтому создадим в SecondViewController переменную с названием closure: ((String) -> ())? .
В SecondViewController , для быстрого примера, вызовем в методе viewDidLoad то самое замыкание, и поместим в него текст ( closure?(“I can pass data by closure!”) ) Обратите внимание, что как и с делегатом, замыкание должно быть опциональное.
И теперь в FirstViewController также устанавливает связь с этим замыканием, когда происходит переход. Но в этом случае вместо передачи ссылки на себя, он передает замыкание.
class FirstViewController: UIViewController < @IBOutlet var textLabel: UILabel! override func prepare(for segue: UIStoryboardSegue, sender: Any?) < guard let destination = segue.destination as? SecondViewController else < return >destination.closure = < [weak self] text in self?.textLabel.text = text >> >
class SecondViewController: UIViewController < var closure: ((String) ->())? @IBOutlet var textLabel: UILabel! @IBAction func changeDataInFirstVC() < closure?("I can pass data by closure") >>
Также обратите внимание, что замыкание содержит ссылку на себя. Таким образом, как и делегирование, использование замыканий по-прежнему создает связь между двумя ViewController.
Этот подход немного более лаконичен, чем делегирование. Но использование замыканий также имеет ряд особенностей / недостатков, которых нет у делегирования.
Если вам нужно более одного замыкания для связи с предыдущим контроллером представления, вам нужно сохранить свойство для каждого из них. При делегировании весь интерфейс выделяется внутри протокола, и вам нужно только одно свойство делегата.
На мой взгляд, замыкания лучше работают как обратные вызовы для асинхронных задач, таких как сетевые запросы или анимации. Делегирование является лучшим решением для связи ViewController.
Неправильные техники
Мы познакомились с одними из лучших практик для передачи данных между ViewController. Но к сожалению, в просторах интернета наблюдается и много неправильных.
В этом разделе мы рассмотрим, какие из них, и почему вы не должны их использовать.
Не используйте UserDefaults iOS
В iOS UserDefaults хранят пользовательские настройки, которые должны сохраняться между запусками приложения.
Все, что хранится в UserDefaults , остается там до тех пор, пока вы не удалите приложение из телефона, поэтому это не механизм для передачи данных между объектами.
Кроме того, вы можете хранить только простые типы данных в UserDefaults в форме списков свойств. Это означает, что вам нужно преобразовать любой пользовательский тип, прежде чем вы сможете поместить его туда.
В общем, ваше приложение должно получать доступ к UserDefaults через одну точку, которая обычно является настраиваемым контроллером совместно используемой модели.
Не используйте Notifications
Notifications в iOS дают вам канал, по которому какой-то код может отправлять сообщение другим объектам, на которые он не имеет прямой ссылки.
Я видел, как многие разработчики используют Notifications для передачи данных между контроллерами представления. Это не то, для чего они нужны!
Notifications создают косвенность и усложняют выполнение вашего кода. Когда вы публикуете notifications , вы не можете быть уверены, какие объекты получат его и в каком порядке. Это часто приводит к неожиданному поведению.
Notifications могут быть полезны иногда. Но вы должны использовать их экономно, и только когда это необходимо.
Выводы
Как я и говорил, существует много способов передачи данных между ViewController. Но только некоторые из них можно назвать хорошей практикой. Поначалу другие могут показаться удобными, но потом они могут создать Вам проблемы в будущем.
Поэтому выбирайте правильный подход и двигайтесь дальше!
Автор статьи: Михаил Цейтлин