「だっふんだ 」と言うとSAP S/4HANAで購買依頼伝票を打てるiOSアプリを実装する(1/2)


初めまして、Mona Lisacoと申します。
普段はアドオン大好きABAP女子なのですが今回初めてiOSアプリを作ってみました。

（※ この記事は [SAP Advent Calendar 2018](https://adventar.org/calendars/3262) の12月12日分の記事として執筆しています）

近頃のドリフ大爆笑大流行の影響で、とある雑貨屋の麹町一丁目店で「ドリフ大爆笑DVD」の品切れが頻繁に発生しているらしく、「だっふんだ 」の一声で「ドリフ大爆笑DVD」の購買依頼伝票を打つことができれば非常に便利なのに……という声がよく聞かれます。

そこで、「だっふんだ」という音声コマンドによって、SAP S/4HANAの実機で品目「ドリフ大爆笑DVD」の購買依頼が登録できるiOSモバイルアプリを、iOSのSpeechフレームワークを用いて実装したいと思います。

クリスマスも迫るこの頃、大切な人へのプレゼントに「ドリフ大爆笑DVD」を贈りたい人はますます増えるはず。
このアプリが導入されれば、大人気商品の品切れを心配する必要はもうありません。


### 概要

下記のようなアプリを実装していきます。

* フロントエンド（iOSモバイルアプリ）でSpeech Frameworkを用いて音声認識し、音声を「だっふんだ」と認識できた場合
  * モバイルアプリ上でドリフのオチの音が鳴り、
  * バックエンド（SAP S/4HANA）に「ドリフ大爆笑DVD」の品目コード等を渡し、ODataのCreateメソッド（ABAPで実装）で購買依頼伝票を登録する。
* 音声を「だっふんだ」と認識できなかった場合、「へんなおじさん」の文字列が倍増する。また、認識された文字列が画面下部に表示される。

![daf2.png](https://qiita-image-store.s3.amazonaws.com/0/320955/d6ea0b22-bb81-2560-76c3-bc436707def9.png)

![daf1.png](https://qiita-image-store.s3.amazonaws.com/0/320955/25d6e186-03a0-c9de-d1bc-10204f8917b7.png)


### 構築手順

大きく下記の手順で構築していきます。
1. XcodeでフロントエンドUIおよび音声認識処理を実装
2. バックエンド（SAP S/4HANA）の環境整備をした上で、SDK for iOS Assistantでバックエンドと連携するXcodeプロジェクトを自動生成
3. 2で生成したXcodeプロジェクトに1の実装をマージする
 
記事は全2回に分けて書いていきます。
今回は1のトピックについてのみ書き、2および3の手順は次回の記事で紹介します。

第2回の記事はこちら↓
https://qiita.com/monamona/items/ba4d0b0b861fe9676b3a

### 環境

モバイル環境　iPhone8／iOS12.1
開発環境　Xcode 10.1


### フロントUIを作る

Xcodeを起動し、File > New > Project > Single View App を選択して適当に名前をつけるとXcodeプロジェクトが作成できます。

画面左のナビゲーションを見てみましょう。いくつかのファイルが生成されています。
基本的には、ViewController.swiftにプログラムを書き、Main.storyboardでUIを設定していくことになります。
（ドリフのオチの音のサウンドファイルをあらかじめ追加しておきましょう。Add > Add files to ... でファイルを選びます。）

それでは早速Main.storyboardをクリックし、UIオブジェクトを配置していきます。
shift + command + L でライブラリを表示させ、テキストビューやボタンを選んで、画面上に適当に配置します。
今回はテキストビュー mainText および whatYouSaid 、それからボタン UIButton を作りました。

View > Assistant Editor > Show Assistant Editor で　ViewController.swift を画面右に表示させ、Control を押しながらテキストビューやボタンを　ViewController クラスの配下にドラッグします。

![スクリーンショット 2018-12-07 21.30.22.png](https://qiita-image-store.s3.amazonaws.com/0/320955/5c36bc07-6813-5350-3654-9fef937d10ce.png)

たったこれだけの操作でUIとプログラムが連携されます。超簡単です。


### サンプルコードを使ってみる

画面左のナビゲーションからViewController.swiftを開き、ここにコードを書いていきます。

こちらからApple公式の[サンプルコード](https://developer.apple.com/library/archive/samplecode/SpeakToMe/Listings/SpeakToMe_ViewController_swift.html#//apple_ref/doc/uid/TP40017110-SpeakToMe_ViewController_swift-DontLinkElementID_6)を参照できます。
（このサンプルコードについては[こちらの記事](https://qiita.com/shu223/items/ca8e62803e83e396c177)の説明が役に立ちます。）

このコードをViewController.swiftにコピーし、これを適宜書き換えながらだっふんだアプリを作っていくことにしましょう。


### フレームワークをインポート

前述の通りSpeechは音声認識のためのフレームワークです。

すでにUIKitとSpeechはインポートされていると思いますが、ドリフのオチの音を鳴らしたいのでAV Foundationを追加しておきます。

```ViewController.swift
import UIKit
import Speech
import AVFoundation
```


### 変数の準備など

だっふんだアルゴリズムに必要なグローバル変数を宣言しておきます。

```ViewController.swift
public class ViewController: UIViewController, SFSpeechRecognizerDelegate {
   //変数の宣言
    var isRecognized = false //音声認識されたら立てるフラグ
    var isDafunda = false //だっふんだと認識されたら立てるフラグ
    var recognitionResult: String = "" //認識結果のテキストを入れる変数
}
```

また、アプリ起動時にはmainTextに「へんなおじさん」テキストが1行だけ表示されるようにしておきます。

```ViewController.swift
  public override func viewDidLoad() {
        super.viewDidLoad()
        recordButton.isEnabled = false
        //起動時のテキスト
        mainText.text = "へんなおーじさん だから へんなおーじさん\n"
    }
```


### ボタンが押された時の処理を実装する

ボタンが押された時の操作を記述するため、もう一度Main.storyboardに戻ります。
先ほどと同様、Controlを押しながらViewController.swiftにボタンをドラッグするのですが、このときConnection項目をActionに設定します。

すると、ボタンが押された時に実行されるメソッドが簡単に作れます。

```ViewController.swift
    @IBAction func StartRecording(_ sender: Any) {
    }
```

それではボタンをタップしたときの処理を記述していきます。

音声認識中であれば音声認識をストップ、そうでなければスタート。
スタートさせるときには、フラグなどを初期化したうえで音声認識のメソッドを呼び出します。
ストップさせるときには、音声認識メソッドで立てられたフラグに応じて別々のメソッドを呼び出します。

```ViewController.swift
    @IBAction func startRecording(_ sender: Any) {
        
        if audioEngine.isRunning {
 
            //音声認識中であれば音声認識をストップ
            audioEngine.stop()
            recognitionRequest?.endAudio()
            recordButton.isEnabled = false
            recordButton.setTitle("Done", for: .disabled)

            //音声認識後のフラグによって異なるメソッドを呼び出す
            if isRecognized == false {
                recognitionFailed() //認識に失敗していた場合のメソッド
            } else  {
                if isDafunda == false {
                    otherWordsRecognized() //だっふんだ以外の言葉を認識していた場合のメソッド
                } else {
                    dafundaRecognized() //だっふんだと認識されていた場合のメソッド
                }
            }
        } else {
 
            //前回の音声認識でだっふんだの認識に成功していた場合のみmainTextを初期化
            if isRecognized == true {
                if isDafunda == true {
                    mainText.text = "へんなおーじさん だから へんなおーじさん\n"
                }
            }

            //音声認識を開始したあとで用いるフラグなどを初期化
            isDafunda = false
            isRecognized = false
            recognitionResult = ""

            //音声認識中でないので音声認識をスタート
            recordButton.setTitle("Done", for: [])
            try! startRecording()
            
        }
    }
```


### 認識結果に応じてフラグを立てる

上記のボタンによって音声認識をスタートすると音声認識メソッドが呼び出されます。
音声認識結果に応じてフラグを立てられるようこのメソッドを適宜書き換えていきましょう。

```ViewController.swift
        recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { result, error in
            var isFinal = false
            
            if let result = result {
                
                isFinal = result.isFinal
                
                //認識結果を取得
                self.recognitionResult = result.bestTranscription.formattedString
                
                //認識結果によってフラグの値を変える
                if recognitionResult == "" { //認識に失敗した場合
                    self.isRecognized = false
                    self.isDafunda = false
                } else {
                    if recognitionResult == "だっふんだ" { //だっふんだと認識した場合
                        self.isRecognized = true
                        self.isDafunda = true
                    } else { //だっふんだと認識しなかった場合
                        self.isRecognized = true
                        self.isDafunda = false
                    }
                }
            }
```   


### フラグに応じて呼び出されるメソッドをそれぞれ実装

上記のボタンによって音声認識をストップすると立っているフラグに応じて別々のメソッドが呼び出されます。

認識に失敗していた場合に呼び出されるメソッドでは、mainTextの「へんなおじさん」テキストが倍増します。
whatYouSaidのテキストには「？」と表示させます。

```ViewController.swift
    func  recognitionFailed() {
        mainText.text = mainText.text + mainText.text
        whatYouSaid.text = "?"
    }
```            

「だっふんだ」以外の言葉を認識していた場合に呼び出されるメソッドでも、mainTextの「へんなおじさん」テキストが倍増します。
whatYouSaidにはそのとき認識された言葉を表示させます。

```ViewController.swift
   func otherWordsRecognized() {
        mainText.text = mainText.text + mainText.text
        whatYouSaid.text = self.recognitionResult
    }
```            

「だっふんだ」と認識していた場合に呼び出されるメソッドでは、mainTextに「🤪「ドリフ大爆笑DVD」🤪」「 🤪購買依頼登録しました🤪」と表示させます。
whatYouSaidには「だっふんだ！」と表示させます。

さらに、ここでドリフのオチの音の音源を再生しましょう。
サウンド再生については[こちらの記事](https://picolica.com/2017/02/25/swift3-play-sound/)を参照しました。

```ViewController.swift
    func dafundaRecognized() {
        
        self.mainText.text = "🤪「ドリフ大爆笑DVD」🤪\n 🤪購買依頼登録しました🤪"
        self.whatYouSaid.text = "だっふんだ！"

        //オチの音のファイルのパスを指定
        let soundFilePath = Bundle.main.path(forResource: "ochi", ofType: "mp3")!
        let sound:URL = URL(fileURLWithPath: soundFilePath)

        // AVAudioPlayerのインスタンスを作成
        do {
            self.audioPlayerInstance = try AVAudioPlayer(contentsOf: sound, fileTypeHint:nil)
        } catch {
            print("AVAudioPlayerインスタンス作成失敗")
        }
        self.audioPlayerInstance.prepareToPlay()
         
        //サウンド再生
        self.audioPlayerInstance.currentTime = 0
        self.audioPlayerInstance.play()    
        
    }
```            


### なぜかオチの音が鳴らない

以上でおおよその実装は完了ですが、せっかくのドリフのオチの音が鳴りません。
音声認識のみ、あるいはサウンド再生のみであれば問題ないのですが、これらを同時に行おうとする場合、設定が競合してサウンド再生に失敗することがあります。

これを回避するためサウンド再生の直前で下記のようにカテゴリを再設定しておきます。

```ViewController.swift
        //再生と録音を同時に可能になるよう設定
        let session = AVAudioSession.sharedInstance()
        try! session.setCategory(AVAudioSessionCategoryPlayAndRecord)
        try! session.setActive(true)
``` 


### 音声認識の判定について

上記ではだっふんだと言えばだっふんだと音声認識ができる前提で書いていますが、実際は必ずしもそう認識されるとは限りません。

一般的な語彙であればかなりの精度で認識されるのですが、「だっふんだ」だと厳しいこともあるようです。
何度も優しくだっふんだと呼びかけてデバッグしながら適当にif文を書き換え、誤認識されたテキストでもisDafundaがtrueになるよう調整してみてください。
文字列の[カタカナ変換](https://qiita.com/uhooi/items/882c1066d92f05c68ec4)も役に立ちます。


### 購買依頼の登録について

今回はフロントのみの実装なので「購買依頼登録しました」とだけ表示させていますが、[次回の記事](https://qiita.com/monamona/items/ba4d0b0b861fe9676b3a)ではODataのCreateメソッドで品目「ドリフ大爆笑DVD」の購買依頼がSAP S/4HANAの実機上で登録されるよう、フロントエンドの改修・バックエンドの実装をやっていきます。


### まとめ

以上のように手探りながらもだっふんだアプリのフロント部分を実装してみました。

上述の通り私は今回ほぼ初めてiOSアプリを作ったのですが、Apple社が"Everyone can code"を標榜していることからも分かるようにSwiftは初心者にとても優しい言語だと感じました。

また、Swiftは（ABAPに比して）ネット上にTipsやサンプルコードがざくざく埋蔵されています。
がんばってググればどうにかなることが多いので非常に心強いです。

ちなみにここまでの実装で「だっふんだ」と言うとドリフのオチの音が鳴るだけの楽しいアプリとして使用することができます。


＊続き：「だっふんだ」と言うとSAP S/4HANAで購買依頼伝票を打てるiOSアプリを実装する(2/2)↓
https://qiita.com/monamona/items/ba4d0b0b861fe9676b3a