読者です 読者をやめる 読者になる 読者になる

CrossBridge Lab

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

【Swift3】 Firebaseの設定ファイルをdebugビルドとreleaseビルドで切り替える

Firebase Swift iOS メモ

はじめに

Firebaseを使うには設定ファイル(plist)をプロジェクトに追加する必要があります。

debug環境もrelease環境も同じ設定ファイルを使う場合は特に問題がないのですが、debugビルドのみbundleIDを変えている場合にはFirebaseのコンソールで2つのアプリを登録し、それぞれ別々の設定ファイルGoogleService-Info.plistが生成されるのでビルド時に読み込むplistを切り替える必要があります。

GoogleService-Info.plistをビルドによって切り替える

まずはファイル名を変更します。ここではdebug用のファイルをGoogleService-Info-debug.plistとします。

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

次にAppDelegateでplistのファイル名の変数を#if DEBUGで切り替えるように定義します。

#if DEBUG
let firebasePlistName = "GoogleService-Info-debug"
#else
let firebasePlistName = "GoogleService-Info"
#endif

そして今まではFIRApp.configure()とconfigure()メソッドを呼んでいたところをオプションを指定するように修正します。

class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        if let firbaseOptions = FIROptions(contentsOfFile: Bundle.main.path(forResource: firebasePlistName, ofType: "plist")) {
            FIRApp.configure(with: firbaseOptions)
        }
        
        return true
    }

もし3つ以上の環境を切り替える場合は定義する変数を増やして同じように切り替えるようにすればOKです。


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

watchOS Xcode メモ

はじめに

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を使って音を鳴らす&喋らす

watchOS AppleWatch Swift 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でよかったのか!?