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

【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 からユーザの許可を求めるアラートへの説明文追加が必須に – 強火で進め

【Cordova】Cordova を使ってみよう(2)Androidでスプラッシュスクリーンを表示する

【OSX 10.11.3 + Node.js v4.2.6 + nam 3.5.3 + Cordova 6.0.0 + Xcode 7.2 + Android Stuido 1.3】

※ターミナルのコマンドについては、$を省略しています。理由はコピペの時に必要ないからです。

Cordovaの続きです。前回に引き続き、下記サイトを参考にAndroidでスプラッシュスクリーン表示をテストしてみました。

Cordova: アプリの設定 – Build Insider

これが意外にハマりました。原因は、参考サイトではCordovaのバージョンが3.0.0なのに対して、現在は6.0.0なので仕様が変わっているようです。以下、手順です。

1.Splashscreen プラグインのインストール

Cordovaのバージョン4.0.0以降、スプラッシュ・スクリーンの機能がプラグイン化されたようです。ターミナルから以下のコマンドでインストールします。

cordova plugin add cordova-plugin-splashscreen

【参考】Apache Cordova のスプラッシュ・スクリーン表示がややこしい | まさくらのブログ
※本サイトでエラーが出ているので、暫定的にキャッシュをリンクしました。

2.config.xmlの設定

config.xmlに設定を書き込みますが、これはプロジェクトディレクトリの直下(今回の場合は、hello/config.xml)にあるファイルです。hello/platforms/android/res/xml/にも同名のファイルがあるので注意。

基本的に設定は、プロジェクトディレクトリ直下のconfig.xmlを変更して、プラットフォーム別のプロジェクトに反映するようです。

Androidのスプラッシュスクリーンは、以下の2つを設定します。
・タグの中に解像度別の画像リンク。
・widgetタグの中にスプラッシュスクリーンの表示時間を指定したタグ(preference name=”SplashScreenDelay” value=”10000″)。

<?xml version='1.0' encoding='utf-8'?>
<widget id="com.example.hello" version="1.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
    <name>HelloWorld</name>
    <description>
        A sample Apache Cordova application that responds to the deviceready event.
    </description>
    <author email="dev@cordova.apache.org" href="http://cordova.io">
        Apache Cordova Team
    </author>
    <content src="index.html" />
    <plugin name="cordova-plugin-whitelist" spec="1" />
    <access origin="*" />
    <allow-intent href="http://*/*" />
    <allow-intent href="https://*/*" />
    <allow-intent href="tel:*" />
    <allow-intent href="sms:*" />
    <allow-intent href="mailto:*" />
    <allow-intent href="geo:*" />
    <platform name="android">
        <allow-intent href="market:*" />
        <splash src="platforms/android/res/drawable-port-ldpi/splash.9.png" density="port-ldpi" /><!--200x320-->
        <splash src="platforms/android/res/drawable-port-mdpi/splash.9.png" density="port-mdpi"/><!--320x480-->
        <splash src="platforms/android/res/drawable-port-hdpi/splash.9.png" density="port-hdpi"/><!--480x800-->
        <splash src="platforms/android/res/drawable-port-xhdpi/splash.9.png" density="port-xhdpi"/><!--720x1280-->
        <splash src="platforms/android/res/drawable-land-ldpi/splash.9.png" density="land-ldpi" />><!--320x200-->
        <splash src="platforms/android/res/drawable-land-mdpi/splash.9.png" density="land-mdpi"/><!--480x320-->
        <splash src="platforms/android/res/drawable-land-hdpi/splash.9.png" density="land-hdpi"/><!--800x480-->
        <splash src="platforms/android/res/drawable-land-xhdpi/splash.9.png" density="land-xhdpi"/><!--1280x720-->
    </platform>
    <platform name="ios">
        <allow-intent href="itms:*" />
        <allow-intent href="itms-apps:*" />
    </platform>
    <preference name="SplashScreenDelay" value="10000" />
</widget>
3.config.xmlの設定を各プラットフォームに反映する

・ターミナルから以下のコマンドを入力します。

cordova prepare -d

ここでAndroidディレクトリにあるconfig.xml(hello/platforms/android/res/xml/config.xml)を開いてみると、スプラッシュスクリーンの情報が追加されています。

