UIKit 1 / 1.4. Начинаем строить наш интерфейс


Видео


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

− Нормально, состав воздуха примерно как на Земле. Просто привыкнуть надо. В этом так называемом воздухе помимо кислородов и прочих азотов, примешан еще какой−то газ не из системы дядюшки Менделеева. Всего пару тысячных процента, но дышать немного непривычно. – сказал угрюмый Коля, сидящий поодаль.
− Зачем ты это сделал? Зачем ты закрыл дверь? Слава…
− Он был в ярости, а у меня, знаешь ли нет пояса по карате, даже белого.
− Это ты сделал дыру в корабле? − Саша аккуратно положил голову Оли на землю, вскочил и побежал к Коле, замахнулся и ударил его в лицо со всей силы, они оба упали на землю. − Я тебя сейчас убью, тварь, это из−за тебя мы непонятно где, из−за тебя Слава погиб, это ты все время хотел назад, а назад не вернешься, ты блин член команды или слабак?
− Хочешь убить меня? Ну давай, все равно мы здесь умрем. Мы непонятно где, здесь ничего нет, и улететь отсюда мы не сможем, давай, убей меня. Слабо?
− Прекратите, − Аня подбежала к Саше и начала его оттаскивать, только что очнувшаяся Оля вскочила и хотела помочь подруге, как вдруг упала и закричала так пронзительно, что все сразу остановились, Саша вжал в землю Колю и побежал к Оле.


Чтобы создать что-то исключительное, нужно проявить внимание ко всем деталям.

Армани

В этом уроке


Interface Builder

Что кроется за этими двумя странными буквами? Interface Builder. (На русском обычно используется вариант Интерфейс Билдер без перевода) Пожалуй, лучшая программа для создания интерфейсов приложений. Если Вы работали с Visual Studio или Android Studio, то такой подход покажется в миллион раз проще - дизайн отделён от кода. Это даст возможность выстроить очень классную схему работы:

  1. Системный архитектор,менеджер или владелец проекта могут создать своё видение проекта и его архитектуру
  2. Дизайнер нарисует интерфейс в той среде, которая ему удобнее
  3. Верстальщик превратит результат в раскадровку
  4. Программисты параллельно с 2 и 3 будут создавать бизнес-логику
  5. Параллельная разработка очень сильно упрощает процесс

XIB, NIB & StoryBoard

Чтобы открыть IB, просто выберите файл типа .xib или .storyboard:

Перейдите к файлу main.storyboard:

Здесь представлена иерархия сцен и объектов в сценах:

  1. Стрелка указывает, что этот контроллер - точка входа в раскадровку
  2. Наш ViewController
  3. Здесь изменяется масштаб. Вы можете сделать это обычными жестами мыши или трекпада, что даже более удобно
  4. Вы можете перетаскивать сцены как угодно: их взаимное расположение в раскадровке не влияет на реальное отображение

Вы можете скрыть левую панель (1) (она ещё называется Document Outline), нажав по кнопке 5. Нажав ещё раз, Вы сможете вернуть её.

Обратите внимание на кнопки в области 6 (слева направо):

  1. Закрывает левое навигационное окно, в котором мы видели информацию о файлах и ходе выполнения программы
  2. Открывает или закрывает консоль
  3. Открывает окно утилит. Оно разделено на две части, нас интересует часть снизу (7)

Эта часть называется библиотекой объектов и состоит из 4 вкладок (слева направо):

  1. Шаблоны файлов
  2. Шаблоны кода (функций, классов)
  3. Объекты для IB
  4. Медиа объекты

Включите третью вкладку.


Наша первая программа кнопка

Найдите в колекции объектов IB кнопку - Button. Вы можете пролистать его в виде списка или коллекции или же просто ввести слово в строку поиска.

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

Теперь обратим внимание на верхнюю часть правой панели (слева направо):

  1. Инспектор файла - пока что слабо интересен.
  2. Окно быстрой помощи.
  3. Инспектор идентичности - он отображает данные, связанные с идентичностью объекта, то есть его классом, свойствами keyPath и тому подобным. На данный момент в нём указан класс объекта UIButton - это стандартный класс кнопки в UIKit.
  4. Инспектор свойств - содержит свойства объекта, которые можно менять. Изменение большей части свойств станет доступно сразу же.
  5. Инспектор размеров - позволяет менять расположение объекта, его размеры, поведение касательно размеров.
  6. В инспекторе связей Вы можете настроить связи объекта с различными действиями, событиями, источниками данных.

3-6-ые инспекторы выступают контекстными - их содержимое и сам факт наличия меняются в зависимости от объекта, на котором они открыты.


