カテゴリー別アーカイブ: Xcode

Swiftの確認事項もろもろ(2)

引き続きガリガリやってます。ブラウザのタブも一杯になってきたので、第2弾です。

・自分用のアプリでは、関数作ってたけどその時間もないので。備忘録的に。
RGBの16進数表記をUIColorのコードに変換してくれるサービス。 » ブロードヒューマンネットワーク社員ブログ

・カンタンお手軽。速度的にはどうなんすかね?
【Swift】配列内で重複する要素を除去する – Qiita

・ソートです。明示的にソートしないと、Controller間で要素順が変わってしまうと事象があったので。
【Swift】配列をソートする方法(昇順・降順・逆順) | Fussan Blog

・UITextViewでのHTML表示関連
UITextViewにHTMLを表示する(Swift3版) – Qiita
[Swift] URLエンコード URLデコード: スタジオプリズム㐧3ブログ
Decode HTML string in Swift 3 – Stack Overflow

・String操作いろいろ
[Swift3] Stringのプロパティ/メソッドのまとめ (文字列操作) – Qiita

・正規表現など
Swiftで正規表現を使って文字列を置換する – Qiita

・配列周りなど
Swiftの配列操作(map,reduce,filter) – Qiita

・extensionってなんすか?
エクステンション | Swift言語を学ぶ

・こんなことできるんですね。タブなんかを外側に表示するのに便利。
UITableViewCellの領域外にUIをはみ出させたい – Qiita

・for in もおさらい。
Swift さくっと確認したい基礎文法 [いろんなループ処理] – Qiita

・特殊文字の置換
Unescape HTML special characters of String in Swift

・TableCellの表示位置チェック
【iOS】特定のセルがUITableViewの表示領域内に収まっているかどうかを調べる

・Constraintは初めていじりました。
NSLayoutConstraintをプログラムでシンプルに書く – Qiita

Swiftの確認事項もろもろ(1)

急遽、Swiftを実戦投入(要は仕事)することになったので、諸々メモです。

・Calendarである日付を起点にいろいろ操作できるのは便利。
Swift3での日時に関する処理 – Qiita

・mapに出てくる「$0」の意味がわからなかった(笑)
特有の関数? mapとは?$0とは? – “まだ”の力 [Swift]基礎辞書

・演算子も改めて再確認。
【Swift】演算子の使い方。論理演算、比較演算、代入演算、ビット演算 | はじはじアプリ体験記

・私の場合も「nilなのは値じゃなくて変数だった」でした。
明らかにnilじゃないのに「unexpectedly found nil〜」が出る

・この辺もObjective-Cにはないので、混乱しがち。
[Swift] Optional 型についてのまとめ Ver2 – Qiita

・DateFormatterの使い方もろもろ
【Swift】Dateの王道 【日付】 – Qiita
日付操作の基本 – Swiftをはじめよう!

【Xcode】各種ユーザーデータを初期処理する際の注意

【macOS Sierra 10.12.6+Xcode 8.3.3+iPhoneSE(iOS 10.3.2)】

とあるアプリで、カメラロールの写真とミュージックのアートワークを使ってたんですが、何故かインストール直後にどちらも0件になってしまう事象が発生。2回目以降に起動すると使えるのに・・・。

と思って調べたら、初期処理の実行タイミングに問題がありました。

※ここでいう「初期処理」とは、各種ライブラリをこのアプリ使うための実装処理を指します。

iOS10以降は各種ユーザーデータへアクセスに許可が必要になりました。で、以前書いたようにinfo.plistにメッセージ用テキストが必須になったわけですが、これで使用許可を求めるダイアログを表示してくれますが、該当処理はダイアログによる許可を待ってくれるわけではなく、許可されないと(ダイアログのボタンを押さないと)無視されてしまうようです。