<?xml version='1.0' encoding='utf-8'?>
<widget id="com.example.hello" version="1.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
    <preference name="loglevel" value="DEBUG" />
    <feature name="Whitelist">
        <param name="android-package" value="org.apache.cordova.whitelist.WhitelistPlugin" />
        <param name="onload" value="true" />
    </feature>
    <feature name="SplashScreen">
        <param name="android-package" value="org.apache.cordova.splashscreen.SplashScreen" />
        <param name="onload" value="true" />
    </feature>
    <allow-intent href="market:*" />
    <splash density="port-ldpi" src="platforms/android/res/drawable-port-ldpi/splash.9.png" />
    <splash density="port-mdpi" src="platforms/android/res/drawable-port-mdpi/splash.9.png" />
    <splash density="port-hdpi" src="platforms/android/res/drawable-port-hdpi/splash.9.png" />
    <splash density="port-xhdpi" src="platforms/android/res/drawable-port-xhdpi/splash.9.png" />
    <splash density="land-ldpi" src="platforms/android/res/drawable-land-ldpi/splash.9.png" />
    <splash density="land-mdpi" src="platforms/android/res/drawable-land-mdpi/splash.9.png" />
    <splash density="land-hdpi" src="platforms/android/res/drawable-land-hdpi/splash.9.png" />
    <splash density="land-xhdpi" src="platforms/android/res/drawable-land-xhdpi/splash.9.png" />
    <name>HelloWorld</name>
    <description>
        A sample Apache Cordova application that responds to the deviceready event.
    </description>
    <author email="dev@cordova.apache.org" href="http://cordova.io">
        Apache Cordova Team
    </author>
    <content src="index.html" />
    <access origin="*" />
    <allow-intent href="http://*/*" />
    <allow-intent href="https://*/*" />
    <allow-intent href="tel:*" />
    <allow-intent href="sms:*" />
    <allow-intent href="mailto:*" />
    <allow-intent href="geo:*" />
    <preference name="SplashScreenDelay" value="10000" />
</widget>
4.Android実機又はエミュレータでアプリを起動する

。ターミナルから以下のコマンドを入力します(ココでは実機で起動します)。

cordova run android

これでスプラッシュスクリーンが表示されました。Cordovaの情報は多いですが、バージョンによる違いもあるので、必ず公式のドキュメントに当たった方がよさそうです。

Cordovaサイトの右上にどのバージョンの情報かが表示されているのでチェック。
Cordovaサイトの右上にどのバージョンの情報かが表示されているのでチェック。

【参考】
アイコンとスプラッシュ画面 – Apache Cordova(6.0.0)
Cordovaめも: スプラッシュスクリーン表示

【Cordova】Cordova を使ってみよう(1)環境設定編

先日のCreateJS勉強会で話題に上ったCordova。とりあえず使ってみようということで、環境設定して、コンパイルできるとことまでやってみたので、その作業メモです。ターミナルを使い慣れていないので、主にその周辺についてです。

Cordovaとは何か?

Adobeの Phone Gap という、HTML5+CSS3で作ったコンテンツをパッケージ化してiOS/Android用アプリにするという技術が、オープンソース化されて Cordova と呼ばれています。詳しくは下記で。

【参考】HTML5ハイブリッドアプリ開発を支えるOSS「Cordova」はなぜアツいのか?PhoneGapとの違いは何か? – ふろしき.js

作業環境

・OSX 10.11.3
・Node.js v4.2.6
・nam 3.5.3
・Cordova 6.0.0
・Xcode 7.2
・Android Stuido 1.3

環境設定

下記のサイトを参考にしました。
【参考】Cordovaを用いた開発環境を構築する – Build Insider

以下、環境構築する際に引っかかった点などをまとめておきます。

【Node.jsのインストール】

私の場合は導入済みでした。その時の手順などはこちらを参照。

【cordovaのコマンドラインツールをインストール】

このインストールがなかなか終わらなくて、npmのバージョンが古いのかとアップデートかけたら、npm自体が消えてしまいました。原因はよくわからず・・・。調べてみたら同じ現象について書いてある記事を見つけて、この通りにnpmの再インストールをして復活しました。
【参考】[node.js]npm自体のアップデートしようとしたらなぜかコマンドが消えた… – もじれつとがめん

その後、cordovaのインストールは無事終了。

【Android SDKに含まれているディレクトリにパスを通す】

「以下のように環境に応じてパスを設定してください」と書いてあるけど、どうすればいいかわからず(笑)。
で、次の手順で設定します。

1.Android Stuidoを起動し、「Android Stuido」>「Preferences…」を選択。
2.Preferencesウィンドウの右側で「Appearance & Behavior」>「System Settings」>「Android SDK」を選択すると、「Android SDK Location」蘭にパスが表示されます。

ココでのパスは「」
この環境でのパスは「/Users/c_geru/Library/Android/sdk」

Android SDKのパスをFinderで確認すると、SDKの中に「tools」「platform-tools」のディレクトリが確認出来ます。

