WatchKitでWKInterfaceImageを使ってアニメーション

この投稿はWatchKit Advent Calendar 2014の13日目の記事です。

WKInterfaceImageとは

WatchKitで画像を表示するためのクラスです。
単に画像を表示するだけでなく、アニメーションさせるための仕組みもあります。

静的な画像を使用したアニメーション

静的な画像を用いてパラパラ漫画のようにアニメーションさせる場合は、Watch AppのStoryboardとアセットカタログだけで実装できます。

動作イメージ

moonanime
※シミュレータではもうちょっとぬるぬる動きます。

アセットカタログ

アニメーションさせたい画像を下図のように、<画像名><連番(0はじまり)>のImage Setを作成します。

xcassets

※Xode6.1のスクリーンショットです。

Storyboard

WKInterfaceImageをInterface Controllerに配置し、プロパティに以下のように設定します。

Image:<画像名>
※連番は含めないので注意
Animate:Yes
Duration:1コマを表示する時間(秒)
Animate on Load:チェック

これだけでパラパラ漫画のようにアニメーションできます。

再生制御

WKInterfaceImageの以下のメソッドを使って、再生の制御をすることもできます。

動的な画像を使用したアニメーション

WatchKit Extension側でUIImageを動的に生成して、WKInterfaceImageでApple Watchに表示することもできます。

今回は1秒ごとにランダムな色で塗りつぶされたUIImageを生成し、Apple Watchで表示してみました。

動作イメージ

dynamicanime

UIImage作成処理

func generateImage() -> UIImage {
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), false, 0)
    let context = UIGraphicsGetCurrentContext()
    
    func colorValue() -> CGFloat{
        return CGFloat(arc4random_uniform(255)) / 255.0
    }
    
    CGContextSetRGBFillColor(context, colorValue(), colorValue(), colorValue(), 1)
    
    CGContextFillRect(context, CGRectMake(0, 0, 100, 100))
    
    let generatedImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    
    return generatedImage
}

タイマーの設定

var timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "updateImage", userInfo: nil, repeats: true)

func update() {
    // dynamicImageはWatch AppにあるWKInterfaceImageをOutletで参照したもの。
    dynamicImage.setImage(generateImage())
}

これだけ!

まとめ

現状のWatchKitでは、ダイナミックに画面を更新するには、WKInterfaceImageを使う必要がありそうです。
動的にUIImageを作る方法をしっかりとマスターしておいたほうが良いですね。

参考

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


Apple WatchのNotification画面のカスタマイズについて

この投稿はWatchKit Advent Calendar 2014の10日目の記事です。

Custom Notification Interfaces

Apple Watchのnotificationの画面は2種類あります。
詳しくは以下参照。
Apple WatchでのNotification受け取りについて | blog.haranicle.net

Watch Appのターゲットを作るときにNotificationにチェックを入れておくと、Storyboardに以下のようなものが出来上がります。

notification_static_dynamic

Dynamicの方はOptionalなので削除することもできます。
また、削除したあとに、以下の手順で手動で作ることもできます。

  • Interface ControllerをStoryboardを配置し、Staticから配置した Interface ControllerにSegueを伸ばし、”dynamic controller interface”をクリック
  • または、後述のカテゴリ編集画面のチェックボックスで追加することもできます

NotificationはGlanceと異なり、1つのWatch Appで複数持つことができます。

Notificationが届くと、WatchKitはDynamicのInterfaceを表示しようとします。
あなたがDynamicのInterfaceを提供しなかった場合、または何らかの理由でDynamicのInterfaceが利用できない場合は、Staticのinterfaceを表示します。
また、あなたがStaticのInterfaceを表示するように明示することもできます。
DynamicのInterfaceを構成するためには以下のメソッドを呼び出す必要があります。

  • didReceiveRemoteNotification:withCompletion:
  • didReceiveLocalNotification:withCompletion:

これらのメソッドが”WKUserNotificationInterfaceTypeDefault”を返すと、明示的にStaticのInterfaceを表示することができます。

Configuring the Notification Type of a Custom Interface

各NotificationのInterfaceは、Apple Watchにいつ使用するかを教えるために、割り当てられたNotificationタイプを持っている必要があります。
Notificationはカテゴリの値をPayloadに含むことができます。
もし、Notificationがカテゴリの値を含んでいない場合は、デフォルトのNotificationの画面を表示します。

届いたNotificationとInterfaceを対応付けるために、それぞれに同じカテゴリの値を設定する必要があります。
Interfaceのカテゴリの値はStoryboardで設定できます。
Nameの値は一意である必要があります。