で、viewDidLoad に書いてあった写真ライブラリとメディアライブラリの初期処理が、インストール直後でも正しく実行されるように、以下のように修正しました。

    //写真ライブラリ:初回許可(未設定の場合にダイアログの選択結果を受け取って、初期処理を行う)
    PHAuthorizationStatus status_photo = [PHPhotoLibrary authorizationStatus];
    if (status_photo == PHAuthorizationStatusNotDetermined){
        // このアプリに与える権限が未選択(アプリ初回起動時)
        NSLog(@"---PHPhotoLibrary:未選択");
        
        [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
            if (status == PHAuthorizationStatusAuthorized) {
                // Access has been granted.
                NSLog(@"-----写真ライブラリ:使用OK");
                [self initIconList_photo];
            }
            else {
                // Access has been denied.
            }
        }];
        
    } else if (status_photo == PHAuthorizationStatusAuthorized){
        NSLog(@"-----写真ライブラリ:使用OK済み");
        [self initIconList_photo];
        
    }
    
    //メディアライブラリ設定:初回許可(未設定の場合にダイアログの選択結果を受け取って、初期処理を行う)
    MPMediaLibraryAuthorizationStatus status_media = [MPMediaLibrary authorizationStatus];
    if (status_media == MPMediaLibraryAuthorizationStatusNotDetermined){
        // このアプリに与える権限が未選択(アプリ初回起動時)
        NSLog(@"---MPMediaLibrary:未選択");
        
        [MPMediaLibrary requestAuthorization:^(MPMediaLibraryAuthorizationStatus status) {
            if (status == MPMediaLibraryAuthorizationStatusAuthorized) {
                // Access has been granted.
                NSLog(@"-----メディアライブラリ:使用OK");
                [self initIconList_artwork];
            }
            else {
                // Access has been denied.
            }
        }];
        
    } else if (status_media == MPMediaLibraryAuthorizationStatusAuthorized){
        NSLog(@"-----メディアライブラリ:使用OK済み");
        [self initIconList_artwork];
    }

要は、requestAuthorization でそれぞれのライブラリの使用許可ダイアログの結果を受け取って、OK(写真なら PHAuthorizationStatusAuthorized)だった場合に、初期処理(写真の場合は、initIconList_photo )を実行するということです。

ちなみに、else if で書かれているのは、既に許可済みの場合(写真だと PHAuthorizationStatusAuthorized)の場合に、初期処理を実行するためです(これがないと、後から許可した場合に初期処理が実行されない)。

各種ライブラリへのアクセス許可のダイアログは、info.plistにメッセージ用テキストを必須条件として、プログラム中で初めて該当ライブラリにアクセスしたタイミングで表示されるので、写真ライブラリとメディアライブラリのチェック順を入れ替えれば、ダイアログを出す順番も制御できます。また、特に初期処理が要らないライブラリなら、ユーザーがボタンを押したタイミングで許可を求めるダイアログを表示することもできそうです。

【参考】
iOSでカメラと写真の利用許可の確認方法[AVCaptureDevice] – Qiita
【Xcode】iOS10以降は各種ユーザーデータへアクセスに許可が必要 | AS blind side
【Xcode】iOS10で各種ユーザーデータへのアクセス許可を参照 | AS blind side
requestAuthorization: – MPMediaLibrary | Apple Developer Documentation

【Xcode】効果音と多重再生とSFSpeechRecognizer(音声認識)

作成中のアプリで使おうと思って調べたメモです。

サウンドの再生方法


調べてみると、下記の2つの方法があって、それぞれ用途によって総称されているフレームワークが違っている。

・AudioToolBoxを使う方法(効果音等短い音)
・AVFoundationを使う方法(BGM等長い音やループ)

【参考】
Objective-C:音楽(BGM)や効果音(SE)等のサウンド再生方法 | siro:chro
アプリ開発ブログ(仮): 効果音を鳴らす
【Xcode】アプリ内のボタンに効果音を付ける! | iDEACLOUD/dev

BGMと効果音を併用する際の問題点


効果音はAudioToolBox、BGMはAVFoundationで鳴らせばいいのかと思って、効果音をAudioToolBoxで組み込んで、テスト中にミュージックアプリで音楽流しながら、効果音を鳴らしたら音楽がフェードアウトしてしまった。で、調べてみたら、どうもAudioToolBoxとAVFoundationの併用に問題があるらしいことが発覚。

以下のリンクによれば、AudioToolBoxで音を鳴らすと、AVFoundationの方はフェードアウトしてしまうらしい。
なので、効果音もAVFoundationで再生することにする。

【参考】
BGMを鳴らしながら効果音再生するときの落とし穴 – iOSアプリ開発まとめWiki – アットウィキ

多重再生するための設定


更に調べてみると、多重再生させるには、以下の2つが必要とのこと。

・音声ファイルを .caf形式にする
・AVAudioSessionのカテゴリを変更する

.cafとは、Core Audio File の拡張子で、iOSおよびOS Xのネイティブのオーディオファイルフォーマットらしいです。

ターミナルから以下のコマンドで変換できます(例:sound_ok.m4a を sound_ok.caf に変換)

afconvert -f caff -d 0 sound_ok.m4a sound_ok.caf

※変換したところ、ファイルサイズは【107KB】>>【54KB】になりました。