ああ
選択されているのが「tools」「platform-tools」ライブラリは不可視ファイルなので、Finderで確認するには不可視ファイルの可視化が必要。

3.パスがわかったので、2つのディレクトリにパスを通します。ターミナルから下記のコマンドを1つずつ入力してreturnキーを押します。「echo 」以降がコマンドです。正しく受け付けられると、入力可能(c-gerus-MBP2011:/ c_geru$ が表示され、カーソルが点滅している)な状態になります。

※「c-gerus-MBP2011:/ c_geru$」の部分は、当然ながら環境によって異なります。

c-gerus-MBP2011:/ c_geru$ echo "export PATH=\$PATH:/Users/c_geru/Library/Android/sdk/tools" >> ~/.bash_profile
c-gerus-MBP2011:/ c_geru$ echo "export PATH=\$PATH:/Users/c_geru/Library/Android/sdk/platform-tools" >> ~/.bash_profile

パスの設定について詳しくは下記を参照。今回行ったのは.bash_profileというファイルにコマンドライン上からファイルにテキストを挿入する方法です。
【参考】[Mac, Linux] PATHを通す方法 | Memo on the Web

【プロジェクトを作成する】

参考サイト通りにコマンド打ったら、次のようなエラーが出ました。

c-gerus-MBP2011:/ c_geru$ cordova create hello com.example.hello HelloWorld -d
Creating a new cordova project.
Copying assets."
Error: EACCES: permission denied, mkdir '/hello'
    at Error (native)
    at Object.fs.mkdirSync (fs.js:794:18)
    at /usr/local/lib/node_modules/cordova/node_modules/cordova-lib/src/cordova/create.js:284:16
    at _fulfilled (/usr/local/lib/node_modules/cordova/node_modules/q/q.js:787:54)
    at self.promiseDispatch.done (/usr/local/lib/node_modules/cordova/node_modules/q/q.js:816:30)
    at Promise.promise.promiseDispatch (/usr/local/lib/node_modules/cordova/node_modules/q/q.js:749:13)
    at /usr/local/lib/node_modules/cordova/node_modules/q/q.js:557:44
    at flush (/usr/local/lib/node_modules/cordova/node_modules/q/q.js:108:17)
    at nextTickCallbackWith0Args (node.js:419:9)
    at process._tickCallback (node.js:348:13)

ターミナルがデフォルトの状態では、ルート(通常なら Macintosh HD 直下)に位置しているため、ディレクトリを作成する権限がないためです。この場合は、スーパーユーザーを示す「sudo」を冒頭につけて、下記のように実行します。

c-gerus-MBP2011:/ c_geru$ sudo cordova create hello com.example.hello HelloWorld -d

※この際パスワード入力を求められます。ログイン時のパスワードを使用します。
【参考】必ず使える!Macのターミナルで使う基本UNIXコマンド15選 | NEZU.log

大体は作業用にディレクトリを作って、そこにまとめることが多いと思うので、今回はユーザディレクトリ配下の特定のディレクトリを指定して(/Users/c_geru/works/_2tone_dev/_cordova/hello)そこにプロジェクトを作成することにしました。

c-gerus-MBP2011:/ c_geru$ cordova create /Users/c_geru/works/_2tone_dev/_cordova/hello com.example.hello HelloWorld -d
プロジェクトの対象プラットフォームを確認する

参考サイトとは若干表示が違いました。この辺はバージョンによる差違かも。

c-gerus-MBP2011:hello c_geru$ cordova platform ls
Installed platforms: android 5.1.0, ios 4.0.1
Available platforms: amazon-fireos, blackberry10, browser, firefoxos, osx, webos
プロジェクトにAndroidとiOS用のファイルを追加する

cordovaコマンドは作ったプロジェクトのルートで行う必要があるので、追加する前にディレクトリを変更します。

c-gerus-MBP2011:/ c_geru$ cd /Users/c_geru/works/_2tone_dev/_cordova/hello
Androidエミュレータを利用する

問題なく起動しましたが、すごく時間かかりました。この辺はマシンスペックの問題かも。
(MacBook Pro (15-inch, Early 2011) 2GHz intel Core i7 メモリ 8GB)

iOSシミュレータを利用する

参考サイトでは、ios-simをインストールするように指示されていますが、直接エミュレータを起動して問題なかったです。Xcodeがインストール済であれば必要ないのかな?

ブラウザで確認する

「パソコンのブラウザで表示されたアドレスにアクセスすると、指定したプラットフォーム向けのWebアプリを見ることができます。」と書かれているけど、実際に「http://localhost:8000/」にアクセスするとメニュー画面が表示され、そこから各プラットフォームを選択することになるようです。

