Google Maps Elevation APIを使って海抜計測アプリを作成してみた

私にとって初のiOSアプリをリリースしたので、App Storeに公開するまでの軌跡を残しておく。

# **:bulb: 作ったもの**
いつでもどこでも世界中の海抜を測れるアプリ『[KAIVAP](https://t.co/drCiUUiR61)』
![AppIcon_180.png](https://qiita-image-store.s3.amazonaws.com/0/190870/a8026502-57fc-0476-d848-8710375acee4.png)
<img width="200" alt="Simulator-Screen-Shot---iPhone-8---2018-02-12-at-12.23.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/190870/914b1214-de42-1638-a9ab-29116c5e3f63.png">

# **:hammer: 使ったもの**
- 開発ツール
    - [Xcode](https://developer.apple.com/jp/xcode/)

- ライブラリ管理ツール
    - [Carthage](https://github.com/Carthage/Carthage)
    - [CocoaPods](https://cocoapods.org/)

- フレームワーク
    - [APIKit](https://github.com/ishkawa/APIKit)
    - [Himotoki](https://github.com/ikesyo/Himotoki)

- API
    - [GoogleMaps](https://developers.google.com/maps/documentation/ios-sdk/?hl=ja)
    - ~~[標高API](https://maps.gsi.go.jp/development/api.html)~~
    - [Google Maps Elevation API](https://developers.google.com/maps/documentation/elevation/start?hl=ja)

# **:pushpin: 構築手順**
## **きっかけ**
何十本とアプリをリリースしているエンジニアの方にお会いし刺激を受けた。
取り敢えず何でもいいから0→1でアプリをリリースしようと思い、歩いているときに「ここは海抜〇〇mです」と書かれた電柱を見てGoogleMapから海抜（標高）を計測できるアプリを作ろうと決める！

## **アプリ作成**
主要部分はこんな感じ。
100行ぐらいしか書いてないですね笑

```swift:ViewController.swift
import Foundation
import UIKit
import APIKit
import GoogleMaps
import GooglePlaces

class ViewController: UIViewController, CLLocationManagerDelegate, GMSMapViewDelegate {

    var locationManager: CLLocationManager!
    var placesClient: GMSPlacesClient!
    var currentLocation: CLLocationCoordinate2D?
    var currentCameraPosition: GMSCameraPosition?
    var zoomLevel: Float = 15.0
    var mapView: GMSMapView!

    // 一部省略

    @IBOutlet weak var elevationLabel: UILabel!
    @IBOutlet weak var calculateButton: UIButton!
    @IBOutlet weak var resetButton: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        setupView()
    }

    func setupView() {
        let camera = GMSCameraPosition.camera(withLatitude: -33.86, longitude: 151.20, zoom: 1.0)
        let footerViewHeight: CGFloat = 88.0
        let mapViewSize = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height-footerViewHeight)
        mapView = GMSMapView.map(withFrame: mapViewSize, camera: camera)
        mapView.isMyLocationEnabled = true
        do {
            mapView.mapStyle = try GMSMapStyle(jsonString: mapStyle)
        } catch {
            NSLog("One or more of the map styles failed to load. \(error)")
        }
        mapView.delegate = self

        let marker = GMSMarker()
        marker.position = CLLocationCoordinate2D(latitude: -33.86, longitude: 151.20)
        marker.title = "Sydney"
        marker.snippet = "Australia"
        marker.map = mapView
        view.addSubview(mapView)
        view.sendSubview(toBack: mapView)

        locationManager = CLLocationManager()
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.requestAlwaysAuthorization()
        locationManager.distanceFilter = 50
        locationManager.startUpdatingLocation()
        locationManager.delegate = self
        
        placesClient = GMSPlacesClient.shared()
    }
    
    func calculateElevation(lat: Double, lng: Double) {
        let request = GetElevationRequest(lat: lat, lng: lng)
        Session.send(request) { [weak self] result in
            guard let `self` = self else { return }
            
            switch result {
            case .success(let elevation):
                if let elevation = elevation.results.first?.elevation {
                    if elevation == 0 {
                        self.elevationLabel.textColor = UIColor.init(hex: "CB1B45")
                        self.elevationLabel.text = "0(計測不能)"
                    } else {
                        if elevation > 5 {
                            self.elevationLabel.textColor = UIColor.init(hex: "333333")
                        } else {
                            self.elevationLabel.textColor = UIColor.init(hex: "DB4D6D")
                        }
                        let text = NSString(format: "%.1f", elevation)
                        self.elevationLabel.text = text as String
                    }
                }
            case .failure(let error):
                print("error: \(error)")
            }
        }
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let location = locations.last {
            currentLocation = location.coordinate
            let camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude,
                                                  longitude: location.coordinate.longitude,
                                                  zoom: zoomLevel)
            
            if mapView.isHidden {
                mapView.isHidden = false
                mapView.animate(to: camera)
            } else {
                mapView.animate(to: camera)
                calculateElevation(lat: location.coordinate.latitude, lng: location.coordinate.longitude)
            }
        }
    }

    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        switch status {
        case .restricted:
            print("Location access was restricted.")
        case .denied:
            print("User denied access to location.")
            mapView.isHidden = false
        case .notDetermined:
            print("Location status not determined.")
        case .authorizedAlways: fallthrough
        case .authorizedWhenInUse:
            print("Location status is OK.")
        }
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        locationManager.stopUpdatingLocation()
        print("Error: \(error)")
    }
    
    func mapView(_ mapView: GMSMapView, didChange position: GMSCameraPosition) {
        currentCameraPosition = position
    }

    @IBAction func didPressCalculateButton(_ sender: Any) {

        if let currentCameraPosition = currentCameraPosition {
            calculateElevation(lat: currentCameraPosition.target.latitude, lng: currentCameraPosition.target.longitude)
        }
    }
    
    @IBAction func didPressResetButton(_ sender: Any) {
        
        if let currentLocation = currentLocation {
            self.mapView.animate(toLocation: currentLocation)
            self.self.calculateElevation(lat: currentLocation.latitude, lng: currentLocation.longitude)
        }
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}
```

## **実機ビルドが出来ない！？**
アプリが完成したので実機で現在地が取れているか確認しようと思い、実機ビルドに挑戦した。
が、シミュレータでは発生しなかったエラーが出現!?!?

```Command /bin/sh failed with exit code 1```

数日間ハマっていたが、下のダイアログを　Xcodeインストール時に拒否していたことが原因だと判明！
Xcodeを再インストールし、パスワードを入力して「常に許可」を選択したらエラーが消えてくれました>_<

![access_xcode.png](https://qiita-image-store.s3.amazonaws.com/0/190870/abe9c733-ff64-4881-f8ff-21633f991e49.png)
[Xcodeの「キーチェーンログインのパスワードを入力してください」パスワードが通らない](http://www.weblog-life.net/entry/xcode_debug_error)

## **エラー祭り!?!?**
やっと実機ビルドが出来る！！！と思ったら新たなエラーが。。。
今度は何やねん(ｰｰ;)

```dyld: Library not loaded: @rpath/Pods_xxx.framework/Pods_xxx```

あー。ライブラリーが読み込めていないのかー。意味の分かるエラーで助かるわー。
と思いきや!?!?
そんな優しいエラーではなかった。

[Library not loaded エラー？ここを見直そう](https://qiita.com/kakipo/items/3cd87e00bc96bc3888b2)
[Carthageを使ってライブラリを管理する](http://xyk.hatenablog.com/entry/2015/10/11/115714)

色々な対処法を試してみたが、、、エラーは一向に消えてくれない。
そこでふと思ったのが、
**実機ビルド時のみ起こるってことは証明書周りでミスってる？？**

確認してみると…
`Certificates`も`Provisioning Profiles`には何も問題がないんですよ。
キーチェーンアクセスを見ても`有効期限`も切れてないし。

数日後…やっと解決策を見つけることが出来ました！
**証明書を「常に信頼」から「システムデフォルトを使用」に変更！**

![qiita_01.png](https://qiita-image-store.s3.amazonaws.com/0/190870/f87fe2cb-eef8-070f-88a6-5b76be0daaec.png)

[dyld: Library not loaded: @rpath/libswiftCore.dylib](https://stackoverflow.com/questions/26024100/dyld-library-not-loaded-rpath-libswiftcore-dylib)

どこかのタイミングで変えてしまったんでしょうね。
デフォルトは「システムデフォルトを使用」になっています。

エラー地獄から抜け、やっとの思いで実機ビルドに成功しました！

## **Archive後のValidateで躓く**
```
iTunes Store Operation Failed
ERROR ITMS-90087: "Unsupported Architectures. The executable for ${APP_NAME}.app/Frameworks/APIKit.framework contains unsupported architectures '[x86_64, i386]'."
```

<img width="746" alt="スクリーンショット 2018-02-10 8.15.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/190870/fee54867-d2c7-451f-3181-fa5bf2ab4c05.png">

[Check and Remove Unsupported Architecture [x86_64, i386] in IPA / Archive
](https://stackoverflow.com/questions/42641806/check-and-remove-unsupported-architecture-x86-64-i386-in-ipa-archive)
上の記事を参考に、サポートされていないアーキテクチャーを外したら上手くいきました！

## **アプリ公開**
アプリを申請してからは比較的順調にリリースまで辿り着けました。
今回一番お世話になった記事を載せておきます。
↓↓↓
[iOSアプリを登録、申請して公開するまで](https://i-app-tec.com/ios/app-release.html)


# **まとめ**
プロジェクトを立ち上げてからリリースするまで、
コードを書いている時間 >>> Xcodeの設定に悩まされる時間 だった気がします笑
ですが、

- アプリ申請の流れを把握できた
- Xcodeの設定を見直すことが出来た（次回からは困らない！はず）
- iOSアプリ開発者の仲間入りが出来た
- 証明書周りの知識がほんの少しだけついた

など得るものは大きかったです！

**アプリリリースはそんなに難しくない！！！**
これからもバンバンiOSアプリをリリースしていきたいと思います！