CrossBridge Lab

技術ネタ、デバイスネタを...

AppleWatchアプリを作るときにハマったBundleID周りの設定

はじめに

AppleWatchアプリの話ではなくiOS側のアプリで、DebugビルドとReleaseビルドでBundleIDを分けることはよくあると思います。同じ端末にデバッグ用とリリース用(or ストアからダウンロードしたもの)の両方のアプリを入れたい場合ですね。

で、そのときにAppleWatchに対応させようとTargetを追加したときにハマったのでメモとして残しておきます。AppleWatchに限らずにExtention系(Notification Service Extension とか)で同じ問題が発生するはずです。

ビルドが通ったのにエラーが出る

Target追加→とりあえずビルドして実行しましょうね〜って思ったところビルドが成功したのちシミュレータが立ち上がってされアプリも起動しようかってタイミングでXcodeさんに怒られる。

そんなBundleIDないから、的な。

そう、WatchKit Appの設定には端末側のアプリのBundleIDの記述があります。そしてWatchKit ExtentionにはWatchKit AppのBundleIDの記述があります。それぞれ正しく設定されていないとビルドエラーは発生しないけど実行しようとしたタイミングで怒られてしまうようです。(ビルド時にエラー出してよ・・・)

Build Settings と info.plist を修正する

AppleWatch側(WatchKit App と WatchKit Extention)の設定にもdebugとreleaseで分ける記述を追加することで解決します。

WatchKit App

まずはアプリ自体のBundleIDを分けます。これはiOSアプリと同じようにProduct Bundle Identifierを分けるだけです。

f:id:crossbridge-lab:20161225111812p:plain

次にinfo.plistにWKCompanionAppBundleIdentifierという値があり、これはCompanionApp(iOS側のアプリのこと)のBundleIDを指定するものです。この値をデバッグとリリースで分けるようにするため適当な変数を参照するようにします。ここでは$(COMPANION_BUNDLE_ID)としています。

f:id:crossbridge-lab:20161225111227p:plain

で、build settingsの方で定義します。

f:id:crossbridge-lab:20161225111617p:plain

WatchKit Extension

次に WatchKit Extension側です。まずはProduct Bundle Identifierを分けます。

f:id:crossbridge-lab:20161225112434p:plain

info.plistにWatchKit AppのbundleIDを指定する記述があります。ここをデバッグとリリースで分けるようにするため適当な変数を参照するようにします。ここでは$(WKAPP_BUNDLE_ID)としています。

f:id:crossbridge-lab:20161225112254p:plain

そしてbuildsettingsの方でその定義を書きます。

f:id:crossbridge-lab:20161225112504p:plain

これで無事実行することができるようになりました。


watchOS3で追加されたAVFoundataionを使って音を鳴らす&喋らす

はじめに

今回の記事はiOS その3 Advent Calendar 2016の12/19分となります。

watchOS3でAVFoundataionフレームワークが追加されました。watchOS3ではSceneKit/SpriteKitも追加されており、(楽しいかどうかはおいておいて)AppleWatchで遊ぶゲームアプリが作りやすくなりました。

AVAudioEngineで音声ファイルを鳴らす

プロジェクトに音声ファイルを追加する

まずはファイルをプロジェクトに追加します。注意しなければいけないのは WatchKit Extension に追加するということです。WatchKit Appの方ではないので気をつけましょう。

f:id:crossbridge-lab:20161218232956j:plain

AVAudioEngine、AVAudioPlayerNode、AVAudioFileを使って再生する

使うAVFoundataionのクラスは AVAudioEngineAVAudioPlayerNodeAVAudioFile の3つです。

まずはコードを。

    var audioEngine: AVAudioEngine!
    var audioPlayerNode: AVAudioPlayerNode!
    var audioFile: AVAudioFile!
    
    override func awake(withContext context: Any?) {
        super.awake(withContext: context)
        
        guard let path = Bundle.main.path(forResource: "se", ofType: "caf") else {
            return
        }
        
        audioEngine = AVAudioEngine() // (1)
        audioPlayerNode = AVAudioPlayerNode() // (2)
        let audioPath = URL(fileURLWithPath: path)
        audioFile = try! AVAudioFile(forReading: audioPath) // (3)
        audioEngine.attach(audioPlayerNode) // (4)
        audioEngine.connect(audioPlayerNode,
                            to: audioEngine.mainMixerNode,
                            format: audioFile.processingFormat) // (5)
        audioEngine.prepare() // (6)
        try! audioEngine.start() // (7)
    }
    
    @IBAction func hanldeButton1() {
        audioPlayerNode.scheduleFile(audioFile, at: nil) { () -> Void in // (8)
            print("complete")
        }
        audioPlayerNode.play() // (9)
    }

以下の手順で音声ファイルを再生します。

  1. AVAudioEngineのインスタンスを生成する
  2. AVAudioPlayerNodeのインスタンスを生成する
  3. Bundle内の音声ファイルを指定してAVAudioFileのインスタンスを生成する
  4. AVAudioEngineのattach(_:)メソッドでAVAudioPlayerNodeをアタッチする
  5. AVAudioEngineのconnect(_:to:format:)メソッドでNode同士を接続する
  6. AVAudioEngineのprepare()メソッドとstart()メソッドで処理を開始させる
  7. AVAudioPlayerNodeのscheduleFile(_:at:completionHandler)メソッドで音声ファイルの再生をスケジュールする
  8. AVAudioEngineのstartメソッドで再生する