notification_type_config

リモートNotificationを生成するときに、Payload内にカテゴリの値を含めます。
カテゴリの値はStoryboardで指定したものと同じである必要があります。

Configuring the Static Notification Interface

StaticなNotificationのInterfaceを作る上でのルールは以下です。

  • すべての画像はWatch Appのバンドルに含めなければいけません。
  • Interfaceに操作できる何か、テーブル、地図を含めてはいけません。
  • InterfaceのnotificationAlertLabel outletはLabelに接続されていなければいけません。そのLabelのテキストはNotificationのメッセージがセットされます。その他のラベルの文字はStoryboardで設定した値しか表示できません。(!?)

StaticなNotificationのLabelと画像は変更できません。(だからStaticなのか!)

notification_design

Configuring a Dynamic Notification Interface

DynamicなNotificationのInterfaceを実装するためにはWKUserNotificationInterfaceControllerのサブクラスを作成する必要があります。

Designing Your Dynamic Interface

WKUserNotificationInterfaceControllerのサブクラスで実行時にLabel、画像、その他のオブジェクトをOutlet経由で変更することができます。
Notificationをタップすると、Watch Appが起動するので、インタラクティブなコントロールを含むべきではありません。

  • 基本的にLabel、画像、グループ、セパレータを使用してください。
  • テーブルや地図を含むこともできます。
  • ボタンやスイッチ、その他のインタラクティブなコントロールを含めないでください。

Configuring Your Dynamic Interface at Runtime

初期化後、WatchKitはあなたのNotificationInterfaceControllerの以下のメソッドにPayloadのデータを渡します。
このメソッド内で渡されたデータに対応するようにInterfaceを書き換えます。

  • Remote Notificationの場合 didReceiveRemoteNotification:withCompletion:
  • Local Notificationの場合 didReceiveLocalNotification:withCompletion:

このメソッドでInterfaceの書き換えがおわったら、WatchKitにInterfaceの準備が完了したことを伝えるために、Completionハンドラブロックを実行する必要があります。

notification_event_cycle_2x

didReceiveRemoteNotification:withCompletion:の実装例です。

- (void)didReceiveRemoteNotification:(NSDictionary *)remoteNotification withCompletion:(void(^)(WKUserNotificationInterfaceType interface)) completionHandler {
    // Get the aps dictionary from the payload.
    NSDictionary* apsDict = [remoteNotification objectForKey:apsKeyString];
    // Retrieve the title of the invitation.
    NSString* titleString = [apsDict objectForKey:titleKeyString];
    [self.titleLabel setText:titleString];
    // Extract the date and time from the custom section of the payload.
    // The date/time information is stored as the number of seconds since 1970.
    NSDictionary* customDataDict = [remoteNotification objectForKey:customDataKey];
    NSNumber* dateValue = [customDataDict objectForKey:invitationDateKey];
    NSDate* inviteDate = [NSDate dateWithTimeIntervalSince1970:[dateValue doubleValue]];
    // Format the date and time strings.
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    // Call a custom method to get the localized date format string for the user.
    // The default date format string is "EEE, MMM d".
    dateFormatter.dateFormat = [self dateFormatForCurrentUser];
    NSString *formattedDateString = [dateFormatter stringFromDate:inviteDate];
    // Call a custom method to get the localized time format string for the user.
    // The default time format string is "h:mm a".
    dateFormatter.dateFormat = [self timeFormatForCurrentUser];
    NSString *formattedTimeString = [dateFormatter stringFromDate:inviteDate];
    // Set the date and time in the corresponding labels.
    [self.dateLabel setText:formattedDateString];
    [self.timeLabel setText:formattedTimeString];
    // Set the location of the meeting.
    NSString* locationString = [customDataDict objectForKey:invitationLocationKey];
    [self.locationLabel setText:locationString];
    // Set the invitation's notes (if any).
    NSString* notesString = [customDataDict objectForKey:invitationNotesKey];
    [self.notesLabel setText:notesString];
    // Tell WatchKit to display the custom interface.
    completionHandler(WKUserNotificationInterfaceTypeCustom);
}

Testing Your Custom Interface

シミュレータでDynamicなNotificationのInterfaceをテストするには、ビルドスキームを作成します。
そして、Xcodeのテンプレートで提供されたRemoteNotificationPayload.jsonをPayloadのデータとして使用します。
詳細については以下を参照してください。
WatchKit Programming Guide: Configuring Your Xcode Project