また、AVAudioSessionのカテゴリは、デフォルトではオーディオ再生中にアプリを起動するとオーディオが停止(AVAudioSessionCategorySoloAmbient)するようになっているので、どちらも鳴らせるように設定を変更します。Objective-Cだと、AppDelegate.m にこんな感じに書きます。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // バックグラウンドでの音の再生を許可
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
    return YES;
}

【参考】
Swift:AVAudioSession アプリのバックグラウンドで音楽を再生する | siro:chro
iPhoneアプリ開発のためのサウンドフォーマットまとめ | ぱーくん plus idea
Core Audioの概要【PDF】
【iOS】音楽を止めずに効果音を同時に再生するには|てくめも@ecoop.net

SFSpeechRecognizer(音声認識)で効果音が再生されない問題


SFSpeechRecognizer(音声認識)を使ったアプリに効果音を組み込んでみたら、SFSpeechRecognizerを使って音声認識した後に、効果音が鳴らなくなった。

結果的には、SFSpeechRecognizerを使って音声認識を実行した後に、もう一度AVAudioSessionのカテゴリを変更してやることで解決しました。Objective-Cだと、こんな感じ。

// バックグラウンドでの音の再生を許可(AppDelegateで設定しているが、speechRecognizer使用後に再度設定しないと、音が鳴らない)
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];

SFSpeechRecognizer自体はここでは触れないので、下記リンクを参照して下さい。要は、SFSpeechRecognizerを使う際にAVAudioSessionのカテゴリを録音用に変更(AVAudioSessionCategoryRecord)しているので、それを戻さないと音が鳴らないということです。

【参考】
[iOS 10] SFSpeechRecognizerで音声認識を試してみた | Developers.IO

【Xcode】iOS10で各種ユーザーデータへのアクセス許可を参照

【OSX 10.11.6+Xcode 8.0 (8A218a)+iPhoneSE(iOS 10.0.2)】

前回の記事でアプリの初回起動時以外に、各種ユーザーデータへのアクセス許可をどのように参照すればいいのかを前回対象とした「写真」「マイク」「音声認識」「メディアライブラリ」についてまとめました。

※以下、コードは Objective-C です。

写真


従来の写真アクセスである「ALAssetsLibrary」でコードを書いていると・・・

'ALAssetsLibrary' is deprecated: first deprecated in iOS 9.0 - Use PHPhotoLibrary from the Photos framework instead

というワーニングが出ます。

従来の「ALAssetsLibrary」はiOS9以降非推奨になり、Photos frameworkの「PHPhotoLibrary」を使いなさいということなので、「PHPhotoLibrary」ベースでのチェック方法です(もちろん写真のアクセス自体も「PHPhotoLibrary」ベースである前提です)。

//写真設定:参照(アプリの設定で許可されているかどうか)
-(BOOL)isPermitPhotoLibrary
{
    PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
    switch (status) {
        case PHAuthorizationStatusNotDetermined:
            // このアプリに与える権限が未選択(アプリ初回起動時)
            return NO;
            break;
            
        case PHAuthorizationStatusRestricted:
            // 設定による使用制限で、アプリのアクセスの許可を変更できない
            return NO;
            break;
            
        case PHAuthorizationStatusDenied:
            // このアプリに与える権限を拒否(アプリ初回起動時「許可しない」を選択)
            return NO;
            break;
            
        default:
        case PHAuthorizationStatusAuthorized:
            // このアプリに与える権限を許可(アプリ初回起動時「OK」を選択)
            return YES;
            break;
    }
}

マイク


いくつか方法があるようですが、ここでは「AVAudioSessionRecordPermission」を使ってます。

//---マイク使用許可チェック
-(BOOL)isPermitMic{
    AVAudioSessionRecordPermission status = [AVAudioSession sharedInstance].recordPermission;
    
    switch (status) {
        case AVAudioSessionRecordPermissionUndetermined:
            // このアプリに与える権限が未選択(アプリ初回起動時)
            NSLog(@"---AVAudioSession:未選択");
            return NO;
            break;
            
        case AVAudioSessionRecordPermissionDenied:
            // このアプリに与える権限を拒否(アプリ初回起動時「許可しない」を選択)
            NSLog(@"---AVAudioSession:拒否");
            return NO;
            break;
            
        default:
        case AVAudioSessionRecordPermissionGranted:
            // このアプリに与える権限を許可(アプリ初回起動時「OK」を選択)
            NSLog(@"---AVAudioSession:許可");
            return YES;
            break;
    }
}