AudioUnitは使うことができない

なおAPI Diffsの中にAVAudioUnitの文字を見つけて「おっ!」となったのですが、どうやらまだAudioUnitを使うことはできないようです。Preset のクラスはありますが、肝心の本体のクラスがありません。(AVAudioUnitReverbPresetクラスはあるがAVAudioUnitReverbクラスが無い)

残念ながらエフェクトをかけて再生することはできないようです。ただ Preset のクラスが追加されたということは将来的に使えるようになるかもしれませんね。ハードウェアが進化したApple Watch Series3が発売されるときでしょうか?!

AppleWatchに喋らせる

AVSpeechSynthesizer

次にAppleWatchに喋らせる方法です。喋らせるにはAVSpeechSynthesizerクラスを使います。

まずはコードを。

class InterfaceController: WKInterfaceController {
    
    var speechSynthesizer: AVSpeechSynthesizer!
    
    override func awake(withContext context: Any?) {
        super.awake(withContext: context)
        
        AVSpeechSynthesisVoice.speechVoices().forEach {
            print($0.language)
        }
        
        speechSynthesizer = AVSpeechSynthesizer() // (1)
        speechSynthesizer.delegate = self // (2)
    }
    
    @IBAction func handleButton2() {
        let speechUtterance = AVSpeechUtterance(string: "おはよう。こんにちは。こんばんは") // (3)
        speechUtterance.voice = AVSpeechSynthesisVoice(language: "jp-JP")  // (4)
        speechUtterance.rate = AVSpeechUtteranceDefaultSpeechRate
        speechUtterance.pitchMultiplier = 1.0   // [0.5 - 2] Default = 1
        speechSynthesizer.speak(speechUtterance)  // (5)
    }
}

extension InterfaceController: AVSpeechSynthesizerDelegate {
    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer,
                           didStart utterance: AVSpeechUtterance) {
        print("再生開始")
    }
    
    func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer,
                           didFinish utterance: AVSpeechUtterance) {
        print("再生終了")
    }
}

以下の手順で音声ファイルを再生します。

  1. AVSpeechSynthesizerのインスタンスを生成する
  2. AVSpeechSynthesizerのdelegateを設定する(必須ではない)
  3. 喋らせる文章を指定してAVSpeechUtteranceのインスタンスを生成する
  4. AVSpeechUtteranceのvoiceプロパティにAVSpeechSynthesisVoice(言語)を設定する
  5. AVSpeechSynthesizerのspeakメソッドで喋らせる(再生する)

なお上記のサンプルでは(4)と(5)の間に再生させるスピードrateと、再生するピッチpitchMultiplierを指定しています。これらの値を変更することで早く喋らせたり声の高さを変えることができます。

AVSpeechSynthesizerDelegateクラスのメソッドで再生開始や再生終了のタイミングで処理を行うことができます。

喋らせることができる言語

AVSpeechSynthesisVoiceクラスのspeechVoicesメソッドで一覧を取得することができます。

AVSpeechSynthesisVoice.speechVoices().forEach {
    print($0.language)
}

上記のコードをwatchOS3.1.1で実行すると以下の結果を以下の言語が利用できることがわかります。今後アップデートで言語が増える可能性はあります。

ar-SA
cs-CZ
da-DK
de-DE
el-GR
en-AU
en-GB
en-US
es-ES
es-MX
fi-FI
fr-CA
fr-FR
he-IL
hi-IN
hu-HU
id-ID
it-IT
ja-JP
ko-KR
nl-NL
no-NO
pl-PL
pt-BR
pt-PT
ro-RO
ru-RU
sk-SK
sv-SE
th-TH
tr-TR
zh-CN
zh-HK
zh-TW

まとめ

AVFoundataionが追加されたことで音声ファイルの再生などできることが増えました。増えましたが・・・そんなにAppleWatchで音を鳴らす機会ってあるんですかね(元も子もない)

元も子もない一環でSprikeKitで作ったFlappyBirdもよければ御覧ください。AppleWatchでFlappyBirdをして楽しいかどうか・・・Starをもらえるとこの無意味な鳥も浮かばれます。

github.com

あと、よくよく考えたらwatchOSの記事なのでiOS Advent Calendarでよかったのか!?


Apple Watch Nike+を買ってみた

 どうせ無いだろうと思いつつ行ったAppleStoreで残念ながらApple Watch Nike+の在庫があったので購入してしまいました。

 42mmのスペースグレイアルミニウムケースとブラック/クールグレーNikeスポーツバンドです。

開封

f:id:crossbridge-lab:20161212223709p:plain

 開けてみると・・・Designed for athletes by Apple and Nikeと書かれています。

f:id:crossbridge-lab:20161212223848p:plain

充電器

 AppleWatchの充電はLightningでもなければマイクロUSBでもありません。非接触充電になります。購入時に一つケーブルが付属しますがもう一つ買ってオフィスなどに置いておくと充電を忘れたときに安心です。

 毎度のことですがApple純正は高い・・・

 3000円も出すなら何かサードパーティ製で良いものがないかと探したところ見つけたのがこれ。Series1のために買ってみたところ台座は金属ではないので高級感はないですがお値段以上の質感ではあります。当然Series2でも問題なく使うことができます。

 さてSeries2でどれだけ速くなったのか楽しみです。GPSは・・・ジョギングで試してみます。とりあえず先日作ったFlappyBirdもどきを動かしてみよう!?

crossbridge-lab.hatenablog.com