参考

WatchKit Programming Guide: Custom Notification Interfaces

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


Apple WatchでのNotification受け取りについて

この投稿はWatchKit Advent Calendar 2014の9日目の記事です。

Notificationを受け取ったら

あなたのiOSアプリがLocal NotificationかRemote Notificationをサポートしていたら、Apple WatchもNotificationを表示します。
もし、ユーザのiPhoneにLocal NotificationかRemote Notificationが届いたら、iOSはiPhoneかApple WatchのどちらにNotificationを表示するかを決定します。
Apple WatchにNotificationが送られた場合、システムはユーザにNotificationが来たことを微かに知らせます。
(たぶん、バイブレーション)
もし、ユーザがNotificationを見ることを選んだら(たぶん、腕をあげたら)、システムはShort-Look Interfaceを表示します。
もし、ユーザがNotificationを見続けたら(しばらく腕を上げたままだったら??) ※、システムはLong-Look Interfaceを表示します。
Watch AppはNotificationをカスタマイズしていなければ、システムがデフォルトのNotification画面を用意してくれます。

※ Short-Look InterfaceをタップするとLong-Look Interfaceが表示できるようです。
(@koogawaさん情報有り難うございます!)

Short-Look Interface

notification_short_look

通知が来た時に、ユーザが最初に見る画面です。
Short-Look Interfaceはスクロールすることはできません。
デフォルトでは、アプリ名、アイコン、Notificationのpayloadに含まれるタイトルを表示します。

Long-Look Interface

notification_long_look

Long-Look InterfaceはNotificationの内容と関連するアクションボタンを表示するスクロール可能な画面です。
デフォルトでは、Apple Watchがアイコン、タイトル、メッセージを含む画面を表示します。

Long-Look Interfaceの3つの領域

sash

アプリアイコンとアプリ名が表示されます。
デフォルトでは、一部透明ですが色をカスタマイズすることもできます。

content area

Notificationの詳細な情報を表示します。
このエリアはある程度自由にカスタマイズ可能です。

bottom area

Dismissボタンとアプリで設定したいくつかのアクションボタンを表示します。
Dismissボタンはシステムが自動的に常に表示してくれます。

アクションボタンを追加する方法

まず、任意のアクション(UIMutableUserNotificationAction)を作成し、
カテゴリ(UIMutableUserNotificationCategory)の中に複数のアクションをセットします。
NSMutableSetにすべてのカテゴリをセットして、Notificationの設定(UIUserNotificationSettings)
にNSMutableSetとNotificationの属性(バッジや音の有無など)をセットします。
最後に[UIApplication sharedApplication]にNotificationの設定を登録します。