【参考】
マイクのアクセス許可 – iOS9とXcode7
authorizationStatusForMediaType: – AVCaptureDevice | Apple Developer Documentation
AVAudioSessionRecordPermission – AVFoundation | Apple Developer Documentation
Swift: アルバム・カメラ・マイク・プッシュ通知のアクセス許可判定一覧 | siro:chro

メディアライブラリ


MPMediaLibraryの「authorizationStatus」を使います。Appleのリファレンスにもあんまり詳しいことは書いてないです。何でだ?

//メディアライブラリ設定:参照(アプリの設定で許可されているかどうか)
-(BOOL)isPermitMediaLibrary
{
    MPMediaLibraryAuthorizationStatus status = [MPMediaLibrary authorizationStatus];
    switch (status) {
        case MPMediaLibraryAuthorizationStatusNotDetermined:
            // このアプリに与える権限が未選択(アプリ初回起動時)
            NSLog(@"---MPMediaLibrary:未選択");
            return NO;
            break;
            
        case MPMediaLibraryAuthorizationStatusRestricted:
            // 設定による使用制限で、アプリのアクセスの許可を変更できない
            NSLog(@"---MPMediaLibrary:許可を変更できない");
            return NO;
            break;
            
        case MPMediaLibraryAuthorizationStatusDenied:
            // このアプリに与える権限を拒否(アプリ初回起動時「許可しない」を選択)
             NSLog(@"---MPMediaLibrary:拒否");
            return NO;
            break;
            
        default:
        case MPMediaLibraryAuthorizationStatusAuthorized:
            // このアプリに与える権限を許可(アプリ初回起動時「OK」を選択)
            NSLog(@"---MPMediaLibrary:許可");
            return YES;
            break;
    }
}

【参考】
authorizationStatus – MPMediaLibrary | Apple Developer Documentation
MPMediaLibraryAuthorizationStatus – MediaPlayer | Apple Developer Documentation

音声認識(Speech.Framework)


SFSpeechRecognizerの「authorizationStatus」を使います。

    [SFSpeechRecognizer requestAuthorization:^(SFSpeechRecognizerAuthorizationStatus status) {
        dispatch_async(dispatch_get_main_queue(), ^{
            switch (status) {
                case SFSpeechRecognizerAuthorizationStatusAuthorized: {
                    NSLog(@"--許可あり");
                    break;
                }
                case SFSpeechRecognizerAuthorizationStatusDenied: {
                    NSLog(@"--拒否");
                    break;
                }
                case SFSpeechRecognizerAuthorizationStatusRestricted: {
                    NSLog(@"--設定により使えない");
                    break;
                }
                case SFSpeechRecognizerAuthorizationStatusNotDetermined: {
                    NSLog(@"--未認証");
                    break;
                }
            }
        });
        
    }];

【参考】
SFSpeechRecognizer – Speech | Apple Developer Documentation
authorizationStatus – SFSpeechRecognizer | Apple Developer Documentation
SFSpeechRecognizerAuthorizationStatus – Speech | Apple Developer Documentation

おまけ:自分のアプリの設定を呼び出す


各種ユーザーデータへのアクセス許可を参照して、許可がないものは改めて許可してもらいましょう。ということで、設定アプリの中にある自分の設定を呼び出す方法です。

いわゆるURLスキームというやつらしいですが、iOS10で使い方(openURLのメソッド)が変わっているようです。また下記の参考リンクに「URLスキームの内容をinfo.plistに記載する」とありますが、単純に自分のアプリ設定を呼び出すだけなら不要なようです(実機で動作確認済)。

//このアプリの設定を呼び出し
- (void)prefsButtonPushed {
    NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
    if ([[UIApplication sharedApplication] canOpenURL:url]) {
        [[UIApplication sharedApplication] openURL:url
                                           options:@{}
                                 completionHandler:nil];
        //以下は、iOS9までの書き方
        //[[UIApplication sharedApplication] openURL:url];
    }
}

これを実行すると、自分のアプリの設定が表示されます。

自分のアプリの設定が表示されます。
自分のアプリの設定が表示されます。

【参考】
application:openURL:options: – UIApplicationDelegate | Apple Developer Documentation
UIApplicationOpenSettingsURLString – UIKit | Apple Developer Documentation
[iOS 10] UIApplication の openURL: が Deprecated になりました | Developers.IO
iOS9でカスタムURLスキームの遷移に失敗するときの注意点 – Qiita
【iOS9】特定のURLスキームのみを呼び出し可能にする→.canOpenURL(url)について | MUSHIKAGO APPS MEMO
投稿の編集 ‹ AS blind side — WordPress
標準アプリのURLスキームについて – もう一人のY君

