Swift Часть 2: Простое iOS приложение

Если вы нашли опечатку в тексте, выделите ее и нажмите CTRL + ENTER.

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

Для этого туториала вам понадобится Xcode минимальной версии 6.1.1 (время написания этой статьи). Вам не нужен какой-либо опыт в программировании на Swift или Objective-C, но если он все таки есть, то он лишь ускорит процесс усвоения материала.

Поехали!

Запустите Xcode и пройдите по File\New\Project. Выберите iOS\Application\Single View Application и нажмите Next.

В графе Product Name (имя приложения) напишите TipCalculator, установите Language на Swift и смените Devices на iPhone. Use Core Data выбирать не нужно. После, нажмите Next.

Выберите директорию для сохранения проекта и нажмите Create.

Давайте взглянем, что создал для нас Xcode: в верхнем левом углу выберите iPhone Simulator и нажмите кнопку Play.

Если вы все сделали правильно, то вы увидите симулятор с белым экраном:

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

Создаем модель приложения

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

В этом туториале ваша модель будет просто представлена классом TipCalculator, который вы создали на прошлом уроке, только мы переименуем его в TipCalculatorModel.

Давайте добавим класс в ваш проект. Чтобы это сделать нужно пройти File\New\File и выбрать iOS\Source\Swift File. Называем файл TipClculatorModel.swift и жмем Create.

Заметка

Вы не можете обратиться к коду, который располагается в файле Playground'а. Playground нужен только для тестирования вашего или его макетирования. Если вы хотите использовать код из playground'а, то вам придется просто его перенести в файл Swift, как мы и сделаем тут.

Откройте TipCalculator.swift и скопируйте туда ваш класс TipCalculator (но только класс и больше ничего!) из файла предыдущего туториала и сделайте следующее:

  1. Переименуйте класс в TipCalculatorModel
  2. Поменяйте total и taxPct из констант в переменные (потому что пользователь будет менять эти данные, когда запустит приложение)
  3. Из-за этого вам нужно поменять subtotal в вычисляемое свойство. Замените свойство subtotal на следующее:
var subtotal: Double {
  get {
    return total / (taxPct + 1)
  }
}

Фактически, вычисляемое свойство не хранит значения. Вместо этого оно его вычисляет, каждый раз, основываясь на других значениях. Здесь subtotal считается каждый раз, когда мы обращаемся к нему, основываясь на значениях total и taxPct.

Заметка

Вы также можете использовать метод setter для вычисляемого свойства, если вам нравится синтаксис вроде этого:

var subtotal: Double {
  get {
    return total / (taxPct + 1)
  }
  set(newSubtotal) { 
     //... 
  }
}

Однако, setter будет обновлять ваши свойства total и taxPct, основываясь на newSubtotal, но для нас это бессмысленно, так что имплементировать это мы не будем.

  1. Удалите строку, которая устанавливает subtotal в init.
  2. Удалите все комментарии, которые есть в файле

В итоге у вас должно получиться следующее:

import Foundation
 
 class TipCalculatorModel {
 
  var total: Double
  var taxPct: Double
  var subtotal: Double {
    get {
      return total / (taxPct + 1)
    }
  }
 
  init(total: Double, taxPct: Double) {
    self.total = total
    self.taxPct = taxPct
  }
 
  func calcTipWithTipPct(tipPct: Double) -> Double {
    return subtotal * tipPct
  }
 
  func returnPossibleTips() -> [Int: Double] {
 
    let possibleTipsInferred = [0.15, 0.18, 0.20]
    let possibleTipsExplicit:[Double] = [0.15, 0.18, 0.20]
 
    var retval = [Int: Double]()
    for possibleTip in possibleTipsInferred {
      let intPct = Int(possibleTip*100)
      retval[intPct] = calcTipWithTipPct(possibleTip)
    }
    return retval
 
  }
 
 }

С моделью приложения мы разобрались, настало время поработать над интерфейсом!

Вступление в Storyboards и в Interface Builder

Заметка

Если вы уже бывалый разработчик под iOS, то эта статья и следующая будут для вас легкими. Для ускорения процесса вы можете сразу перейти в секцию "Обзор View Controller".

Вы создаете интерфейс приложения в Storyboard. В Xcode есть встроенный инструмент для удобного редактирования Storyboard, который называется Interface Builder.

В Interface Builder вы можете размещать: кнопки, текст, ярлыки и другие элементы (которые называются Views). Осуществляется все это с помощью простого перетягивания с панели, на экран вашего приложения.

Двигаемся дальше, нажмите на Main.storyboard в левой части Xcode, для того, чтобы показать Storyboard в Interface Builder.

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

  1. В левом краю у вас есть Project Navigator или навигатор проекта, где отображаются все файлы вашего проекта.
  2. Слева в Interface Builder располагается Document Outline (схема документа), где вы можете быстро взглянуть на все элементы располагающиеся на вашем "экране". При нажатии на стрелочки вы получите развернутую иерархию элементов вашего приложения на текущем "экране" (View Controller). На данный момент у вас всего один View Controller или "экран" с одним пустым белым View (смотри в иерархии). Мы скоро добавим сюда некоторые элементы.
  3. Стрелка слева от View Controller свидетельствует о том, что это входной View Controller или "экран", то есть это именно тот вид, который появляется при загрузке приложения. Вы можете изменить первоначальный "экран" просто перетащив стрелку на другой, которого у нас нет.
  4. Внизу Interface Builder'а вы видите что-то вроде "w Any", "h Any". Это значит, что вы редактируете ваш внешний вид приложения, который будет работать в интерфейсе любого размера. Вы можете сделать это через опцию Auto Layout. Кликнув на область экрана, вы можете переключить редактирование отображения для устройств принадлежащих определенному классу размеров. Об этом вы узнаете из наших будущих статей.
  5. Наверху View Controller'а вы увидите маленькие иконки, которые отображают сам View Controller, First Responder, Exit. Если вы немного программировали в Xcode ранее, то вы заметили, что эти иконки ранее были внизу. Этими иконками в этой статье мы пользоваться не будем, так что пока не берите в голову.
  6. Внизу справа Interface Builder'а четыре иконки для Auto Layout. Ну о них мы поговорим в следующих туториальных.
  7. Справа вверху Interface Builder'а располагается Inspectors (инспекторы) для выбранного вами элемента. Если у вас ничего нет, то пройдите по меню View\Utilities\Show Utilities. Обратите внимание, что тут несколько вкладок, мы будем использовать их в этом туториале для конфигурации вида.
  8. Внизу справа Interface Builder'а располагаются библиотеки или Libraries. Это перечень различных элементов, которые вы можете добавить к виду вашего приложения. Уже очень скоро мы перетащим несколько элементов из Libraryes на View Controller(экран) вашего приложения.

Создание элементов интерфейса приложения

Помните, что ваш класс TipCalculatorModel имеет два значения для ввода: общая сумма (total) и процент налога (tax percentage).

Было бы здорово, если бы пользователь смог вводить значения с цифровой клавиатуры, таким образом, текстовое поле (Text Field) подходит идеально для этих целей. Что же касается ввода процента налога, то обычно запрещено использовать его для маленьких значений, так что лучше мы будем использовать слайдер (Slider).