- (void)registerSettingsAndCategories {
    // Create a mutable set to store the category definitions.
    NSMutableSet* categories = [NSMutableSet set];
    // Define the actions for a meeting invite notification.
    UIMutableUserNotificationAction* acceptAction = [[UIMutableUserNotificationAction alloc] init];
    acceptAction.title = NSLocalizedString(@"Accept", @"Accept invitation");
    acceptAction.identifier = @"accept";
    acceptAction.activationMode = UIUserNotificationActivationModeBackground;
    acceptAction.authenticationRequired = NO;
    UIMutableUserNotificationAction* declineAction = [[UIMutableUserNotificationAction alloc] init];
    declineAction.title = NSLocalizedString(@"Decline", @"Decline invitation");
    declineAction.identifier = @"decline";
    declineAction.activationMode = UIUserNotificationActivationModeBackground;
    declineAction.authenticationRequired = NO;
    // Create the category object and add it to the set.
    UIMutableUserNotificationCategory* inviteCategory = [[UIMutableUserNotificationCategory alloc] init];
    [inviteCategory setActions:@[acceptAction, declineAction]
                    forContext:UIUserNotificationActionContextDefault];
    inviteCategory.identifier = @"invitation";
    [categories addObject:inviteCategory];
    // Configure other actions and categories and add them to the set...
    UIUserNotificationSettings* settings = [UIUserNotificationSettings settingsForTypes:
                                            (UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound)
                                                                             categories:categories];
    [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
}

アクションボタンをタップすると

システムはアクションを処理する方法を決定するために、登録されたUIUserNotificationActionオブジェクトを使用します。
アクションはフォアグラウンドまたはバックグラウンドで処理できます。

フォアグラウンドで処理する場合

あなたのWatch Appを起動し、メインのInterface ControllerにタップされたアクションボタンのIDを受け渡します。
WKInterfaceControllerのサブクラス(メインのInterface Controller)では、アクションを処理してはいけません。
WKInterfaceControllerのサブクラスでは、以下のメソッドを実装する必要があります。

  • handleActionWithIdentifier:forRemoteNotification:
  • handleActionWithIdentifier:forLocalNotification: methods

バックグラウンドで処理する場合

iOSアプリをバックグラウンドで起動し、以下のどちらかのメソッドで処理をします。

  • application:handleActionWithIdentifier:forRemoteNotification:completionHandler:
  • application:handleActionWithIdentifier:forLocalNotification:completionHandler:

参考

[UIApplication sharedApplication]

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


Apple Watchで初めて登場した新UI「Glance」について

この投稿はWatchKit Advent Calendar 2014の8日目の記事です。

Glanceとは

glance_lister_2x

GlanceはユーザがあなたのWatch Appから重要な情報を表示するための補助的な機能です。
(つまり、実装しなくてもいいです。)
iOSでいう、通知センターのようなものだと思っています。
1つのWatch Appにつき、最大1つのGlanceを持つことができます。
GlanceはWKInterfaceControllerオブジェクトですが、ユーザの操作を受け付けません。
タップすると、あなたのWatch Appを起動します。
Handoffで起動するWKInterfaceControllerを設定することもできます。
(詳細は以下を参照)
WatchKitでHandoff | blog.haranicle.net

Youtubeに動画がありました。
ちょうど2分ぐらいからGlanceの挙動を見ることができます。

Glanceインタフェースガイドライン

  • すぐに情報を伝えられるように設計します。大量の文字を表示してはいけません。グラフィックス、色、アニメーションを適切に使用してください。
  • 最も重要な情報に焦点を当ててください。Glanceはアプリに変わるものではありません。重要な情報のみをトリミングして表示してください。
  • インタラクティブなコントロールを含めないでください。(含めても操作できないので)
  • テーブルとマップの使用は避けたほうがいいようです。禁止はされていませんが、限られたスペースはテーブル、マップは有用ではありません。
  • 表示する情報をタイムリーにしてください。ユーザに情報を提供するために、時刻や位置情報など利用できるすべてのリソースを使用してください。

Glanceの作り方

2通りあります。

Watch Appのターゲット作成時に自動生成

まだプロジェクトにWatch Appのターゲットがない場合はこの手順でGlanceを作成できます。こっちのほうがおすすめ。

File > New > Target… >
Watch Appを選択してNext >
Choose options for your new target:でInclude Glance Sceneにチェック >
Finish

これで、以下のものが作成されます。

  • WatchKit Extensionグループ内にGlanceController.swift
  • Watch Appグループ内のStoryboard内にGlance Interface

手動でGlance作成

すでにプロジェクトにWatch Appのターゲットにある場合はこの手順でGlanceを作成できます。

  1. Watch Appのグループを右クリック > New File… > Cococa Classを選択してNext > Class: 適当なクラス名、Subclass of:WKInterfaceControllerにしてNext > TargetsでWatchKit ExtensionのみにチェックしてCreate
  2. Watch Appのグループ内にあるStoryboardにGlance Interface Controllerを追加します。
  3. StoryboardにGlance Interface Controllerと 1. で作成したクラスのファイルを紐付け

iOSシミュレータでのGlanceの起動方法

Glance起動用のSchemeを作成

簡単にGlanceを起動できるようにするために、Schemeを作成します。
Product > Scheme > Edit Scheme… >
左上で”プロジェクト名 Watch App”のSchemeを選択 >
Duplicate SchemeボタンでSchemeを複製 >
“Copy of プロジェクト名 Watch App” SchemeのRunを選択 >
Info > Executableで”Glance – プロジェクト名 Watch App.app”を選択 >
Close

Run

“Copy of プロジェクト名 Watch App” Schemeを選択してRunボタンを押すと、
Glanceが表示されます。
Scheme名は、Product > Scheme > Manage Schemes… で変更できます。

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


Apple Watch Appのライフサイクルまとめ

この投稿はWatchKit Advent Calendar 2014の7日目の記事です。

前提知識

Watch Appのアーキテクチャ

Apple Watch側にStoryboardやリソースファイルを置いて、iPhoneのWatchKit Extension側で処理を行います。
UIの更新などApple WatchとiPhone間の通信はWatchKitが行います。

app_communication_2x
(via WatchKit Programming Guide: Watch App Architecture)

Watch Appのライフサイクル

ユーザがApple WatchであなたのWatch Appを起動すると、自動的にiPhone側のWatchKit Extensionを起動します。
ユーザの操作が終わると、iOSはWatchKit Extensionの実行を中断します。
つまり、WatchKit Extensionは、ユーザがApple WatchであなたのWatch Appを操作中の時のみ実行できます。

launch_cycle_2x
(via WatchKit Programming Guide: Watch App Architecture)

WKInterfaceControllerでのライフサイクルハンドリング

下記図のとおりです。

watch_app_lifecycle_simple_2x
(via WatchKit Programming Guide: Watch App Architecture)

initWithContext:

WKInterfaceControllerが作成されて、最初に呼ばれるメソッドです。
データのロードや、ラベル、画像、テーブルなどのオブジェクトを更新するために使用します。
contextでHandoffや他の画面から値を受け取ることができます。

willActivate

ユーザにUIが表示されたタイミングで呼ばれるメソッドです。
UIの更新や、UIがアクティブなときだけ発生するようなタスクの実行に使用します。
例えば、タイマーのセットや、アニメーションの開始、ビデオのストリーミング開始(ぇ、そんなのできたっけ?)などです。

didDeactivate

ユーザがApple Watchであなたのアプリを操作することをやめると呼ばれるメソッドです。
クリーンアップの処理を行います。
例えば、タイマーの停止や、アニメーションの停止、ビデオのストリーミング停止などです。
次にwillActivateが呼ばれるまでの間は、UIに値をセットしても無視されます。

複数画面ある場合は?

実験してみました。
First、Second、Thirdという名前のWKInterfaceControllerを作成し、
First->Second->Third->Second->Firstの順に画面遷移し、ライフサイクルハンドリングするメソッドでログを吐いてみました。

以下ログ。

FirstのinitWithContext
FirstのwillActive
SecondをPush
SecondのinitWithContext
SecondのwillActivate
FirstのdidDeactivate
ThirdをPush
ThirdのinitWithContext
ThirdのwillActivate
SecondのdidDeactivate
ThirdをPop
SecondのwillActivate
ThirdのdidDeactivate
SecondをPop
FirstのwillActivate
SecondのdidDeactivate

次の画面がwillActivateになったら、自身がdidDeactivateになるようです。

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


WKInterfaceDeviceクラスの全プロパティ・メソッド解説

この投稿はWatchKit Advent Calendar 2014の6日目の記事です。

WKInterfaceDevice

Apple Watchのデバイス情報を取得するためのクラスです。
以下すべてのメソッドとプロパティの解説です。

Getting the Shared Device Object

  • currentDevice
    デバイスオブジェクトを取得します。

Getting the Screen Information

  • screenBounds
    画面のサイズのCGRectが取得できます。

  • screenScale
    画面のスケールが取得できます。Apple Watchは2.0です。

Getting the Device Settings

  • currentLocale
    ロケールの設定をNSLocaleで取得できます。
    NSLocaleについてはこちら参照。
    NSLocale Class Reference

  • preferredContentSizeCategory
    ユーザはOSの設定でアプリに表示するアプリのサイズ(大きめ、小さめ)を要求できます。
    システムがreturnしたフォントオブジェクトはこの設定に応じて自動的にリサイズされます。
    コードからフォントオブジェクトを要求する場合、適切なサイズのフォントオブジェクトを要求するために、このプロパティの値を使用します。
    このプロパティの値については以下を参照してください。
    Content Size Category Constants – UIApplication Class Reference

Managing Image Caches on the Device

画像をキャッシュできるようです。
@tidさんの詳しい解説があるので省略します。
Swift – WatchKitでWatchに画像をキャッシュする – Qiita

まとめ

シンプル。必要最低限という感じですね…

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


WKInterfaceObjectクラスの全プロパティ・メソッド解説

この投稿はWatchKit Advent Calendar 2014の5日目の記事です。

WKInterfaceObjectとは

WatchKitに含まれるすべてのUIパーツのベースとなるクラスです。
つまり、WKInterfaceObjectを理解すれば、WatchKitのUIパーツすべて理解できます。たぶん。

WKInterfaceObjectはなんと、継承したり、コードでインスタンスを作ったりはできません。
すべてのUIパーツはStoryboardで作成する必要があります。
コードからUIパーツを弄りたい場合は、以下のようにしてStoryboardから参照します。

@property (weak, nonatomic) IBOutlet WKInterfaceButton* myButton;

詳しくはリファレンスを参照してください。

Hiding and Showing the Object

  • setHidden:
    iOSのUIViewと同じです。インスタンスを作ることはできないけど、隠すことはできるみたいです。

  • setAlpha:
    アルファを0.0から1.0の間で指定できます。0.0だと完全に透明で見えなくなります。

Getting the Property Name

  • interfaceProperty
    Storyboard上のオブジェクトとコードで扱うオブジェクトを対応させるためにWatchKitが内部的に使用する値のようです。
    開発者がこの値を直接使用する必要はないそうです。

Changing an Object’s Size

  • setWidth:
    Widthを指定します。
    0.0をセットすると、無視されてStoryboardの値が使用されます。

  • setHeight:
    Heightを指定します。
    0.0をセットすると、無視されてStoryboardの値が使用されます。

Configuring the Accessibility Attributes

  • setAccessibilityLabel:
    オブジェクトの簡潔な説明をセットします。

  • setAccessibilityHint:
    ユーザがオブジェクトを操作した時に何が起こるかの説明をセットします。

  • setAccessibilityLabel:
    オブジェクトの値をセットします。(スライダーやスイッチで使用する?)

そもそもAccessibilityとは

iOSの設定でアクセシビリティという項目がありますが、
VoiceOverやAssistiveTouchなど障害者の方向けの機能(?)などが
含まれています。

下記ページに詳しく書かれています。

iOSアプリ開発とアクセシビリティ対応について | Technology-Gym

Accessibilityに値をセットするとどんな挙動になるの?

試しにSliderを作って、適当な値をセットし、アクセシビリティインスペクタで確認してみましたが何も起きませんでした。
Apple Watchにはどのようなアクセシビリティの機能があるんでしょうか…

まとめ

以上、WKInterfaceObjectの全メソッド解説でした。
全体的にsetterしかないんですね。
Accessibilityがどんな挙動になるんでしょうか。楽しみです。

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


WatchKitでpush画面遷移

この投稿はWatchKit Advent Calendar 2014の4日目の記事です。

Apple WatchでのPush画面遷移

Apple Watchでは以下の3種類の画面遷移があるようです。

  • push
  • modal
  • page (正式名称はわかりませんがページをめくるような遷移)

今回はpushのお話です。

リファレンス

今回もリファレンスを確認してWKInterfaceControllerに怪しいAPIがありました。

WKInterfaceController

WKInterfaceController Class Reference

以下の3つのAPIが関係ありそうです。
* pushControllerWithName:context:
* popController
* popToRootController

実装

Storyboard

以下のようにします。
storyboard

緑の四角形はボタンです。

コード

FirstInterfaceController.swift

// pushSecondボタン押下時の処理
@IBAction func onPushSecondButtonPushed() {
    pushControllerWithName("secondInterfaceControllerId", context: "gotoSecond!!")
}

引数nameには遷移先のInterfaceControllerのIdentifierを指定します。
IdentifierはStoryboardで設定します。
contextには遷移先のInterfaceControllerに受渡したい値を指定します。
contextはAnyObject!なのでなんでも受け渡せそうです。
contextはnilを指定することもできますが、おすすめはしません。(…とリファレンスに書いてありますが理由はよくわかりません。)

WatchKitにはiOSでいうNavigationControllerのようなものはありません。
pushControllerWithName:context:を実行するだけでpush画面遷移できます。iOSに慣れているとなんだか不思議。

SecondInterfaceController.swift

import WatchKit
import Foundation

class SecondInterfaceController: WKInterfaceController {
    
    // 初期化処理
    override init(context: AnyObject?) {
        super.init(context: context)
        NSLog("%@ init context:%@", self, context as String)
    }
    
    // pushThirdボタン押下時の処理
    @IBAction func onPushThirdButtonPushed() {
        pushControllerWithName("thirdInterfaceControllerId", context: "gotoThird!!")
    }
    
    // popボタン押下時の処理
    @IBAction func onPopButtonPushed() {
        popController()
    }
}

init実行のタイミング、つまりFirstInterfaceControllerのpushSecondボタン押下時に以下の様なログが表示されます。

2014-12-03 23:52:43.805 WatchKitSample WatchKit Extension[12014:2877844] <WatchKitSample_WatchKit_Extension.SecondInterfaceController: 0x7fe99c200080> init context:gotoSecond!!

contextで値を受け渡せていることがわかりますね。

popController()を実行すると前の画面に戻ることができます。
ただ、popで画面遷移すると、iOSのNavigationControllerのように自動的に左上に戻るボタンが表示されます。
戻るボタンは自分で実装する必要はなさそうです。

ThirdInterfaceController.swift

import WatchKit
import Foundation

class ThirdInterfaceController: WKInterfaceController {
    
    override init(context: AnyObject?) {
        super.init(context: context)
        NSLog("%@ init context:%@", self, context as String)
    }
    
    @IBAction func onPopButtonPushed() {
        popController()
    }
    
    @IBAction func onPopToRootButtonPushed() {
        popToRootController()
    }
}

popToRootController()を実行すると、FirstInterfaceControllerに戻れます。
一気に戻れるので便利ですね。

まとめ

以上、push画面遷移の解説でした。とっても普通。

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


WatchKitで文字入力UIのTextInputControllerを表示する

この投稿はWatchKit Advent Calendar 2014の3日目の記事です。

Apple Watchで文字入力できるの?

Apple Watchを紹介しますの動画の2:19あたりにこんな映像があります。

textinputcontroller

このUIで以下の方法で文字入力出来そうです.
* リストから文字列を選ぶ
* Smart Replies (なんだこれ)
* 音声入力

リファレンス

今回もリファレンスを確認してWKInterfaceControllerに怪しいAPIがありました。

WKInterfaceController

WKInterfaceController Class Reference

以下の2つのAPIが関係ありそうです。

メソッド名がいかにも..

実装

早速実装してみました。
StoryboardでWKInterfaceControllerの画面にボタンを2つ配置し、
ボタン押下時の処理を以下のようにしました。
これで、片方のボタン押下時には文字入力のUIが表示されて、
もう片方のボタン押下時には文字入力のUIが閉じるはず!

@IBAction func onOpenButtonPushed() {
    presentTextInputControllerWithSuggestions(["aaa", "bbb", "ccc"], completion: {(str) -> Void in println(str)} )
}

@IBAction func onCloseButtonPushed() {
    dismissTextInputController()
}

実行結果

ボタンを押しても何も起きませんでした。

たすけてStack Overflow

The presentTextInputControllerWithSuggestions:completion: method of WKInterfaceController is not currently supported in iOS Simulator.
swift – Text Input Controller WatchKit – Stack Overflow

…はい。

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


WatchKitでHandoff

この投稿はWatchKit Advent Calendar 2014の2日目の記事です。

前提知識

Watch Appの画面

以下の3種類があります。
WatchKitのページの中央あたりに写真が載っています。

WatchKit Apps

アプリの画面です。WKInterfaceControllerクラスを継承して作成します。

Glances

read-onlyな情報を提示する画面です。WKInterfaceControllerクラスを継承して作成します。

Actionable Notifications

通知画面です。WKUserNotificationInterfaceControllerクラスを継承して作成します。WKUserNotificationInterfaceControllerクラスはWKInterfaceControllerクラスを継承しています。

Handoffとは

HandoffはiOS8およびOSXv10.10で新たに導入された機能で、同じユーザに結びつけられた複数のデバイス間で、操作内容(アクティビティ)を引き継ぐことができます。
(via Handoffプログラミングガイド)

WatchKitでHandoff

そもそも、そんなことできるの?

WatchKit – Apple Developerの動画の25:20あたりでUse Handoff APIと書かれているのできっと使えるはず!

今すぐためせる?

後述しますが、一部シミュレータで試せます。

リファレンスを読み解く

リファレンスを確認すると、WKInterfaceControllerにそれっぽいAPIがありました。
(長いのでとりあえず動かしたい人は、”実装してみる”まで読み飛ばしてOKです。)

WKInterfaceController

WKInterfaceController Class Reference

以下の2つのAPIがHandoffに関係ありそうです。

actionForUserActivity:context:

リファレンス

リファレンス日本語訳

Return Value
  • ルートのInterfaceControllerのかわりに表示する画面のInterfaceControllerのIdentifier。
  • nilを返すとルートのInterfaceControllerが表示される。
Discussion
  • このメソッドを実装して、Handoffからアプリが起動された時に、どのInterfaceControllerを表示するかをWatchKitに伝えるために使用します。
  • Glanceをタップすると、Glanceに対応するアプリが起動します。
  • 通常であれば、ルートのInterfaceControllerが表示されます。
  • しかし、GlanceでupdateUserActivity:userInfo:メソッドを呼び出すと、システムが起動サイクルの早期に他のInterfaceControllerのIdentifier※を返すチャンスをくれます。
  • もし、Glanceが有効なIdentifierを返したら、システムはルートの画面の代わりにIdentifierに対応するInterfaceControllerを表示します。
  • このメソッドのデフォルト実装は何もしません。このメソッドをオーバーライドするとき、superを呼び出してはいけません。

※ IdentifierはStoryboardで設定します。

解説

GlanceでupdateUserActivity:userInfo:メソッドを実行すると、アプリ起動時に表示する画面を切り替えられるようになります。
userInfoに好きな情報を入れられるので、アプリ起動時に任意の画面でuserInfoの情報を表示することができそうです。

updateUserActivity:userInfo:

リファレンス

リファレンス日本語訳

Discussion
  • Glanceでは、このメソッドを呼び出して、表示している内容についての情報をuserInfoとして提供してください。ユーザがGlanceをタップするとアプリにコンテキスト情報が送信されます。
  • このメソッドを呼び出すと、アプリにアクティビティを送信します。システムがiPhoneに情報を配信し、iPhoneが他のデバイスに情報を伝播します。
  • InterfaceControllerのコード実行中はいつでもこのメソッドを呼び出すことができる。

解説

メソッド名の通り、アクティビティの更新ができるようです。

実装してみる

前置きが長くなりましたが、いよいよWatchKitでHandoffを使うアプリを実装してみましょう。
リファレンスを読み解いた結果、2種類のパターンがあることがわかりました。

Storyboard

以下のようになってるとします。(XcodeのスクリーンショットはNDAなので…)
storyboard

[パターン1] iPhoneのアクティビティをAppleWatchで継続する場合

Glanceからアプリを起動します。
このパターンはシミュレータで動くことを確認できます。

処理の流れ

以下の様な流れになります。

p1

  1. GlanceでupdateUserActivity:userInfo:を実行する。
  2. ユーザがGlanceをタップする。
  3. Firstに実装したactionForUserActivity:context:がアプリ起動時に表示したいInterfaceControllerのIdentifier(つまりSecondのIdentifier)を返す。
  4. システムがIdentifierに対応するInterfaceController(つまりSecond)を表示する。

GranceController.swift

どこかでupdateUserActivity:userInfo:を実行します。
今回は、initで実行しました。

override init(context: AnyObject?) {
    super.init(context: context)
    NSLog("%@ init", self)    
    updateUserActivity("net.haranicle.watchHandoff.sample", userInfo: ["hoge":"piyo"])
}

FirstInterfaceController.swift

actionForUserActivity:context:を実装して、SecondのIdentifierをreturnします。

override func actionForUserActivity(userActivity: [NSObject : AnyObject]?, context: AutoreleasingUnsafeMutablePointer<AnyObject?>) -> String? {
    NSLog("%@ actionForUserActivity", self)
    
    return "secondInterfaceControllerId"
}

これだけでGlanceをタップすると自動的にSecondが表示されるようになりました!
(一瞬Firstが表示されます。)

動かないときは…

iOSシミュレータでiCloudにログインしてみてください。
Glanceの起動方法がわからない場合はググって!(NDAこわい)

[パターン2] AppleWatchのアクティビティをiPhoneで継続する場合

先に断っておきますが、このパターンは動きませんでした。
WatchKitのバグなのか、僕のコードが悪いのかわかりませんが。
以下は予想でしかないです。間違っていたら指摘してください。

処理の流れ

p2

  1. First実行中の任意のタイミング(ボタン押下時にした)でupdateUserActivity:userInfo:を実行する。
  2. システムがiPhoneに情報(アクティビティ)を配信し、iPhoneが他のデバイスに情報を伝播します。
  3. ユーザがiOSデバイスのロック画面のHandoffからiOSアプリを起動します。
  4. HandoffでiOSアプリ起動時にで継続の処理(application:willContinueUserActivityWithType)が実行されます。

FirstInterfaceController.swift

ボタン押下時にupdateUserActivity:userInfo:を実行しましたが、ボタン押下時にWatch画面に回るポンデリングが表示されっぱなしになりました。
なにか間違ってるんでしょうか..? このパターンの検証は諦めました。

@IBAction func onButtonPushed() {
    updateUserActivity("net.haranicle.watchHandoff.buttonPushed", userInfo: ["hoge":"piyo"])
}

まとめ

今回は[パターン1]iPhoneのアクティビティをAppleWatchで継続する場合はWatchKitでもHandoffが動くことがわかりました。
早く実機でためしたい!!

参考

Getting started with Handoff

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード