Совсем недавно, в сентябре, Apple выпустила ожидаемый многими апдейт, вторую версию операционной системы watchOS. По написанию программ для нее статей на Хабре вроде еще не было, попробуем исправить этот момент.
Все знают, что ключевым недостатком всех «умных часов» является малое время работы от батарей. Инженеры Apple решили исправить этот момент весьма простым способом — максимально разгрузить процессор часов. Для этого было придумано оригинальное решение — на часах хранились лишь ресурсы программы, а все вычисления делались на процессоре телефона. Программа для часов состояла из 2х компонентов: Watch App (то что хранится на часах) и WatchKit Extention (хранится на телефоне). Т.е. по сути, это было неким вариантом «удаленного рабочего стола» для смартфона — без наличия рядом телефона приложение работать не могло. В качестве канала связи скорее всего, использовался не отличающийся быстротой Bluetooth LE. Пользователи в итоге часто жаловались на «заторможенность» интерфейса, что было следствием этого принципа. В общем, несмотря на оригинальность, концепция «не взлетела». Так вот, основное отличие OS2 — теперь приложение хранится на часах полностью. Однако абсолютно независимым оно все равно не является — хотя приложение на часах работает автономно, должна быть «основная» программа для iPhone, вместе с которой это приложение ставится. Все это сильно похоже на «костыль», и в следующей версии такого ограничения наверно не будет. Впрочем, посмотрим.
Если говорить о железе, то программисту доступны 2 варианта часов для отладки:
— экран 38mm, разрешение 272x340,
— экран 42мм, разрешение 312x390.
Остальных характеристик (память, процессор и пр), на сайте Apple их найти не удалось. Впрочем, для нашего проекта это не столь важно. Перейдем к проекту (осторожно, траффик).
Как и все приложения для Mac OS, iOS, программы для watchOS создаются в среде Xcode. Скачать ее бесплатно можно в Mac App Store. Итак, выбираем в Xcode создание нового проекта. В версии 7.0 появился новый template iOS App with WatchKit App.
Создаем приложение, назовем его WatchTest. Xcode создает проект, как показано на рисунке.
Как можно видеть, проект состоит из 3х частей:
— WatchTest — основное приложение для iOS,
— WatchKit App — часть приложения, хранящее ресурсы,
— WatchKit Extention — часть приложения, хранящая код.
(Видно, что разделение на 2 части осталось, что похоже на «костыль» N2, но теперь по крайней мере, обе части хранятся на часах, и задержки связанной с передачей сигнала на смартфон и обратно, не будет).
Для учебных целей создадим что-то несложное, например программу-секундомер. Один Label выводящий время, и 2 кнопки «старт-стоп» и «сброс». Нажатие кнопки старт запускает таймер, инкрементирующий количество долей секунды. Опустим много промежуточных деталей, покажем сразу результат. Код (на Swift) и ресурсы показаны на рисунке.
Здесь можно видеть контролы (с тэгом
Запускаем приложение, убеждаемся что оно работает. Теперь создадим программу для Apple Watch, которая будет «пультом ДУ» для нашего секундомера. Там будет 2 кнопки «старт» и «сброс», которые будут активировать соответствующие команды на смартфоне. Этого можно было бы не делать, ограничившись таким же секундомером на часах, но интересно попробовать новый Watch Connectivity framework, предназначенный специально для обмена между часами и телефоном.
Открываем interface.storyboard на WatchKit App. Добавляем 2 кнопки и Label для вывода результата. В коде создаем объект класса WCSession, который и отвечает за коннект часов с телефоном. У этого класса есть метод sendCommand, которым мы и воспользуемся. Его параметром является dictionary, в который мы можем поместить различные данные. В нашем случае мы будем передавать обычную текстовую команду. Способ может не самый эффективный, зато простой и наглядный.
Итоговый результат (ресурсы + код) показан на рисунке.
Запускаем, нажимаем кнопки… и ничего не происходит. Правильно, мы не добавили обработчик сообщений от часов в основную программу.
Добавляем в приложение iOS практически идентичный код для инициализации WCSession. Интерес для нас представляет функция session:didReceiveMessage, которая автоматически вызовется, как только приложение получит сообщение от часов. Параметром этой функции будет тот самый dictionary, который мы передали при отправке. В replyHandler мы можем передать любой ответ, который уже на часах может быть проанализирован. Функция dispatch_async(dispatch_get_main_queue()) нужна для того, чтобы работа с таймером осуществлялась в основном потоке, иначе он не запускается корректно.
Окончательный вариант кода виден на рисунке.
Теперь запускаем программу на часах и смартфоне, и все работает — нажатия кнопок на часах корректно запускают таймер, как и хотелось.
Среда Xcode имеет в комплекте вполне функциональный симулятор, который выглядит примерно так.
Почему-то среди пар «симулятор-часы» есть только iPhone 6 и iPhone 6 Plus, будут ли часы работать с другими моделями, мне неизвестно. Впрочем Apple Watch у меня все равно нет, как и iPhone 6/6Plus, так что проверить «вживую» увы, не на чем (это интересно, но не настолько, чтобы заплатить 30 тыс.р. за часы).
Эту программу размещать в App Store мы разумеется, не будем. А для тех, кто захочет размещать там свои шедевры, отмечу кратко:
— необходимо добавить дополнительные иконки в Watch Kit App, они будут отображаться на часах
— необходимо добавить новые скриншоты и иконку в App Store
— необходимо добавить новые Apple ID для каждого компонента (App и Extention) и сгенерировать distribution-сертификаты для каждого компонента. Всего таким образом, получается 3 сертификата на программу.
— далее как обычно, Build-Archive, и полученный архив, содержащий все внутри, заливается в App Store.
Как-то так. Если кому-то интересно, можно будет продолжить. Как можно видеть, программирование для watchOS хоть и имеет некоторые особенности, но кардинально не отличается от создания обычной iOS-программы. Автор желает всем удачных экспериментов.
Что нового
Все знают, что ключевым недостатком всех «умных часов» является малое время работы от батарей. Инженеры Apple решили исправить этот момент весьма простым способом — максимально разгрузить процессор часов. Для этого было придумано оригинальное решение — на часах хранились лишь ресурсы программы, а все вычисления делались на процессоре телефона. Программа для часов состояла из 2х компонентов: Watch App (то что хранится на часах) и WatchKit Extention (хранится на телефоне). Т.е. по сути, это было неким вариантом «удаленного рабочего стола» для смартфона — без наличия рядом телефона приложение работать не могло. В качестве канала связи скорее всего, использовался не отличающийся быстротой Bluetooth LE. Пользователи в итоге часто жаловались на «заторможенность» интерфейса, что было следствием этого принципа. В общем, несмотря на оригинальность, концепция «не взлетела». Так вот, основное отличие OS2 — теперь приложение хранится на часах полностью. Однако абсолютно независимым оно все равно не является — хотя приложение на часах работает автономно, должна быть «основная» программа для iPhone, вместе с которой это приложение ставится. Все это сильно похоже на «костыль», и в следующей версии такого ограничения наверно не будет. Впрочем, посмотрим.
Если говорить о железе, то программисту доступны 2 варианта часов для отладки:
— экран 38mm, разрешение 272x340,
— экран 42мм, разрешение 312x390.
Остальных характеристик (память, процессор и пр), на сайте Apple их найти не удалось. Впрочем, для нашего проекта это не столь важно. Перейдем к проекту (осторожно, траффик).
Создаем проект
Как и все приложения для Mac OS, iOS, программы для watchOS создаются в среде Xcode. Скачать ее бесплатно можно в Mac App Store. Итак, выбираем в Xcode создание нового проекта. В версии 7.0 появился новый template iOS App with WatchKit App.
Создаем приложение, назовем его WatchTest. Xcode создает проект, как показано на рисунке.
Как можно видеть, проект состоит из 3х частей:
— WatchTest — основное приложение для iOS,
— WatchKit App — часть приложения, хранящее ресурсы,
— WatchKit Extention — часть приложения, хранящая код.
(Видно, что разделение на 2 части осталось, что похоже на «костыль» N2, но теперь по крайней мере, обе части хранятся на часах, и задержки связанной с передачей сигнала на смартфон и обратно, не будет).
Создаем приложение iOS
Для учебных целей создадим что-то несложное, например программу-секундомер. Один Label выводящий время, и 2 кнопки «старт-стоп» и «сброс». Нажатие кнопки старт запускает таймер, инкрементирующий количество долей секунды. Опустим много промежуточных деталей, покажем сразу результат. Код (на Swift) и ресурсы показаны на рисунке.
Здесь можно видеть контролы (с тэгом
@IBOutlet
, в нашем случае только один UILabel) и обработчики 2х кнопок с тегом @IBAction
(кому интересно подробнее, можно почитать например здесь).Исходный код этой части (после небольшого рефакторинга)
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var timeLabel: UILabel!
var seconds: Int!
var timer: NSTimer!
let interval: Double = 1.0/20.0
override func viewDidLoad() {
super.viewDidLoad()
self.seconds = 0;
// Watch support
initWatchConnection()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
@IBAction func onButtonStart(sender: AnyObject) {
startOrStopTimer()
}
@IBAction func onButtonReset(sender: AnyObject) {
resetTimer()
}
func startOrStopTimer() {
if (self.timer == nil) {
self.timer = NSTimer.scheduledTimerWithTimeInterval(interval, target:self,
selector:"onTimer", userInfo:nil,
repeats:true)
} else {
self.timer.invalidate()
self.timer = nil
}
}
func resetTimer() {
if (self.timer != nil) {
self.timer.invalidate()
self.timer = nil
}
self.seconds = 0;
self.timeLabel.text = "0.00"
}
func onTimer() {
self.seconds = self.seconds + 1
self.timeLabel.text = String(format: "%.2f", Double(self.seconds)*interval)
}
}
Запускаем приложение, убеждаемся что оно работает. Теперь создадим программу для Apple Watch, которая будет «пультом ДУ» для нашего секундомера. Там будет 2 кнопки «старт» и «сброс», которые будут активировать соответствующие команды на смартфоне. Этого можно было бы не делать, ограничившись таким же секундомером на часах, но интересно попробовать новый Watch Connectivity framework, предназначенный специально для обмена между часами и телефоном.
Создаем приложение Apple Watch
Открываем interface.storyboard на WatchKit App. Добавляем 2 кнопки и Label для вывода результата. В коде создаем объект класса WCSession, который и отвечает за коннект часов с телефоном. У этого класса есть метод sendCommand, которым мы и воспользуемся. Его параметром является dictionary, в который мы можем поместить различные данные. В нашем случае мы будем передавать обычную текстовую команду. Способ может не самый эффективный, зато простой и наглядный.
Итоговый результат (ресурсы + код) показан на рисунке.
Исходный код этой части
import WatchKit
import Foundation
import WatchConnectivity
class InterfaceController: WKInterfaceController, WCSessionDelegate {
@IBOutlet var statusLabel: WKInterfaceLabel!
private var session : WCSession!
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
}
override func willActivate() {
super.willActivate()
session = WCSession.isSupported() ? WCSession.defaultSession() : nil
session?.delegate = self
session?.activateSession()
}
override func didDeactivate() {
session = nil
super.didDeactivate()
}
@IBAction func onButtonStartStop() {
self.sendCommand("startstop")
}
@IBAction func onButtonReset() {
self.sendCommand("reset")
}
func sendCommand(cmd: String) {
self.statusLabel.setText("Sending...")
if let session = session where session.reachable {
let applicationData = [ "body" : cmd ]
session.sendMessage(applicationData,
replyHandler: { replyData in
self.statusLabel.setText("Send: done")
}, errorHandler: { error in
self.statusLabel.setText("Send: fail")
})
} else {
self.statusLabel.setText("No connection")
}
}
}
Запускаем, нажимаем кнопки… и ничего не происходит. Правильно, мы не добавили обработчик сообщений от часов в основную программу.
Дорабатываем приложение iOS
Добавляем в приложение iOS практически идентичный код для инициализации WCSession. Интерес для нас представляет функция session:didReceiveMessage, которая автоматически вызовется, как только приложение получит сообщение от часов. Параметром этой функции будет тот самый dictionary, который мы передали при отправке. В replyHandler мы можем передать любой ответ, который уже на часах может быть проанализирован. Функция dispatch_async(dispatch_get_main_queue()) нужна для того, чтобы работа с таймером осуществлялась в основном потоке, иначе он не запускается корректно.
Окончательный вариант кода виден на рисунке.
Исходный код полностью
import UIKit
import WatchConnectivity
class ViewController: UIViewController, WCSessionDelegate {
@IBOutlet weak var timeLabel: UILabel!
var seconds: Int!
var timer: NSTimer!
let interval: Double = 1.0/20.0
private var session : WCSession!
override func viewDidLoad() {
super.viewDidLoad()
self.seconds = 0;
// Watch support
initWatchConnection()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
@IBAction func onButtonStart(sender: AnyObject) {
startOrStopTimer()
}
@IBAction func onButtonReset(sender: AnyObject) {
resetTimer()
}
func startOrStopTimer() {
if (self.timer == nil) {
self.timer = NSTimer.scheduledTimerWithTimeInterval(interval, target:self,
selector:"onTimer", userInfo:nil,
repeats:true)
} else {
self.timer.invalidate()
self.timer = nil
}
}
func resetTimer() {
if (self.timer != nil) {
self.timer.invalidate()
self.timer = nil
}
self.seconds = 0;
self.timeLabel.text = "0.00"
}
func onTimer() {
self.seconds = self.seconds + 1
self.timeLabel.text = String(format: "%.2f", Double(self.seconds)*interval)
}
func initWatchConnection() {
if (WCSession.isSupported()) {
session = WCSession.defaultSession()
session?.delegate = self
session?.activateSession()
}
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject],
replyHandler: ([String : AnyObject]) -> Void) {
if let body:String = message["body"] as? String {
if (body == "startstop") {
dispatch_async(dispatch_get_main_queue(),{
self.startOrStopTimer()
})
replyHandler([ "answer" : "OK" ])
}
if (body == "reset") {
dispatch_async(dispatch_get_main_queue(),{
self.resetTimer()
})
replyHandler([ "answer" : "OK" ])
}
}
}
}
Теперь запускаем программу на часах и смартфоне, и все работает — нажатия кнопок на часах корректно запускают таймер, как и хотелось.
Отладка
Среда Xcode имеет в комплекте вполне функциональный симулятор, который выглядит примерно так.
Почему-то среди пар «симулятор-часы» есть только iPhone 6 и iPhone 6 Plus, будут ли часы работать с другими моделями, мне неизвестно. Впрочем Apple Watch у меня все равно нет, как и iPhone 6/6Plus, так что проверить «вживую» увы, не на чем (это интересно, но не настолько, чтобы заплатить 30 тыс.р. за часы).
Размещение в App Store
Эту программу размещать в App Store мы разумеется, не будем. А для тех, кто захочет размещать там свои шедевры, отмечу кратко:
— необходимо добавить дополнительные иконки в Watch Kit App, они будут отображаться на часах
— необходимо добавить новые скриншоты и иконку в App Store
— необходимо добавить новые Apple ID для каждого компонента (App и Extention) и сгенерировать distribution-сертификаты для каждого компонента. Всего таким образом, получается 3 сертификата на программу.
— далее как обычно, Build-Archive, и полученный архив, содержащий все внутри, заливается в App Store.
Как-то так. Если кому-то интересно, можно будет продолжить. Как можно видеть, программирование для watchOS хоть и имеет некоторые особенности, но кардинально не отличается от создания обычной iOS-программы. Автор желает всем удачных экспериментов.
Комментарии (4)
agee
16.10.2015 20:01Спасибо за старания, но общение часов с телефоном и на версии WatchOS 1 было. Поменялся API, но, имхо, обсуждения принципиально новых фич второй ОС мы в статье так и не увидели. Возможно, я что-то упустил?
ketrin7
DmitrySpb79 Очень подробная инструкция, спасибо. Предложу студентам поэкспериментировать
PapaBubaDiop
Инструкция совсем не подробная — студенты впадут в ступор после первой же кнопки Next при создании проекта. Там три чекбокса, каждый из которых требует чтения документации.
ketrin7