В дополнение к текстовому вводу и слайдеру, нам также нужно: установить ярлыки (Label), панель навигации для отображения названия приложения, кнопку для выполнения вычислений и еще одно текстовое поле для вывода результата.

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

  1. Navigation Panel (или панель навигации). Вместо того, чтобы добавить панель навигации напрямую, выберите ваш View Controller, выделив его в иерархии документов как на рисунке:

    После этого идите в Editor\Embed In\Navigation Controller. Это установит вам панель навигации в ваш View Controller (отныне экран будем называть именно так). Сделайте двойной щелчок на панели навигации (Navigation Bar, которая внутри вашего View Controller) и установите имя Tip Calculator.

  2. Labels (или ярлыки). Перетащите Label или по-русски «ярлык» в ваш View Controller.

    Сделайте двойной щелчок мыши по ярлыку Label и напишите Bill Total (Post-Tax):. Выберите Label и нажмите на пятую вкладку в Inspector, установите там X=33 и Y=81. Повторите все то же самое и для второго ярлыка, только назовите его Tax Percentage (0%): X=20 и Y=120.

  3. Text Field (текстовое поле). Из библиотеки элементов перетащите объект под названием Text Field в ваш View Controller. В инспекторе атрибутов (atribute inspector) установите Keyboard Type = Decimal Pad. В инспекторе размеров (Size Inspector) установите: X=192, Y=77 и Width=392. 
  4. Slider (или слайдер). Перетащите Slider из библиотеки в ваш View Controller. В Attribute Inspector установите Minimum Value=0 (минимальное значение), Maximum Value=10 (максимальное значение) и Current Value=6 (текущее значение). В Size Inspector установите X=190, Y=116 и Width=396.
  5. Button (или кнопка). Из Object Library (привыкаем к английскому) перетаскиваем Button (или кнопку) в ваш View Controller. Сделайте двойной щелчок на кнопке и переименуйте в Calculate. В Size Inspector установите X=268, Y=154.
  6. Text View. Из Object Library перетаскиваем Text View в ваш View Controller. Сделайте двойной щелчек на Text View и удалите текст плейсхолдера. В Attribute Inspector убедитесь в том, что Editable и Selectable не выбраны! В Size Inspector установите X=16, Y=192, Width=568, Height=400.
  7. Tap Gesture Recognizer (элемента, распознающий движения). Из Object Library перетаскиваем Tap Gesture Recognizer в ваш Main View (используйте иерархию элементов для того, чтобы точно выделить Main View, а не элемент внутри него).

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

  8. Auto Layout или (автопозиционирование). Interface Builder часто делает всю грязную работу за нас, когда устанавливает ограничения расположения элементов самостоятельно. В нашем случае мы как раз это и можем использовать. Для того, чтобы это сделать, выберите Main View в иерархии документа и нажмите третью кнопку в правой нижней части Interface Builder'а. Выберете Add Missing Constraints (или добавить недостающие ограничения расположения элементов).

Теперь запустите свой симулятор iPhone 6 и вы должны увидеть, что базовый интерфейс пользователя уже работает!

Тур по View Controller

Заметка

Если вы сразу прыгнули до этой секции, то вот вам наш plaground в zip!

Только что вы создали модель и внешний вид приложения, теперь самое время разобраться с View Controller!

Откройте ViewController.swift. Тут находится код вашего одиночного View Controller'а (экрана) приложения. Эта штуковина отвечает за взаимодействие ваших элементов с вашей моделью.

Вы увидите, что этот класс уже имеет вот такой код внутри:

\\
import UIKit

// 2
class ViewController: UIViewController {

  // 3
 override func viewDidLoad() {
    super.viewDidLoad()
     // Do any additional setup after loading the view, typically from a nib.
   }
  
   // 4
   override func didReceiveMemoryWarning() {
     super.didReceiveMemoryWarning()
     // Dispose of any resources that can be recreated.
   }
  
 }

