В предыдущем уроке мы изучили основы разработки WatchKit. Мы создали проект в Xcode, добавили приложение WatchKit и создали базовый пользовательский интерфейс.
Пользовательский интерфейс нашего приложения WatchKit в настоящее время отображает статические данные. Если вы не живете в пустыне, это не очень полезно для погодных условий. В этом уроке мы собираемся заполнить пользовательский интерфейс данными и создать несколько действий.
1. Обновление пользовательского интерфейса
Шаг 1: Замена WKInterfaceDate
Прежде чем заполнять пользовательский интерфейс данными, нам нужно внести небольшое изменение. В предыдущем уроке мы добавили экземпляр WKInterfaceDate
в нижнюю группу для отображения текущего времени и даты. Однако было бы более полезно отобразить время и дату данных, которые мы отображаем. Причина этого изменения станет ясна через несколько минут.
Откройте Interface.storyboard , удалите экземпляр WKInterfaceDate
в нижней группе и замените его на экземпляр WKInterfaceLabel
. Установите для атрибута ширины метки значение « Относительно контейнера», а для выравнивания метки — по правому краю.
Шаг 2: Добавление торговых точек
Чтобы обновить пользовательский интерфейс динамическими данными, нам нужно создать несколько выходов в классе InterfaceController
. Откройте раскадровку в главном редакторе и InterfaceController.swift в помощнике редактора справа. Выберите верхнюю метку в первой группе и перетащите Control из метки в класс InterfaceController
чтобы создать розетку. Назовите locationLabel
торговой locationLabel
.
Повторите эти шаги для других меток, назвав их dateLabel
и dateLabel
соответственно. Вот как должен выглядеть класс InterfaceController
когда вы закончите.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import WatchKit
import Foundation
class InterfaceController: WKInterfaceController {
@IBOutlet weak var dateLabel: WKInterfaceLabel!
@IBOutlet weak var locationLabel: WKInterfaceLabel!
@IBOutlet weak var temperatureLabel: WKInterfaceLabel!
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
}
|
Теперь, возможно, самое время более внимательно взглянуть на реализацию класса InterfaceController
. В предыдущем уроке я упоминал, что InterfaceController
наследуется от WKInterfaceController
. На первый взгляд может показаться, что экземпляр WKInterfaceController
ведет себя как экземпляр UIViewController
, но в предыдущем уроке мы также узнали, что существует ряд ключевых отличий.
Чтобы помочь нам, Xcode заполнил класс InterfaceController
тремя переопределенными методами. Важно понимать, когда каждый метод вызывается и для чего он может или должен использоваться.
awakeWithContect(_:)
В awakeWithContext(_:)
вы устанавливаете и инициализируете контроллер интерфейса. Вам может быть интересно, чем он отличается от метода init
. Метод awakeWithContext(_:)
вызывается после инициализации контроллера интерфейса. Метод принимает один параметр, объект контекста, который позволяет интерфейсным контроллерам передавать информацию друг другу. Это рекомендуемый подход для передачи информации между сценами, то есть интерфейсными контроллерами.
willActivate
Метод willActivate
аналогичен viewWillAppear(_:)
класса UIViewController
. Метод willActivate
вызывается до того, как пользовательский интерфейс контроллера интерфейса будет представлен пользователю. Он идеально подходит для настройки пользовательского интерфейса перед его представлением пользователю.
didDeactivate
Метод didDeactivate
является аналогом метода willActivate
и вызывается при willActivate
сцены контроллера интерфейса. Любой код очистки входит в этот метод. Этот метод похож на метод viewDidDisappear(_:)
найденный в классе UIViewController
.
Имея это в виду, мы можем начать загружать данные и обновлять пользовательский интерфейс нашего приложения WatchKit. Начнем с загрузки данных о погоде.
2. Загрузка данных о погоде
Лучшие практики
Вы можете подумать, что следующим шагом будет вызов API для службы погоды, но это не так. Если бы мы создавали приложение для iOS, вы были бы правы. Однако мы создаем приложение WatchKit.
Не рекомендуется делать сложные вызовы API для извлечения данных для заполнения пользовательского интерфейса приложения WatchKit. Хотя Apple явно не упоминает об этом в документации, инженер Apple упомянул эту неписаную лучшую практику на форумах разработчиков Apple .
Приложение WatchKit является частью приложения iOS, и это приложение iOS отвечает за выборку данных из удаленного сервера. Есть несколько подходов, которые мы можем использовать для этого, выборка фона — хороший выбор. Однако в этом уроке мы не будем фокусироваться на этом аспекте.
Вместо этого мы добавим фиктивные данные в комплект расширения WatchKit и загрузим их в метод awakeWithContext(_:)
который мы обсуждали ранее.
Создайте пустой файл, выбрав New> File … в меню File . Выберите « Очистить» в разделе « iOS»> «Другое » и назовите файл weather.json . Дважды проверьте, что вы добавляете файл в расширение RainDrop WatchKit . Не забывайте эту маленькую, но важную деталь. Заполните файл следующими данными.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
{
«locations» : [
{
«location» : «Cupertino»,
«temperature» : 24,
«timestamp» : 1427429751
},
{
«location» : «London»,
«temperature» : 11,
«timestamp» : 1427429751
},
{
«location» : «Paris»,
«temperature» : 9,
«timestamp» : 1427429751
},
{
«location» : «Brussels»,
«temperature» : 11,
«timestamp» : 1427429751
}
]
}
|
Обмен данными
Обмен данными между приложением iOS и приложением WatchKit является важной темой. Тем не менее, это руководство посвящено настройке и запуску вашего первого приложения WatchKit. В следующем уроке я сосредоточусь на обмене данными между iOS и приложением WatchKit.
Даже несмотря на то, что мы не будем рассказывать об обмене данными в этом руководстве, важно знать, что приложение iOS и расширение WatchKit не имеют общей песочницы. Обе цели имеют свою собственную изолированную программную среду, и это делает обмен данными менее тривиальным, чем кажется.
Для обмена данными между iOS и приложением WatchKit необходимо использовать группы приложений. Но это тема для будущего урока.
Шаг 1: Добавление SwiftyJSON
Swift — отличный язык, но некоторые задачи в Objective-C проще, чем в Swift. Например, обработка JSON является одной из таких задач. Чтобы упростить эту задачу, я решил использовать популярную библиотеку SwiftyJSON .
Загрузите репозиторий с GitHub , разархивируйте архив и добавьте SwiftyJSON.swift в группу RainDrop WatchKit Extension . Этот файл находится в папке Source архива. Дважды проверьте, что в SwiftyJSON.swift добавлена цель RainDrop WatchKit Extension .
Шаг 2: Реализация WeatherData
Чтобы упростить работу с данными о погоде, хранящимися в weather.json , мы собираемся создать структуру с именем WeatherData . Выберите Создать> Файл … из в меню « Файл» выберите « Файл Swift» в разделе « iOS»> «Источник » и назовите файл « WeatherData» . Убедитесь, что файл добавлен в целевой объект RainDrop WatchKit Extension .
Реализация структуры WeatherData
короткая и простая. Структура определяет три постоянных свойства, date
, location
и temperature
.
1
2
3
4
5
6
7
|
import Foundation
struct WeatherData {
let date: NSDate
let location: String
let temperature: Double
}
|
Поскольку значение температуры weather.json находится в градусах Цельсия, мы также реализуем вычисленное свойство fahrenheit
для простого преобразования между градусами Цельсия и Фаренгейта.
1
2
3
|
var fahrentheit: Double {
return temperature * (9 / 5) + 32
}
|
Мы также определяем два вспомогательных метода toCelciusString
и toFahrenheitString
чтобы упростить форматирование значений температуры. Разве вы не любите строковую интерполяцию Свифта?
1
2
3
4
5
6
7
|
func toCelciusString() -> String {
return «\(temperature) °C»
}
func toFahrenheitString() -> String {
return «\(fahrentheit) °F»
}
|
Как я уже сказал, реализация структуры WeatherData
короткая и простая. Вот как должна выглядеть реализация.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
import Foundation
struct WeatherData {
let date: NSDate
let location: String
let temperature: Double
var fahrentheit: Double {
return temperature * (9 / 5) + 32
}
func toCelciusString() -> String {
return «\(temperature) °C»
}
func toFahrenheitString() -> String {
return «\(fahrentheit) °F»
}
}
|
Шаг 3: Загрузка данных
Прежде чем загружать данные из weather.json , нам нужно объявить свойство для хранения данных о погоде. Свойство weatherData
имеет тип [WeatherData]
и будет содержать содержимое weather.json в качестве экземпляров структуры WeatherData
.
1
|
var weatherData: [WeatherData] = []
|
Для простоты использования мы также объявляем вычисляемое свойство weather
, которое дает нам доступ к первому элементу массива weatherData
. Это данные этого экземпляра WeatherData
которые мы будем отображать в контроллере интерфейса. Можете ли вы догадаться, почему мы должны объявить свойство weather
необязательным?
1
2
3
|
var weather: WeatherData?
return weatherData.first
}
|
Мы загружаем данные из weather.json в awakeWithContext(_:)
. Чтобы сохранить реализацию чистой, мы вызываем вспомогательный метод с именем loadWeatherData
.
1
2
3
4
5
6
|
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Load Weather Data
loadWeatherData()
}
|
Реализация loadWeatherData
, пожалуй, самый сложный фрагмент кода, который мы увидим в этом руководстве. Как я уже сказал, анализ JSON не является тривиальным в Swift. К счастью, SwiftyJSON делает большую часть тяжелой работы за нас.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
func loadWeatherData() {
let path = NSBundle.mainBundle().pathForResource(«weather», ofType: «json»)
if let path = path {
let data = NSData(contentsOfFile: path)
if let data = data {
let weatherData = JSON(data: data)
let locations = weatherData[«locations»].array
if let locations = locations {
for location in locations {
let timestamp = location[«timestamp»].double!
let date = NSDate(timeIntervalSinceReferenceDate: timestamp)
let model = WeatherData(date: date, location: location[«location»].string!, temperature: location[«temperature»].double!)
self.weatherData.append(model)
}
}
}
}
}
|
Мы получаем путь к weather.json и загружаем его содержимое как объект NSData
. Мы используем SwiftyJSON для анализа JSON, передавая объект NSData
. Мы получаем ссылку на массив для ключевых местоположений и зацикливаемся над каждым местоположением.
Мы нормализуем данные о погоде путем преобразования временной метки в экземпляр NSDate
и инициализируем объект WeatherData
. Наконец, мы добавляем объект weatherData
массив weatherData
.
Я надеюсь, что вы согласны с тем, что внедрение не так уж сложно. Поскольку Swift вынуждает нас сделать ряд проверок, реализация выглядит более сложной, чем она есть на самом деле.
3. Заполнение пользовательского интерфейса
Когда данные о погоде готовы к использованию, пришло время обновить пользовательский интерфейс. Как я объяснил ранее, обновление пользовательского интерфейса должно происходить в методе willActivate
. Давайте посмотрим на реализацию этого метода.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
if let weather = self.weather {
locationLabel.setText(weather.location)
// Update Temperature Label
self.updateTemperatureLabel()
// Update Date Label
self.updateDateLabel()
}
}
|
После вызова метода суперкласса willActivate
мы разворачиваем значение, сохраненное в свойстве weather
. Чтобы обновить метку местоположения, мы вызываем setText
, передавая значение, хранящееся в свойстве location
объекта weather
. Чтобы обновить метки температуры и даты, мы вызываем два вспомогательных метода. Я предпочитаю, чтобы метод willActivate
коротким и лаконичным, и, что более важно, я не люблю повторяться.
Прежде чем мы рассмотрим эти вспомогательные методы, нам нужно знать, нужно ли отображать температуру в градусах Цельсия или Фаренгейта. Чтобы решить эту проблему, объявите свойство celcius
типа Bool
и установите для его начального значения значение true
.
1
|
var celcius: Bool = true
|
Реализация updateTemperatureLabel
проста для понимания. Мы безопасно распаковываем значение, сохраненное в weather
и обновляем температурную метку на основе значения в celcius
. Как вы можете видеть, два вспомогательных метода структуры WeatherData
мы создали ранее, пригодятся.
1
2
3
4
5
6
7
8
9
|
func updateTemperatureLabel() {
if let weather = self.weather {
if self.celcius {
temperatureLabel.setText(weather.toCelciusString())
} else {
temperatureLabel.setText(weather.toFahrenheitString())
}
}
}
|
Реализация updateDateLabel
не сложна. Мы инициализируем экземпляр NSDateFormatter
, устанавливаем его свойство dateFormat
и конвертируем дату объекта weather
, вызывая stringFromDate(_:)
dateFormatter
объекта dateFormatter
. Это значение используется для обновления метки даты.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
func updateDateLabel() {
var date: NSDate = NSDate()
// Initialize Date Formatter
let dateFormattter = NSDateFormatter()
// Configure Date Formatter
dateFormattter.dateFormat = «d/MM HH:mm»
if let weather = self.weather {
date = weather.date
}
// Update Date Label
dateLabel.setText(dateFormattter.stringFromDate(date))
}
|
Создайте и запустите приложение, чтобы увидеть результат. Теперь пользовательский интерфейс должен быть заполнен данными из weather.json .
4. Переход на Фаренгейт
Это выглядит хорошо. Но разве не было бы замечательно, если бы мы добавили поддержку и Цельсию, и Фаренгейту? Это легко сделать, так как мы уже заложили большую часть основы.
Если пользовательское усилие касается пользовательского интерфейса контроллера пользовательского интерфейса, отображается меню. Конечно, это работает только при наличии меню. Посмотрим, как это работает.
Откройте Interface.storyboard и добавьте меню к контроллеру интерфейса в схеме документа слева. По умолчанию меню имеет один пункт меню. Нам нужны два пункта меню, поэтому добавьте еще один пункт меню в меню.
Обратите внимание, что меню и его элементы не отображаются в пользовательском интерфейсе. Это не проблема, так как мы не можем настроить макет меню. Что мы можем изменить, так это текст пункта меню и его изображение. Вы лучше поймете, что это значит, когда представим меню.
Выберите верхний пункт меню, откройте инспектор атрибутов , установите для параметра « Заголовок» значение « Цельсий» и « Изображение для подтверждения» . Выберите нижний пункт меню и установите для заголовка значение Фаренгейт, а для изображения — Принять .
Затем откройте InterfaceController.swift в помощнике редактора справа. toCelcius
Control», перетащите элемент верхнего меню в InterfaceController.swift и создайте действие с именем toCelcius
. Повторите этот шаг для нижнего пункта меню, создав действие с именем toFahrenheit
.
Реализация этих действий коротка. В toCelcius
мы проверяем, установлено ли для свойства celcius
значение false
, и, если оно установлено, мы устанавливаем для свойства значение true
. В toFahrenheit
мы проверяем, установлено ли для свойства celcius
значение true
, и, если оно установлено, мы устанавливаем для свойства значение false
.
01
02
03
04
05
06
07
08
09
10
11
|
@IBAction func toCelcius() {
if !self.celcius {
self.celcius = true
}
}
@IBAction func toFahrenheit() {
if self.celcius {
self.celcius = false
}
}
|
Если значение в celcius
изменяется, нам нужно обновить пользовательский интерфейс. Что может быть лучше для реализации этого путем реализации наблюдателя свойства в свойстве celcius
. Нам нужно только реализовать наблюдателя свойства didSet
.
1
2
3
4
5
6
7
|
var celcius: Bool = true {
didSet {
if celcius != oldValue {
updateTemperatureLabel()
}
}
}
|
Единственная деталь, о которой стоит упомянуть, это то, что пользовательский интерфейс обновляется только в том случае, если значение celcius
изменилось. Обновление пользовательского интерфейса так же просто, как вызов updateTemperatureLabel
. Создайте и запустите приложение WatchKit в iOS Simulator, чтобы протестировать меню.
Стоит отметить, что iOS Simulator имитирует отзывчивость физического устройства. Что это обозначает? Помните, что расширение WatchKit работает на iPhone, а приложение WatchKit — на Apple Watch. Когда пользователь касается элемента меню, сенсорное событие отправляется через соединение Bluetooth на iPhone. Расширение WatchKit обрабатывает событие и отправляет все обновления обратно в Apple Watch. Это общение довольно быстрое, но не такое быстрое, как если бы расширение и приложение запускались на одном устройстве. Эта короткая задержка имитируется iOS Simulator, чтобы помочь разработчикам получить представление о производительности.
Вывод
После того, как вы освоили архитектуру приложения WatchKit, становится намного легче понять возможности и ограничения приложений WatchKit первого поколения. В этом уроке мы рассмотрели только основы разработки WatchKit. Существует гораздо больше, чтобы открыть и исследовать. Будьте на связи.