最後に


今回出てきた「Photo.Framework」「Speech.Framwork」については、別途まとめたいですが、とりあえず気になる人は下記を参考に。

・Photo.Framework
Photos vs Assets Library – いまさら始めるPhotos.framework
[iOS 8] PhotoKit 1 – Photos frameworkの概要 | Developers.IO
Photos Framework を使って写真.appと画像をやりとりする – Qiita
Photos frameworkを使ってiPhoneアルバム内の写真を取得・削除する+α – Think Big Act Local
Photos.framework でカメラロールを取得する – ObjecTips
投稿の編集 ‹ AS blind side — WordPress

・Speech.Framwork
[iOS 10] SFSpeechRecognizerで音声認識を試してみた | Developers.IO
[iOS 10] SFSpeechRecognizerの音声認識処理の仕組みを見てみる | Developers.IO
【iOS 10】Speechフレームワークで音声認識 – 対応言語リスト付き – Over&Out その後
新規投稿を追加 ‹ AS blind side — WordPress
【iOS】Speech Frameworkの実装 – Qiita
【iOS 10】Speech Frameworkで音声認識 – あたも開発ブログ
【iOS10】Speech Recognition API(音声認識API)の制約まとめ – Qiita

【Xcode】iOS10以降は各種ユーザーデータへアクセスに許可が必要

【OSX 10.11.6+Xcode 8.0 (8A218a)+iPhoneSE(iOS 10.0.2)】

iPhoneの音楽ライブラリからアートワークを使おうと思って、前に書いた古いコードから必要な部分を移植して動かしてみたらエラーが出た。

[access] This app has crashed because it attempted to access privacy-sensitive data without a usage description.  The app's Info.plist must contain an NSAppleMusicUsageDescription key with a string value explaining to the user how the app uses this data.

iOS10以降は、ユーザーデータにアクセするには、Info.plistに該当アクセスに対応したキーと使用目的を示すテキストを記述する必要があるようです。詳しくは下記リンクを。

[iOS 10] 各種ユーザーデータへアクセスする目的を記述することが必須になるようです | Developers.IO

具体的には、TARGETSからinfoを選んで、下記のように追加するだけ。

TARGETS > info で必要なアクセス先に対する許可を追加します。
TARGETS > info で必要なアクセス先に対する許可を追加します。ここではメディアライブラリ、マイク、音声解析、写真ライブラリの4つを指定。

Keyの選択肢は、全て「Privacy – 」で始まっているので、そこから必要なものを追加すると、アプリの初回起動時にそれぞれ許可を求めるダイアログが表示されます(keyに指定したString部分が、ダイアログの説明分として表示されます)。

info.plistで設定したアクセスに対して、許可を求めるダイアログが表示される。
info.plistで設定したアクセスに対して、許可を求めるダイアログが表示される。

ダイアログでの選択(許可するかしないか)は、iPhoneの 設定>アプリ名の中に保存され、ユーザーが切り替え可能です。逆に言うと、初回起動時以降は、ユーザーに設定で許可を変更するよう促す必要がある、ということですね。

アクセス許可は、iPhoneの 設定>アプリ名の中に保存される。
アクセス許可は、iPhoneの 設定>アプリ名の中に保存される。

で、冒頭の音楽ライブラリのアートワークを使うには、メディアライブラリへのアクセス許可を求めればいいようです(今回は音楽データそのものは使っていません)。

【参考】
iOS10ではカメラアクセスなどの目的を明示しないと強制終了する – Qiita
[iOS 10] ユーザーのプライバシー情報にアクセスするときは理由を書こう | Developers.IO
iOS 10 からユーザの許可を求めるアラートへの説明文追加が必須に – 強火で進め

【Xcode】Cardboard for unityのデモをXcode7で書き出す際の注意点

【OSX 10.11.3+Xcode 7.2.1+Unity 5.3.2f1+iPhone5(iOS 9.2.1)】

VRも盛り上がりを見せつつある昨今、iPhoneでVRのテストをしてみようかとググっていて、こんな記事を見つけました。

Cardboard SDK for Unityを使ってVR対応アプリを作る & Live2Dのキャラを目の前に召喚する – Qiita

以前、「ぱのかの」(現「ルクルク」)のイベントでスマホ用ビューアをもらったことを思い出しました。

IMG_1196
ぱのかの(ルクルク)ビューア。 ここ で手に入ります。

とりあえず「Cardboard SDK for Unity」のデモをテストしてみてみました。手順は下記のリンクを参照。