Есть несколько моментов, c которыми в ы до сих пор не сталкивались. Давайте разберемся с ними по одному:

  1. iOS разделена на множество frameworks(или фреймворки), каждый из которых содержит различные наборы кода. До того как использовать эти наборы в вашем приложении, вы должны импортировать этот фреймворк. UIKit - фреймворк, который содержит базовые классы для работы с View Controller'ми и элементами управления, такими как: кнопки, поля ввода текста и множество других.
  2. Это первый пример, где вы видите класс, который является подклассом. Вы объявляете ViewController, который является подклассом UIViewController.

    Заметка

    Опытные разработчики заметили, что ненужно ставить префикс класса перед именем класса, как вы это делали в Objective-C для избежания совпадения имен (другими словами, вам не нужно называть это RWTViewController). Все потому, что в Swift есть пространство имен и классы, которые вы создали в вашем проекте.

    Для того, чтобы вы поняли, что мы имеем в виду, замените объявление класса следующим кодом:

    class UIViewController {
    }
     
    class ViewController: UIKit.UIViewController {

    Здесь UIKit.UIViewController относится к классу UIViewController в пространстве имени UIKit. Так же как и TipCalculator.UIViewController будет относиться к к классу UIViewController в вашем проекте.

    Не забудьте удалить этот тестовый код и вернуть предыдующее объявление ViewController

  3. Этот метод вызван корневым view этого View Controller'а, когда к нему получен доступ. Всякий раз, когда вы переписываете метод в Swift, вы должны использовать ключевое слово override. Это помогает избежать вам случайного изменения метода.
  4. Этот метод вызывается, когда у вашего устройства остается мало памяти. Это удачное место для чистки ваших ресурсов, которые вы можете припасти для дальнейших операций.

Соединяем ваш View Controller с вашими Views (элементами)

Сейчас, у вас уже сложилось правильное понимание класса View Controller, что ж, давайте добавим некоторые свойства для ваших subviews (элементов, которые находятся внутри view в иерархии документа) и подключим их в interface builder'е.

Для того, чтобы сделать это, добавьте следующие свойства в ваш класс ViewController(прямо перед viewDidLoad):

@IBOutlet var totalTextField : UITextField!
@IBOutlet var taxPctSlider : UISlider!
@IBOutlet var taxPctLabel : UILabel!
@IBOutlet var resultsTextView : UITextView!

Тут мы объявляем четыре переменные, прямо так, как вы изучили в нашем первом туториале: UITextField, UISlider, UILabel и UITextView.

Здесь всего два отличия:

  1. Вы пишите перед переменными ключевое слово @IBOutlet. Interface Builder сканирует код в поиске каких-либо свойств в вашем View Controller'е с этим словом. У найденных свойств он ищет соединения, так что вы смело можете их соединить с элементами (или Views).
  2. Вы обозначаете переменные восклицательным знаком (!). Это значит, что ваши переменные опциональны, но их значения неявно извлекаются. Этот замечательный способ сказать, что вы пишите код, предполагая, что они имеют значения, и ваше приложение завершится с ошибкой, если вдруг хотя бы одно из них окажется без значения, т.е. nil.

    Неявное извлекаемые опционалы - удобный способ для того, чтобы создать переменные, которым точно будут присвоены значения до того, как они будут использованы (как создание элементов пользовательского интерфейса в Storyboard), так что вы не должны извлекать значение опционала каждый раз перед использованием. Давайте попробуем соединить эти свойства с элементами пользовательского интерфейса.

Откройте Main.storyboard и выберите ваш View Controller (Tip Calculater в нашем случае) в иерархии документа (Document Outline). Откройте Connection Inspector (6 вкладка) и вы увидите все свойства, которые вы создали в секции Outlets.

Вы наверняка заметили маленький круг справа от resultsTextView. Удерживайте клавишу ctrl и перетащите курсор с него и до text view, который под Calculate button, и отпустите. Вы только что соединили свойство с элементом (или view).

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

Заметка

Есть даже более простой способ подключения свойств к элементам.

Пока вы держите Main.storyboard открытым, вы можете открыть Assiastand Editor (View\Assistant Editor\Show Assistant Editor), убедитесь, что ваш assistant editor отображает ваш код view controller'а.

После чего вы можете перетащить (с ctrl) ваш элемент в Assistant Editor, прямо до viewDidLoad. В появившемся окне вам будет нужно ввести имя свойства для того чтобы его создать, после чего жмем Connect.

Такой метод позволит вам создать свойство и сразу его подключить, и все это за один раз. Неплохо, правда?

Оба способа работают хорошо, а вот который вам ближе, решать вам.

Подсоединяем Actions(действия) к вашему View Controller'у

Точно также как вы соединили элементы со свойствами вашего view controller'а, вам нужно так же соединить определенные действия ваших элементов (например, нажатие кнопки) с методами вашего view controller'а.

Откроем ViewController.swift и добавим эти три метода в ваш класс:

@IBAction func calculateTapped(sender : AnyObject) {
}
@IBAction func taxPercentageChanged(sender : AnyObject) {
}
@IBAction func viewTapped(sender : AnyObject) {
}

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

Заметка

AnyObject - эквивалент для id в Objective-C.

Для того чтобы Interface Builder заметил ваши новые методы, вам нужно использовать ключевое слово @IBAction (точно так же как и в случае с @IBOutlet).

Далее, откройте Main.stroryboard и убедитесь что в схеме документа вы выделили свой view controller. Также убедитесь, что ваш Connections Inspector (6 вкладка) открыт и вы увидите новые методы в секции Recieved Actions.

Найдите кружок справа от calculateTapped и перетащите его на кнопку Calculate. В появившемся окне выберите Touch Up Inside:

Другими словами мы говорим :"Вызывать мой метод calculateTapped: тогда, когда пользователь уже убирает палец с экрана".

Теперь повторим то же самое для остальных двух методов:

  • Перетащим taxPercentageChanged на ваш слайдер и соединим его с действием Value Changed, которое вызывается каждый раз, как только пользователь двигает слайдер.
  • Перетащим viewTapped на Tap Gesture Recognizer в схему документа. Никаких действий для этого нет, так что ваш метод будет просто вызываться, когда распознает соответствующее движение.

Заметка

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

Вы можете просто ctrl-перетащить от, скажем, кнопки в ваш Swift код вашего View Controller'а в Assistant Editor. В появившемся окне вам нужно выбрать Action и дать имя вашего метода.

Таким образом вы создадите метод в вашем Swift файле и соедините его с действием всего за один шаг. И снова, используйте тот метод соединения, который вам больше всего нравится.

Соединение вашего View Controller'а с вашей моделью

Уже почти все закончили. Все что осталось сделать, так это соединить ваш View Controller с вашей моделью.

Откройте ViewController.swift и добавьте свойство для модели вашего класса и метод для обновления пользовательского интерфейса:

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

func refreshUI() {
  // 1
  totalTextField.text = String(format: "%0.2f", tipCalc.total)
  // 2
  taxPctSlider.value = Float(tipCalc.taxPct) * 100.0
  // 3
  taxPctLabel.text = "Tax Percentage (\(Int(taxPctSlider.value))%)"
   // 4
  resultsTextView.text = ""
}

Давайте пройдемся в refreshUI по одной строчке:

  1. В Swift вам нужно явно конвертировать один тип в другой. Здесь мы конвертируем tipCalc.total из Double в String
  2. Вы хотите, чтобы процент налога отображался как целое число (то есть от 0%-10%), чем дробное (что-то вроде 0.06). Так что просто умножим на 100.
  3. Помните, что это действие необходимо, так как свойство taxPctSlider.value является типом Float.
  4. Тут мы используем интерполяцию для обновления ярылка, отображающего процент налога.
  5. Очищаем результат в текстовом поле (text view) до тех пор пока пользователь не нажал кнопку Calculate.

Следующее, добавим вызов refreshUI внизу в viewDidLoad:

refreshUI()

Так же имплементируем taxPercentageChanged и viewTapped:

@IBAction func taxPercentageChanged(sender : AnyObject) {
  tipCalc.taxPct = Double(taxPctSlider.value) / 100.0
  refreshUI()
}
@IBAction func viewTapped(sender : AnyObject) {
  totalTextField.resignFirstResponder()
}

taxPercentageChanged просто обращает "умножение на 100", в то время как viewTapped вызывает resignFirstResponder в поле totalTextField, когда пользователь нажал на view(что и заставляет клавиатуру исчезнуть с экрана).

Остался один метод. Имплементируем calculateTapped:

@IBAction func calculateTapped(sender : AnyObject) {
  // 1
  tipCalc.total = Double((totalTextField.text as NSString).doubleValue)
  // 2
  let possibleTips = tipCalc.returnPossibleTips()
  var results = ""
  // 3
  for (tipPct, tipValue) in possibleTips {
    // 4
    results += "\(tipPct)%: \(tipValue)\n"
  }
  // 5
  resultsTextView.text = results
}

Давайте все разберем по порядку:

Заметка

Вот как это все работает, если вам это интересно.

Во время написания этого туториала класс String в Swift не имеет доступа ко всем методам, в отличии от NSString (NSString - класс в фреймворке Foundation). Если быть конкретным, то класс String в Swift не имеет метода, который бы преобразовывал тип String в тип Double, хотя NSSting такой метод имеет.

Вы можете вызвать (XXX as NSString)() в Swift для преобразования String в NSString. После чего вы можете использовать любой метод, доступный для NSString, например, метод, преобразующий String в Double.

  1. Тут нужно преобразовать тип String в Double. Это несколько коряво так делать, но думаем, уже скоро будет способ поприличнее.
  2. Здесь вы вызываете метод returnPossibleTips модели tipCalc, которая возвращает словарь с возможными процентами чаевых.
  3. Это то, как вы перебираете словарь через ключи и значения одновременно. Удобно, не так ли?
  4. Здесь мы используем интерполяцию строк, для создания строки, которую поместим в текстовое поле. Мы используем \n для начала новой строки.
  5. Наконец, мы присваиваем текстовому полю значение result.

Вот и все! Запускайте и наслаждайтесь вашим калькулятором чаевых!

Это конечный вариант нашего Xcode проекта.

Урок подготовил: Иван Акулов
Источник урока: http://www.raywenderlich.com/74904/swift-tutorial-part-2-simple-ios-app