О координатной системе в UIKit

Вы думали, что здесь будет простая геометрия? Сильно-сильно ошибались. По историческим и техническим причинам начало координат (0; 0) находится в левом верхнем углу экрана, ось x направлена как обычно вправо, а вот ось y направлена вниз из центра координат.


Аутлеты и действия

Вам нужен способ связать интерфейс с кодом. Для связи объектов с кодом используются аутлеты (или выходы, или outlets). Для выполнения каких-то действий и интерактивности используются действия (actions)

Давайте откроем на экране сразу два окна - StoryBoard и корреспондирующий со сценой контроллер представления. Для этого нажмите по редактору-помощнику наверху экрана (2). Чтобы затем вернутся в единичный вид нажмите по (1) (обычный редактор).

Теперь создадим аутлет - для этого зажмите клавишу ctrl, захватите кнопку и перетащите стрелку от нее в тот участок кода, где хотите её вставить.

Введите в поле Name имя объекта: давайте назовём его myFirstButton. Теперь нажмите на кнопке connect. Получится:

@IBOutlet  weak var myFirstButton: UIButton!

Что за страхолюдина у нас появилась? И так много слов. Сперва обратим внимание на круг напротив декларации кнопки - он отражает связь кнопки с объектов раскадровки.

@IBOutlet - помните, как мы рассматривали атрибуты наподобие @escaping? Это тоже одно из них. @IBOutlet ставится перед объектами в коде, которые Вы хотите открыть для интерфейс билдера.

weak - указание на то, что аутлет связан с контролером слабым образом. Припоминаете, как мы разбивали циклы сильных ссылок? Здесь это ключевое слово используется по той же причине - с одной стороны на кнопку указывает раскадровка, с другой стороны контроллер вида. Слабость данной ссылки позволяет ей спокойно отправиться через реку Стикс при выхода из памяти контроллера или раскадровки.

Так как это слабая ссылка и неявно извлекаемый опционал, то объект объявлен как переменная.

UIButton! - кнопка объявлена как неявно извлекаемый опционал, так как это слабая ссылка.


Свойства программные, свойства интерфейса

Добавьте в метод viewDidLoad() следующий код:

self.myFirstButton.setTitleColor(.red, for: .normal)

Почему мы ссылаемся на self? Как сказано в предыдущем курсе - привычка, позволяющая отделить объекты внешней природы и внутренние объекты контроллера. Затем мы вызываем метод:

setTitleColor(_ color: UIColor?, for state: UIControlState)

Первым аргументом принимается объект типа UIColor?, класса который отображает цвета для интерфейса - таким цветом будет отображаться при состоянии state. Состояние отображается через тип UIControlState.
И то, и другое можно использовать в качестве перечисления - мы просто можем указать цвет .red, а состояние .normal, что есть обычное состояние кнопки. Попробуйте самостоятельно повыбирать другие цвета.

Заметьте, что цвет кнопки не изменился. Это связано с тем, что метод viewDidLoad() будет вызван только при запуске интерфейса в собранном приложении.

Обратите внимание, что если выбрать кнопку и зайти в раздел её свойств, то мы увидим свойство Text Color:

Оно также даёт возможность задать цвет текста кнопки в нормальном состоянии.
Что же будет иметь приоритет? Метод. Ибо он будет вызван после установки свойств раскадровки.
А что же лучше всего использовать? Свойства. Если Вы можете задать что-то через раскадровку, то делайте это именно так. Мы узнаем в будущем, что не все свойства можно задать через IB, однако он для того и предназначен - разгрузить код от рутинной работы.

Запустите приложение и Вы увидите: цвет кнопки стал красным. Однако нажатие на кнопку ничего не даёт. Нам ещё придётся задать ей действия.


О выборе цвета и воздействии через свойства объекта программно

А пока прервёмся на время и поиграемся с красками. Выберите цвет текста и нажмите по разделу other (другой цвет).

Вы можете выбрать цвет через краски на палитре. opacity задаёт прозрачность цвета. Вы также можете воспользоваться пипеткой.

Вы можете задать цвет через пространства RGB или CMYK, причём задание цвета возможно и через шкалу 0-255 и через дробные числа от 0 до 1.

Вы можете выбрать цвет из наборов готовых цветов.

Возможно выбрать цвет через спектр.

А также через наборы профессиональных классических карандашей.

Теперь вернемся в наш метод viewDidLoad() и попробуем добавить следующий код:

self.myFirstButton.titleLabel?.textColor = #colorLiteral(red: 0.9466800094, green: 0.3168402612, blue: 0.2236361504, alpha: 1)