http://localhost:8000/ にアクセスした様子。対応しているプラットフォームのみリンクがついている。
http://localhost:8000/ にアクセスした様子。対応しているプラットフォームのみリンクがついている。
プラットフォームのリンクをクリックするとコンテンツが表示される。
プラットフォームのリンクをクリックするとコンテンツが表示される。iOSのリンクをクリックした様子。

これで環境設定と、エミュレータ、実機、ブラウザでの確認ができました。次は具体的な中身を作ってみてから。

【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アプリの確認ができました。

【Xcode】Facebook投稿でplugin com.apple.share.Facebook.post invalidated

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

引き続きFacebook投稿の話です。

前回のポストに書いたようにFacebook投稿自体は出来るようになったのですが、プログラムで設定したメッセージが表示されませんでした。コードはこんな感じ。

//facebook:送信
- (IBAction)sendFB:(UIBarButtonItem *)sender {

    // 組み込みのFacebookが利用可能な端末かを検証する
    if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeFacebook]) {
        
        // Facebook投稿機能のインスタンスを作成する
        SLComposeViewController *slComposeViewController = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeFacebook];
        
        
        // 投稿するコンテンツを設定する
        // 表示する文字列
        [slComposeViewController setInitialText:@"#アイコン顔カメラ"];
        // URL
        [slComposeViewController addURL:[NSURL URLWithString:@"http://www.c-geru.com/"]];
        // 画像
        [slComposeViewController addImage:[UIImage imageNamed:@"icon_10.png"]];
        
        // 処理終了後に呼び出されるコールバックを指定する
        [slComposeViewController setCompletionHandler:^(SLComposeViewControllerResult result) {
            
            switch (result) {
                case SLComposeViewControllerResultDone:
                    NSLog(@"Done!!");
                    break;
                case SLComposeViewControllerResultCancelled:
                    NSLog(@"Cancel!!");
            }
        }];  
        
        // 表示する
        [self presentViewController:slComposeViewController animated:YES completion:nil];    
    }

}

で、表示される画面がコレ。「slComposeViewController setInitialText:」のテキストが表示されていません。
メッセージが表示されない

この画面でテキスト入力すると、そのテキストは投稿されますが、Xcodeに下記のようなメッセージが表示されます。

plugin com.apple.share.Facebook.post invalidated

【追記】2015.8.17 ちなみに「slComposeViewController setInitialText:」をコメントにしても、投稿後に上記メッセージが表示されます。

調べてみると、こんな情報が。

ios – UIActivityViewControllerでFacebookにシェアできない – スタック・オーバーフロー
sharing – ios plugin com.apple.share.Facebook.post do not show provided text – Stack Overflow

これによると、Facebook側のポリシー変更ということのようです。

Platform Policy 2.3 Example and Explanation

メッセージは基本ユーザに入力してもらうと考えれば、ハッシュタグつけるとか以外に困ることはなさそうですが、一応備忘録として。

【Xcode】Facebook投稿で「ログインしていません」と表示される

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

アプリからFacebook/twitterへの投稿は、投稿するだけなら各サービスへのアプリ登録は必要なく、Social.FrameworkをインポートしてSLComposeViewControllerを使えばカンタンに出来るらしい。

Objective-C – LINE/Facebook/Twitter投稿 – Qiita
[XCODE] iOS組み込みのFacebook投稿機能を使う方法 – YoheiM .NET
【Xcode】Facebook、TwitterにiOS標準フォームで投稿する
serviceType – SLComposeViewController Class Reference

で、試してみたところ、Facebookの方は「ログインしていません」とメッセージが出て投稿できない。

ログインしていません
表示されるアラート

iPhone側では・・・

・Facebookアプリはインストール、ログイン済。
・iPhoneの 設定>Facebook でID、パスワードは登録済み。

になっています。

いろいろテストしてみたところ、下記の手順で投稿できるようになりました。

1.Facebookアプリをログアウト。
2.iPhoneの 設定>Facebook でID、パスワードの登録を確認。
3.Facebookアプリでログイン。

Facebookアプリに最初にログインしたときに、iPhoneの 設定>Facebook でID、パスワードを登録せずに、Facebookアプリ側でログインしていたため、Xcode側からは設定情報を使ってログインされていないと判断されていたみたいです。

またFacebookアプリはログイン状態を保持しているので、iPhone上でFacebookアプリが起動されているかどうかではなく、Facebookアプリログインされた状態になっているかどうかで判断しているようです。

ブラウザ上でのFacebookページへのログイン状態は関係しないようです。まあ設定の情報を使っていないので、当たり前か。