Apple Watchで最寄り駅を取得する

# 重要
**APIの実装が書いた当時と違ってGraphQLになっているのでこのコードはそのままでは動きません！Apolloなどを使ってsapi.tinykitten.meを叩いてください！**

## はじめに
昔最寄り駅を取得するAPIを作ったんですけど、割と応用範囲が広そうなので今日届いたApple Watch Series 3で最寄り駅を取得するアプリを作ってみました。

蛇足ですが最寄り駅を取得するAPIの初版を作った時のセッションを貼っておきます。オタク特有の早口
[https://youtu.be/zJ92TUjhdEw?t=15780](https://youtu.be/zJ92TUjhdEw?t=15780)

## 最寄り駅クソAPIについて
https://sapi.tinykitten.me/
でホストしてます。
~~Swagger~~
~~[https://sapi.tinykitten.me/swaggerui](https://sapi.tinykitten.me/swaggerui)~~
GitHub
[https://github.com/TinyKitten/StationAPI](https://github.com/TinyKitten/StationAPI)
~~どうでもいいけどgoaで作ってます。goとgoaはいいぞ。~~
**今はPhoenix+Elixir+GraphQLで作ってます。SwaggerもGraphQLになったので消えました。**

## リポジトリ
こ↑こ↓
[https://github.com/TinyKitten/NearStationSwift](https://github.com/TinyKitten/NearStationSwift)

## 実装

## プロジェクト作成
これはポチポチするだけです。猿でもできます。いや猿に失礼か。
<img width="1512" alt="スクリーンショット 2018-10-27 1.10.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/244632/17bd8fab-3379-1913-7b13-fe3ca95b09a4.png">
<img width="1512" alt="スクリーンショット 2018-10-27 1.10.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/244632/8438a85e-55e5-a001-fee4-8fd5a86dc68c.png">


### Info.plistに位置情報許可の設定を追加
まずiOS App側とWatchKit Extension側のInfo.plistに「Privacy - Location When In Use Usage Description」を追加してください。
<img width="1137" alt="スクリーンショット 2018-10-27 1.04.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/244632/1ad0c9ea-85cc-b9e2-2462-ae7a4ab263e7.png">
っていうかこれを知らない人はこの記事あまり読むとは思えないんですけど。

### 位置情報を取得する共通コードを書く
~~書くって言いながら借りパクですけど~~

[【Swift 3】Apple Watchで位置情報取得して表示する
](https://qiita.com/MilanistaDev/items/5121dfb8779f6a12f306)

こちらの記事のLocationManager.swiftをお借りしました。謝謝。
今回はCommonグループの中に突っ込みました。
<img width="270" alt="スクリーンショット 2018-10-27 1.07.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/244632/36869feb-3a78-80fa-9a27-80da7aac859a.png">
ファイルを開いて右パネル（インスペクタ）のTarget MembershipにiOSアプリプロジェクトとWatchKit Extensionプロジェクトのチェックボックスをオンにしておかないと「あんたはうちのファイルではありません！」と親に捨てられたときのような悲しみを味わうことになるかと思うので忘れずに変えておいてください。
<img width="294" alt="スクリーンショット 2018-10-27 1.07.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/244632/9462cf13-f3ff-d55a-b151-3c6f9d1aff2e.png">

### 最寄り駅を取得する共通コードを書く
```Network.swift
//
//  Network.swift
//  NearStation WatchKit Extension
//
//  Created by TinyKitten on 2018/10/25.
//  Copyright © 2018 TinyKitten. All rights reserved.
//

import Foundation
import RxSwift
import Alamofire

class Network: NSObject {
    func getStation(lat: Float, lon: Float) -> Observable<[String: Any]> {
        return Observable.create { observer in
            let urlTemplate = "https://sapi.tinykitten.me/v1/station/near?lat=%f&lon=%f"
            let url = String(format: urlTemplate, lat, lon)
            Alamofire.request(url)
            .responseJSON(completionHandler: { res in
                if res.data != nil && res.error == nil {
                    let json = try! JSONSerialization.jsonObject(with: res.data!, options: JSONSerialization.ReadingOptions.allowFragments)
                    observer.onNext(json as! [String : Any])
                } else {
                    observer.onError(res.error!)
                }
            })
            return Disposables.create()
        }
    }
}
```
ひゃー。なんとも手抜き。
**RxSwift**と**Alamofire**を使ってます。ほんとはWebSocketを使いたかったんですけどそこまで凝視することもないだろうしwatchOS対応が怪しそうだったのでHTTPで通信することにしました。SwiftyJSONはなんか非対応っぽかったので使ってません。

### iOS側コードを実装
iOS側で位置情報を許可しないとWatchでも位置情報取れない？みたいなので先にiOS側で最寄り駅を表示するコードを書いちゃいましょう。

```ViewController.swift
//
//  ViewController.swift
//  NearStation
//
//  Created by TinyKitten on 2018/10/25.
//  Copyright © 2018 TinyKitten. All rights reserved.
//

import UIKit
import CoreLocation

class ViewController: UIViewController {

    @IBOutlet weak var stationNameLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        LocationManager.Singleton.sharedInstance.startUpdatingLocation()
        
        let center = NotificationCenter.default
        center.addObserver(self,
                           selector: #selector(type(of: self).fetchStation(notification:)),
                           name: NSNotification.Name(rawValue: LMLocationUpdateNotification),
                           object: nil)
    }

    
    @objc func fetchStation(notification: Notification) {
        let infoDic: Dictionary = notification.userInfo!
        let location: CLLocation? = infoDic[LMLocationInfoKey] as? CLLocation
        let coordinate = location!.coordinate
        
        let lat = (coordinate.latitude.description as NSString).floatValue
        let lon = (coordinate.longitude.description as NSString).floatValue
        
        Network().getStation(lat: lat, lon: lon)
            .subscribe(
                onNext: { data in
                    let white = UIColor(red: 255.0, green: 255.0, blue: 255.0, alpha: 1.0)
                    self.stationNameLabel.textColor = white
                    let stationName = data["station_name"] as! String
                    self.stationNameLabel.text = stationName + "駅"
            },
                onError: { error in
                    let red = UIColor(red: 255.0, green: 0.0, blue: 0.0, alpha: 1.0)
                    self.stationNameLabel.textColor = red
                    self.stationNameLabel.text = "エラーが発生しました"
            }
        )
    }
}
```

位置情報が変わったらリクエストしに行って表示を書き換える感じですね。
Storyboardはこんな感じ。ただ出すだけ。
<img width="1552" alt="スクリーンショット 2018-10-27 1.24.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/244632/976875ea-b384-726d-7f03-c2ff2ed1e485.png">
実際デバッグしてみるとこうなるかと。
<img width="414" alt="IMG_1524.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/244632/a00aa8ab-3e8e-a025-9905-bf9fc3beeaab.png">
やったぜ。

### Apple Watch側で最寄り駅を表示する
iOSで表示できればもうApple Watchでもそんなに変わりはありません。

```InterfaceController.swift
//
//  InterfaceController.swift
//  NearStation WatchKit Extension
//
//  Created by TinyKitten on 2018/10/25.
//  Copyright © 2018 TinyKitten. All rights reserved.
//

import WatchKit
import Foundation

class InterfaceController: WKInterfaceController {

    @IBOutlet weak var stationNameLabel: WKInterfaceLabel!

    override func awake(withContext context: Any?) {
        super.awake(withContext: context)
        
        // Configure interface objects here.
        self.stationNameLabel.setAttributedText(NSAttributedString(string: "読み込み中...", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 24.0, weight: UIFont.Weight.bold)]))

        LocationManager.Singleton.sharedInstance.startUpdatingLocation()
        
        let center = NotificationCenter.default
        center.addObserver(self,
                           selector: #selector(type(of: self).fetchStation(notification:)),
                           name: NSNotification.Name(rawValue: LMLocationUpdateNotification),
                           object: nil)
    }
    
    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()
    }
    
    override func didDeactivate() {
        // This method is called when watch view controller is no longer visible
        super.didDeactivate()
    }
    
    @objc func fetchStation(notification: Notification) {
        let infoDic: Dictionary = notification.userInfo!
        let location: CLLocation? = infoDic[LMLocationInfoKey] as? CLLocation
        let coordinate = location!.coordinate

        let lat = (coordinate.latitude.description as NSString).floatValue
        let lon = (coordinate.longitude.description as NSString).floatValue
                
        let net = Network()
        net.getStation(lat: lat, lon: lon)
            .subscribe(
                onNext: { data in
                    let stationName = data["station_name"] as! String
                    let white = UIColor(red: 255.0, green: 255.0, blue: 255.0, alpha: 1.0)
                    self.stationNameLabel.setTextColor(white)
                    self.stationNameLabel.setAttributedText(NSAttributedString(string: stationName + "駅", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 24.0, weight: UIFont.Weight.bold)]))
            },
                onError: { error in
                    let red = UIColor(red: 255.0, green: 0.0, blue: 0.0, alpha: 1.0)
                    self.stationNameLabel.setTextColor(red)
                    self.stationNameLabel.setAttributedText(NSAttributedString(string: "エラーが発生しました。", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 24.0, weight: UIFont.Weight.bold)]))
            }
        )
    }
}
```
ちょっと変わってるのがテキストと色の書き換えが代入ではなくメソッドになっているのと、Storyboardで太文字にする方法が見当たらなかったのでコードで太文字にしているところです。
<img width="378" alt="スクリーンショット 2018-10-27 1.28.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/244632/e8582ed3-92c4-76fe-8b60-8cc5fed1f2c7.png">
\ﾊﾟﾝ/ﾖｯｼｬｗｗｗｷﾀｧｧｧｧｧｧｧｱｱｱｱｱｗｗｗｗｗｗｗｗｗｗｗ（高い声で）ｳﾜﾔｯﾀｧｧｧｧｧｧｱｱｱｱｱｱ

**こんなもの作ってどうするんでしょうね。**

おしまい。

## 編集履歴

- 2018/10/28 サンプルプログラムにバグを発見したため修正
- 2018/10/29 khskさんからの編集リクエストをマージしました卍　ををになっていた