iOS 用 Unity のスタート ガイド  |  Cardboard  |  Google Developers

で、この記事通りに進めてみたもののXcodeからの書き出しでエラーになります。Xcode7リリース前の記事であるため、2点ほどエラーになった箇所があり、その対処のための備忘録です。

Xcode7.2.1でbitcodeエラー

Unityからビルドすると、Xcodeのプロジェクト一式が書き出されるので、プロジェクトファイル(今回は”Unity-iPhone.xcodeproj”)をXcodeで開くことになります(今回初めて知りました)。

で、Xcodeからビルドすると下記のようなエラーになります。

ld: '/Users/c_geru/work/unity/testProject/xcode/Libraries/Plugins/iOS/libvrunity.a(vraudio_unity.o)' does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. for architecture armv7
clang: error: linker command failed with exit code 1 (use -v to see invocation)

これは、プロジェクト設定の Build Settings > Enable BitcodeをNOにすればいいらしい。詳しくは下記参照。

Xcode7でbitcodeのエラーが出た – Qiita

Xcode7.2.1で”_SecTrustEvaluate”エラー

で、もう一回ビルドしてみると、今度は別のエラーが2件。

Undefined symbols for architecture armv7:
  "_SecTrustEvaluate", referenced from:
      ___75-[GTMSessionFetcher URLSession:task:didReceiveChallenge:completionHandler:]_block_invoke651 in libvrunity.a(GTMSessionFetcher.o)
ld: symbol(s) not found for architecture armv7
clang: error: linker command failed with exit code 1 (use -v to see invocation)
Undefined symbols for architecture armv7:
  "_SecTrustEvaluate", referenced from:
      ___75-[GTMSessionFetcher URLSession:task:didReceiveChallenge:completionHandler:]_block_invoke651 in libvrunity.a(GTMSessionFetcher.o)
ld: symbol(s) not found for architecture armv7
clang: error: linker command failed with exit code 1 (use -v to see invocation)

これらは、Security.frameworkが足りないことによるエラーのようです。プロジェクト設定の General > Linked Frameworks and Libraries で Security.framework を追加してやればOK。詳しくは下記参照。

[Unity] Xcodeで”_SecTrustEvaluate”, referenced from: エラーが出る – Qiita

これで無事、実機にビルドできました。

iPhone5にビルドした様子。
iPhone5に「Cardboard SDK for Unity」のデモをビルドした様子。

【Xcode】Apple Watch用アプリのテスト(2)- 親子間(iPhone、Apple Watch)の通信

【Xcode7.2 + MacOX10.11.2 + watchOS 2】
前回に引き続き、Apple Watch用アプリを試してみました。今回は親子間(iPhone、Apple Watch)の通信について。実機がないのでSimulatorで確認。

同じ方の書いたこの記事を参考にしました。

Apple Watch アプリから親アプリの情報を更新する。 – Apple Watch アプリプログラミング

ただこのままだとエラーが出て、コンパイルが通りません。watchOS 2では、ここで通信に使っている WKInterfaceController.openParentApplication() というメソッドが使えないようです。

ios – ‘openParentApplication(_:reply:)’ has been explicitly marked unavailable here – Xcode 7 Beta – Stack Overflow

そこで調べてみたら、この記事で解説されていました。

[Apple Watch] Watch Connectivity: sendMessage 即座にメッセージを送るには

準備は、こんな感じになります。

・WatchConnectivity を import する。
・WCSessionDelegate を設定する。
・WCSession のサポートの有無をチェックする(WCSession.isSupported())。
・WCSession がサポートされているときには WCSession を初期化(WCSession.defaultSession())して、delegateを設定し、activeにする(activateSession())。

後は、送信・受信用のメソッドをWCSession 用に書き換えてやればOKです。

・送信:– sendMessage:replyHandler:errorHandler:
・受信:– session:didReceiveMessage:replyHandler:

ちょっと気になったのは、Simulatorでのテストでは、iPhone >> Watch への送信は即時に受け取れるんですが、Watch >> iPhone への送信は、10秒ほどのタイムラグが出ました。これがSimulator のみの問題なのか、Apple Watch の仕様なのかは、実機がないので確認出来ていません。

【その他参考リンク】
WCSession Class Reference – iOS Developer Library
WCSessionDelegate Protocol Reference – iOS Developer Library
iOS – watchOS 2 の Watch Connectivity を使ってみた – Qiita
[watchOS 2][iOS 9] Watch Connectivity で情報をやりとりする様々な方法 | Developers.IO

