SAP S/4HANAのデータをiPhoneでAR表示してみた(2/2)

商品をiPhoneのカメラで写すと、SAP S/4HANAから取得した在庫情報がARで浮かび上がるアプリを作ってみました。この記事ではデータ取得〜AR実装の詳細手順を紹介します。
![ar.gif](https://qiita-image-store.s3.amazonaws.com/0/320429/1c79c408-03c8-9f39-7c9e-759a4d4d519f.gif)
（※ この記事は [SAP Advent Calendar 2018](https://qiita.com/monamona/private/c55435580eaad482c7f7) の12月15日分の記事として執筆しています）

## 構築手順
大きく下記の流れで構築しています。
1. SAP S/4HANA上でデータ取得処理（OData）を実装
2. SDK for iOS AssistantでXcodeプロジェクトを生成
**3. XcodeでAR機能を追加実装**

今回は3のトピックを書きます。

 ※トピック1～2は前回の記事↓を参照。
https://qiita.com/zukahira/items/c97f49f211a70dc1b555

## 3. XcodeでAR機能を追加実装する
[前回の記事](https://qiita.com/zukahira/items/c97f49f211a70dc1b555)にて、S4HANAとデータ連携を行うXcodeプロジェクトがSDK for iOSにより自動生成しました。今回は自動生成されたXcodeプロジェクトを改造する形でAR機能を実装していきます。
#### Storyboardを追加する
まずプロジェクトのメインディレクトリ配下に"App"という名前のフォルダを作ります。今回新たに追加実装する資源はこのフォルダに入れることにします。
最初にストーリーボードを作ります。Appフォルダを右クリック＞[New File]からストーリーボードを選択して追加します。今回は"MyStoryboard"と名付けます。
<img width="990" alt="スクリーンショット 2018-12-31 22.29.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/320429/8cbff0b1-062d-d37f-2929-ee415a32b38e.png">
右上の[Library]ボタンを押下し、View Controllerをストーリーボード上にドラッグして配置します。
<img width="993" alt="スクリーンショット 2018-12-31 22.29.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/320429/40dacafd-14d3-5b5f-1bf2-b37383b5cb53.png">
<img width="995" alt="スクリーンショット 2018-12-31 22.30.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/320429/78868052-f920-c9d7-d0f7-92d707efc0c5.png">
次にSceneKit Viewを配置します。右上の[Library]ボタンから、[ARKit SceneKit View]をストーリーボードのView Controller上にドラッグします。
<img width="994" alt="スクリーンショット 2018-12-31 22.30.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/320429/f43b3b8e-39f6-5ca1-7cb3-4196e531f716.png">
配置したScene ViewをView Controllerいっぱいに広げます。次にデバイスの画面の大きさに依存してViewがズレないよう、右下のボタンからConstraintを追加します。View Controllerからの距離を0にして[Add 4 Vonstraints]を押下しましょう。
<img width="1001" alt="スクリーンショット 2018-12-31 22.30.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/320429/46fcc38e-94ad-b5e3-d71e-29465aafbfd7.png">
#### Sceneファイルを追加する
Appフォルダを右クリック＞[New File]から[SpriteKit Scene]を選択して追加します。今回は"Scene.sks"と名前をつけます。
<img width="999" alt="スクリーンショット 2018-12-31 22.31.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/320429/1601078e-9cfb-4238-d5e2-98074739406f.png">
#### Swiftファイルを追加する
Appフォルダを右クリック＞[New File]から[Cocoa Touch Class]を選択して追加します。
<img width="991" alt="スクリーンショット 2018-12-31 22.31.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/320429/830a6180-fc98-566c-757d-0dfb9cc63106.png">
名前を"Scene"、[Subclass of]にはSKSceneを指定します。
<img width="1001" alt="スクリーンショット 2018-12-31 22.31.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/320429/93b2454b-4ae4-59d9-11ea-db9232c02e71.png">
次にView Controller用のSwiftファイルを追加します。Appフォルダを右クリック＞[New File]から[Cocoa Touch Class]を選択して追加します。
<img width="985" alt="スクリーンショット 2018-12-31 22.32.21.png" src="https://qiita-image-store.s3.amazonaws.com/0/320429/c5e3c893-143f-17f3-9fc1-d91275d2a01f.png">
今回は名前を"MyViewController"とします。[Subclass of]にはUIViewControllerを指定します。
<img width="1001" alt="スクリーンショット 2018-12-31 22.32.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/320429/8a80eb14-a4d9-200e-36a7-adb554e86101.png">
Storyboardでクラスを指定してView Controllerと紐付けます。
<img width="984" alt="スクリーンショット 2019-01-02 17.25.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/320429/f03e9961-aadf-a708-4633-7cafea06679a.png">

#### 認識用画像イメージを準備する
今回はARKitの[Recognizing Images](https://developer.apple.com/documentation/arkit/recognizing_images_in_an_ar_experience)を利用して、画像イメージがカメラで検出された際にAR空間上に3Dオブジェクトを配置する動きを実装します。

まず認識用の画像イメージファイルを準備します。Recognizing Images用のイメージ画像をAssetsフォルダに配置します。[+]ボタンから[New AR Resouce Group]を選択してフォルダを作成します。
<img width="1001" alt="スクリーンショット 2018-12-31 22.32.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/320429/a162f25a-53c0-aae3-c0ed-e28031415098.png">
認識用画像ファイルをドラッグ&ドロップして登録します。データ紐付けのため、Image名にS4HANA上の品目コードを設定しておきます。また認識されるモノの実際のサイズも忘れずに設定しておきます。
<img width="992" alt="スクリーンショット 2018-12-31 22.53.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/320429/6d9b9ddb-90e8-7e74-5fa4-ae9008715f66.png">

#### plistを更新する
今回はカメラを使用するため、plistにてPrivacy - Camera Usage Descriptionを設定しておきます。アプリ初回起動時にカメラの使用許可を聞かれる際、メッセージとして表示されるものです。
これが意外と重要で、iOS10からはこの記述がないとアプリが強制終了してしまうようです。詳しくは[こちら](http://dev.classmethod.jp/smartphone/iphone/ios10-privacy-data-purpose-description/)。
<img width="999" alt="スクリーンショット 2018-12-31 22.33.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/320429/1bb85da8-5404-9d2b-c921-d7609253282a.png">

#### 初期表示Storyboardを変更する
SDKで自動生成されたデフォルトコードでは、Main.storyboardが初期表示されるものとして指定されているため、今回作成したStoryboardを指定するように変更します。

作成したView ControllerのIdentify情報にStoryboardID（今回は"App"）を指定しておきます。
<img width="989" alt="スクリーンショット 2019-01-02 4.35.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/320429/325d4f65-120f-958c-6bb8-61b0c25309e3.png">
次にAppDelegate.Swiftを書き換えます。デフォルトコードを下記のように書き換え、今回作成したStoryboardを指定します。

```AppDelegate.swift
    
    private func setRootViewController() {
        DispatchQueue.main.async {

            //デフォルトコード→コメントアウト
            //let splitViewController = UIStoryboard(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "MainSplitViewController") as! UISplitViewController
            //splitViewController.delegate = self
            //splitViewController.modalPresentationStyle = .currentContext
            //splitViewController.preferredDisplayMode = .allVisible

            //今回作成したStoryboardを指定
            let viewController = UIStoryboard(name: "MyStoryboard", bundle: Bundle.main).instantiateViewController(withIdentifier: "App")
            self.window!.rootViewController = viewController

        }
    }

```

#### セッション開始処理を実装する
ここから、メインのView Controller（MyViewController.swift）の実装をしていきます。
まず、StoryboardからSceneViewをOutlet接続します。
<img width="1008" alt="スクリーンショット 2018-12-31 22.34.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/320429/9f29d696-7688-42d6-433c-cf76399b73c0.png">

今回の実装で使用するフレームワークをインポートします。

```MyViewController.swift
import ARKit
import SAPFiori
import SAPOData
```
次にセッション開始メソッドを実装します。

```MyViewController.swift
    //method:セッション開始処理
    func startSession(){
        
        //ToastMessageを出力
        FUIToastMessage.show(
            message: "商品をスキャンしてください",
            icon: FUIIconLibrary.map.legend.zoomExtent.withRenderingMode(.alwaysTemplate),
            inView: sceneView,
            withDuration: 3.0,
            maxNumberOfLines: 1)
        
        //sceneViewの設定
        sceneView.delegate = self
        sceneView.showsStatistics = false
        let scene = SCNScene()
        sceneView.scene = scene
        
        //ImageDetection用の画像ファイル読込
        let referenceImages = ARReferenceImage.referenceImages(inGroupNamed: "AR Resources", bundle: nil)
        let configuration = ARWorldTrackingConfiguration()
        configuration.detectionImages = referenceImages
        
        //sceneViewのセッション開始
        sceneView.session.run(configuration, options: [.resetTracking, .removeExistingAnchors])
        
    }
```
データ取得処理を実装します。
（※簡単のため、今回は初期処理で一括取得するようにしています。）

```MyViewController.swift

    //在庫情報格納用変数
    var stockInfos = [StockInfo]()

    //method:在庫情報の取得処理
    func fetchStockInfoSet(_ completionHandler: @escaping () -> Void) {
        
        //AppDelegate
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        
        //クエリの作成
        let query = DataQuery().selectAll()
        do {
            //クエリ実行
            appDelegate.ytest001SRVEntities!.fetchStockInfoSet(matching: query) { StockInfoSet, error in
                if error == nil {
                    print("在庫情報の取得成功！")
                    self.stockInfos = StockInfoSet!
                } else {
                    print("在庫情報の取得失敗！だっふんだ！")
                }
                completionHandler()
            }
        }
    }
```

初期処理（viewDidLoad）で在庫情報の取得→セッション開始を行うよう下記のように実装します。

```MyViewController.swift
    override func viewDidLoad() {
        super.viewDidLoad()
        //在庫情報の取得処理
        self.fetchStockInfoSet(){
            //セッション開始処理
            self.startSession()
        }
    }
```

#### 画像イメージ認識時処理を実装する
前準備として3Dオブジェクトのための赤色・青色のイメージ画像を準備しておきます。これらは3Dオブジェクト（球体）の表面に表示される画像になります。
<img width="986" alt="スクリーンショット 2018-12-31 22.50.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/320429/28d95c9d-287b-df6b-9a6c-52817659ea0f.png">

ARSCNViewのDelegateメソッドに画像イメージ認識時の処理を実装します。

```MyViewController.swift
    //method:画像認識時の処理
    func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
        let node = SCNNode()
        if let imageAnchor = anchor as? ARImageAnchor {
            //ノード作成処理
            let markNode = getMoonNode(imageName: imageAnchor.referenceImage.name!)
            node.name = imageAnchor.referenceImage.name
            node.addChildNode(markNode)
        }
        return node
    }
    
    //method:ノード作成処理
    func getMoonNode(imageName: String) -> SCNNode {
        // 3Dオブジェクト(球体)の生成
        let sphere = SCNSphere(radius: 0.02)
        let material = SCNMaterial()
        let intStock = stockInfos.filter { $0.matnr == imageName }[0].quan
        if (intStock?.intValue())! < 5
        {
            //店舗在庫の数が5個よりも少なかったら🔴の球体を生成
            material.diffuse.contents = UIImage(named: "moon_red.jpg")
        }else{
            //店舗在庫の数が5個以上だったら🔵の球体を生成
            material.diffuse.contents = UIImage(named: "moon_blue.jpg")
        }
        sphere.materials = [material]
        let node = SCNNode()
        node.position = SCNVector3(0.0, 0.0, 0.03)
        node.geometry = sphere
        sceneView.scene.rootNode.addChildNode(node)
        return node
    }
```

#### 動作確認
実装は完了です。左上のビルドボタンを押してXcodeプロジェクトの動作確認をしましょう。
![スクリーンショット 2018-12-31 22.49.37.png](https://qiita-image-store.s3.amazonaws.com/0/320429/d93cac79-b83a-6430-907b-e988c9ff0d96.png)

iPhoneで実行してみると、こういう感じになります。
![daffunda2.GIF](https://qiita-image-store.s3.amazonaws.com/0/320429/f92073b7-ffa6-298f-edd3-9adc0731d906.gif)
S4の在庫情報([前回ブログ](https://qiita.com/zukahira/items/c97f49f211a70dc1b555)参照)と比較してみると、在庫が5個以上の品目に青球、在庫が5個より少ない品目に赤球が表示されている事が確認できます。
![data.png](https://qiita-image-store.s3.amazonaws.com/0/320429/26d5d6bc-2c37-6943-9e37-bc5aaa71e141.png)

## まとめ
今回はSCP SDK for iOSを使用し、S4HANAの情報をARで表示する簡単なアプリを作ってみました。基幹システムでは長年UI/UXが課題と言われがちでしたが、SDK for iOSを利用することでiOSの最新のUI/UXの技術を簡単に利用する事ができるようになります。モバイル化や、それに伴うUI/UXの変革は業務システムのデジタルトランスフォーメーションを推進する上でとても重要な要素となります。ARもどんな業務変革に応用できるか考えていけたら面白いですね。