Теперь после знака присвоения начните вводить Color literal и выберите в списке предложений Xcode цветовой литерал. Теперь Вы можете очень легко выбрать цвет, как делали это в инспекторе атрибутов.
Что же мы сделали здесь?
Мы попытались присвоить цвет свойству textColor свойства titleLabel у нашей кнопки. И ничего не вышло. Свойство titleLabel имеет тип UILabel?, где UILabel - тип надписи, а опциональным оно является, так как кнопка может и не иметь текста (например, когда она представлена рисунком).
Запустите приложение. Ничего не изменилось. Именно поэтому нужно использовать специальный метод. Сотрите это нововведение.


Действия

Давайте наполним жизнь нашей кнопки смыслом:

Как и раньше захватите кнопку и с зажатым ctrl перетащите её в правое окно и отпустите. В уже знакомом окне выберите не Outlet, а Action. Введите название buttonPressed. В поле Type Вы можете выбрать тип объекта, который посылает действие - по-умолчанию, это Any, но так как мы будем использовать его только для кнопки, то давайте зададим тип UIButton.

У нас появилось три новых строчки кода:

@IBAction  func buttonPressed(_ sender: UIButton) {

}

@IBAction - такой же атрибут, как и в случае с аутлетом - он проецирует метод контроллера в IB, чтобы его можно было прикреплять к объектам. Свойство sender позволяет обрабатывать объект, отправивший данное сообщение.

Пропишем в этом методе следующее:

self.myFirstButton.setTitle("Кнопка", for: .normal)

Мы использовали ещё один новый для нас метод:

setTitle(_ title: String?, for state: UIControlState)

Тут title - значение нового заголовка кнопки, а state - состояние. Это свойство можно также изменить через инспектор атрибутов в свойстве Title. Запустим приложение и увидим, что при нажатии на кнопку, её текст меняется. Вот же магия вне Хогвартса.

Заметьте, что текст в кнопку не вместился. В будущем мы узнаем, что произошло и как с таким бороться.

Теперь посмотрите на инспектор связей, соединений у кнопки. Событие Touch Up Inside связано теперь с действием buttonPressed: у ViewController. Это событие возникает при нажатии на кнопку и отпускании её. Этот подход продиктован принципами дизайна и пользовательского опыта: если пользователь нажал на кнопку, но ещё не отпустил её, то у него есть возможность передумать - он может отвести от неё палец и отпустить, а действия не будет.


Создаём объекты программно

Объекты создаются и программно (хотя напомним, лучше делайте все свои грязные дизайнерские дела через IB). Добавим следующий код в метод viewDidLoad():

let label = UILabel(frame: CGRect(x: 100, y: 100, width: 222, height: 213))

Данный код создаёт лейбл через конструктор UILabel(frame:).
В качестве аргумента конструктора выступает некий frame (кадр) - тут под ним понимается геометрическая фигура.
Для создания прямоугольника используется CGRect(x:y:width:height:), объект фреймворка Core Graphics (CG). Но постойте же? Мы не импортировали его!
Фактически CG, как и Foundation, входит в состав UIKit.

Этот конструктор для прямоугольника принимает значения позиции по x и y, а также ширину и высоту. Причем, как Вы могли заметить, доступны варианты с инициализацией через Int, Double и нечто новое - CGFloat. Это специальный тип для чисел с плавающей запятой, используемый в Core Graphics. И хотя он называется Float, он будет иметь точность, равную максимально возможному Double на данной платформе. Так как, начиная с ::iOS11:: на ::iOS:: больше нет 32-битных приложений и процессоров, то CGFloat почти эквивалентен Double. Заметим, что и CGRect, и CGFloat являются структурами, а не классами.

Несмотря на проделанную нами работу, никакой прямоугольник не появится. Добавим ещё пару штрихов в наш метод:

label.backgroundColor = #colorLiteral(red: 0.9466800094, green: 0.3168402612, blue: 0.2236361504, alpha: 1)

Данная инструкция задаст лейблу наш любимый цвет.

Далее зададим текст метки:

label.text = "Грут"

И этого всё равно мало - просто так созданный объект не появляется по волшебному щелчку.
Необходимо добавить его в представление.
У каждого UIViewController есть свойство view, возвращающее объект типа UIView (базовый класс для всех представлений), у которого в свою очередь есть метод addSubview(_:). Воспользуемся им для добавления представления на форму:

self.view.addSubview(label)

Заметьте, что метод принимает базовый класс, а не метку, что значит, что можно добавить подвид в представление любого класса-потомка. И в любое представление можно добавить другое представление.