【Xcode】Apple Watch用アプリのテスト(1)

【Xcode7.2 + MacOX10.11.2 + watchOS 2】
Apple Watch用アプリを試してみました。実機がないのでSimulatorで確認。

いろいろ探してみたけど、見た中ではこの記事が一番わかりやすかった。

・簡単な Apple Watch アプリを初めて作ってみる – Apple Watch アプリプログラミング –

Apple Watchアプリは、必ず親となるiPhoneアプリがあって、その子としてWatchKit App を作成するということのようです。

このサイトの通りで基本的に問題ないのだけれど、唯一違うのはwatchOSが2にバージョンアップされているので、プロジェクトエディターの追加ボタンから、Apple Watch のWatchKit App を選択するところで、選択肢が2つに増えているところ。ここでは左(watchOS 2)を選択。

WatchKit Appが、watchOS 2とwatchOS1の2種類あるある。
WatchKit Appが、watchOS2とwatchOS 1の2種類ある。無印がwatchOS2用。

もう1点、Simulatorで実行時に「”Install of Apple Watch Application never finished” 」というエラーが出たこと。これは、iPhone側のSimulatorでWatchアプリから、該当アプリの設定を開いて”Show App on Apple Watch”のスイッチを一度切ってから入れ直せばOKでした。詳しくは下記リンクで。

ios – “Install of Apple Watch Application never finished” Error when deploying watch kit app to device – Stack Overflow

これでSimulator上でApple Watchアプリの確認ができました。

【Xode】NavigationController配下にTabBarControllerを組み込む

【Xcode6.2 + iOS 8.4(iPhone5) + MacOX10.9.5】

文字通りのことをやろうと思ったんだけど、なかなかいい例がなかったのでメモ。

TabBarControllerとNavigationContorollerの組み合わせ例はいくつかあったんだけど、大抵がTabBarControllerをベースにして、子にNavigationContorollerを使うというパターン。

今回やりたかったのは、NavigationContorollerをベースにして、その配下でTabBarControllerを使うパターン。アプリの設定画面部分だけをTabBarControllerにするようなイメージです。

それから前提として「Viewは、プログラム生成ではなくStoryboardに配置する」ということ。AutoLayoutを使うことを考えると、レイアウトはStoryboard、処理はコードで分けるのがいいんじゃないかと。

Storyboardの構成はこんな感じです。
storyboard

・ViewController
・SettingViewController
・TimeSettingViewController
・SpeedSettingViewController

この4つは、それぞれ同名のカスタムクラスを作ってStoryboardで割り当てています。ViewControllerがrootControllerになっていて、ボタンタップで2つのタブ(TimeSettingViewController, SpeedSettingViewController)を持つTabBarController(SettingViewController)を呼び出す構成です。

※ちなみにTabBarControllerを配置すると自動的に接続されるSegueは削除してあります。その設定もコード上で試してみたかったので。

で、まずはUITabBarControllerの定義。どこで使うにせよ、これはAppDelegateの中に書くのが定石らしい。

【AppDelegate.h】
・Storyboardの各Viewを参照するので、その定義ファイル(.h)のimportと保存するUITabBarControllerの定義を追加する。

#import <UIKit/UIKit.h>
#import "SettingViewController.h"
#import "TimeSettingViewController.h"
#import "SpeedSettingViewController.h"

@interface AppDelegate : UIResponder <UIApplicationDelegate,UITabBarControllerDelegate>
{
    UITabBarController *myTabBarController;
}

@property (strong, nonatomic) UIWindow *window;
@property (nonatomic,strong) UITabBarController *myTabBarController;

@end

【AppDelegate.m】
・あとで参照する可能性もあるので、各タブのcontrollerも一応定義しておく。
・UITabBarController の生成は、didFinishLaunchingWithOptionsで行う。
・tabControllerの切替イベント(shouldSelectViewController, didSelectViewController)もココに定義。

#import "AppDelegate.h"

@interface AppDelegate ()

@end

@implementation AppDelegate
{
    TimeSettingViewController *TimeSettingVC;
    SpeedSettingViewController *SpeedSettingVC;
}

@synthesize myTabBarController;


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    //UITabBarControllerを生成(storyboard上のUITabBarControllerを指定)
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    myTabBarController = [storyboard instantiateViewControllerWithIdentifier:@"SettingVC"];
    
    //各タブ用UIViewControllerを生成(storyboard上のUIViewControllerを指定)
    TimeSettingVC = [storyboard instantiateViewControllerWithIdentifier:@"TimeSettingVC"];
    SpeedSettingVC = [storyboard instantiateViewControllerWithIdentifier:@"SpeedSettingVC"];
    
    //UITabBarControllerに各タブ用UIViewControllerを設定
    NSArray *controllers = [NSArray arrayWithObjects:TimeSettingVC,SpeedSettingVC, nil];
    [myTabBarController setViewControllers:controllers animated:YES];
    
    //デリゲートの設定
    myTabBarController.delegate = self;
    
    //初期表示のタブを指定
    myTabBarController.selectedViewController = myTabBarController.viewControllers[1];
    
    NSLog(@"--array: %@",myTabBarController.viewControllers);
    NSLog(@"--selectedViewController: %@",myTabBarController.selectedViewController);
    NSLog(@"--index: %d",myTabBarController.selectedIndex);

    return YES;
}

-(BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
     NSLog(@"--tabBarControllerを切り替えます!");
    NSLog(@"--selectedIndex: %d",tabBarController.selectedIndex);
    NSLog(@"-- vc: %@",viewController);
    
    return YES;
}

-(void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
    NSLog(@"--tabBarControllerが切り替わった!");
    NSLog(@"--selectedIndex: %d",tabBarController.selectedIndex);
    NSLog(@"-- vc: %@",viewController);
}

@end

※22行目〜28行目までは、tabBarControllerに各タブを定義する処理なので、Storyboard上でSegueが定義されている場合は不要。

ここでのポイントは、AppDelegateからStoryboardを参照するには、storyboardWithNameを使わないといけないということ。各Viewからなら、self.storyboardでいけるんだけど。

【参考】

次にtabBarContorollerを呼び出す処理を書きます。viewControllerにボタンを配置して、そこからリンクします。

【ViewController.h】
・AppDelegateをimport。
・showSettingViewはボタンタップ時の処理。

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

@interface ViewController : UIViewController

- (IBAction)showSettingView:(id)sender;

@end

【ViewController.m】
・AppDelegateを参照してtabBarControllerを取得して、pushViewControllerで表示する。

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

- (IBAction)showSettingView:(id)sender {
    //AppDelegateを参照して、myTabBarControllerを呼び出す
    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    [self.navigationController pushViewController:appDelegate.myTabBarController animated:YES];
}

@end

iOS View Controller カタログ(PDF)によれば・・・

Tab Barインターフェイスを作成する前に、その使い方を決定する必要があります。Tab Barインターフェイスはデータに対して支配的な構成となるため、用途は以下に限定されます。
・ウィンドウのルートView Controllerとして追加する。
・Split Viewインターフェイスの2つのView Controllerの1つとして追加する。(iPadのみ)
・別のView Controllerからモーダルモードで表示する。
・Popoverから表示する。(iPadのみ)

モーダルモードでのTab Bar Controllerの表示
(一般的ではありませんが)アプリケーションでTab Bar Controllerをモーダルモードで表示すること もできます。Tab Barインターフェイスは、通常はアプリケーションのメインウインドウにインストー ルされ、必要な場合にだけ更新されます。ただし、インターフェイスの設計において必要と認められ る場合には、Tab Bar Controllerをモーダルモードで表示することも可能です。たとえば、アプリケー ションの主たる操作モードを、Tab Barインターフェイスを使用したまったく別のモードに切り替える には、クロスフェードトランジションを使用して、第2のTab Bar Controllerをモーダルモードで表示します。
Tab Bar Controllerをモーダルモードで表示する場合は、必ずpresentModalViewController:animated: メソッドの第1パラメータにTab Bar Contrllerオブジェクトを渡します。このTab Bar Controllerは、表示する前にすでに設定されていなければなりません。したがって、Tab Barインターフェイスをメインウ インドウにインストールする場合とまったく同じ方法で、ルートView Controllerを作成して設定し、それらをTab Bar Controllerに追加する必要があります。

ということらしいので、

・TabBarControllerの定義は、AppDelegateで行う。
・表示は、別のView Controllerからモーダルモードで行う。

ということでいいようです。

【参考】
Tab Bar Controllers
Combined View Controller Interfaces
iOS View Controller カタログ(PDF/上記2つの日本語訳)
iOS View Controller プログラミングガイド(PDF)
StoryboardとSegueの基本 – Kesin’s diary
画面間でのデータの受け渡しに付いて: 永遠ログ
iOS – segue による画面遷移メモ – Qiita
日本語ドキュメント – Apple Developer
プログラム側からUITabBarControllerを切り替えて、さらに画面遷移させたい時 – makoラボ
Programming.log, [Objective-C] applicationDidFinishLaunching とは