sakura.ioから得たデータを元にiOSアプリの地図に経路を表示

#はじめに
お久しぶりです。Lily.Mameokaです。以前、sakura.ioで位置情報を受信してそれを地図上に表示することをしました。(これ→https://qiita.com/LilyMameoka/items/4dd8818f6eff3a39ad45)
その続きでございます。

#こんな人に見てほしい！
・sakura.ioとiOSアプリを通信させたい!
・SwiftでWebSocketしたい!
・SwiftでMapKit使って経度を地図上に表示したい!

#何作るんだ
今回はsakura.ioからのデータをSwiftで扱っていきます。とりあえず、Swiftで扱えるようにして、現在地からモジュールまでの経路を表示します。まぁ、私はJavaScriptもHTMLもょゎょゎなので、Swiftで扱いたいな、ということです。

#本題
今回は前回の続きとして見てくださいませ。arduinoの方の設定とかWebSocket連携サービスの設定とかは前回のままです。
あ、これやる前に、
presentLocation(現在地に飛ぶボタン)
get(データを取得するボタン)
route(ルートを表示するボタン)
という名前の画像をAssets.xcassetsに入れといてください。

```swift:AppDelegate.swift
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }

    func applicationWillResignActive(_ application: UIApplication) {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
    }

    func applicationDidEnterBackground(_ application: UIApplication) {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    }

    func applicationWillEnterForeground(_ application: UIApplication) {
        // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
    }

    func applicationDidBecomeActive(_ application: UIApplication) {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    }

    func applicationWillTerminate(_ application: UIApplication) {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    }


}
```

```swift:ViewController.swift
import UIKit
import Starscream
import CoreLocation
import MapKit

class customPin: NSObject, MKAnnotation {
    var coordinate: CLLocationCoordinate2D
    var title: String?
    var subtitle: String?
    
    init(pinTitle:String, pinSubTitle:String, location:CLLocationCoordinate2D) {
        self.title = pinTitle
        self.subtitle = pinSubTitle
        self.coordinate = location
    }
}

class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
    
    //UI
    var presentLocationButton: UIButton!
    var mapView: MKMapView!
    
    //WebSocket
    var socket: WebSocket?
    
    //location
    var location: CLLocation!
    let locationManager = CLLocationManager()
    var moduleLat: Float = 0
    var moduleLng: Float = 0
    var destinationLocation = CLLocationCoordinate2D(latitude:0.0, longitude:0.0)
    var locationFlag: Bool = false
    var latFlag: Bool = false
    var lngFlag: Bool = false
    
    //tabBar
    var underTabBar: UITabBar!

    override func viewDidLoad() {
        super.viewDidLoad()
        //現在地の取得
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
        locationManager.requestWhenInUseAuthorization()
        locationManager.startUpdatingLocation()
        
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        createTabBar()
        createMapView()
        createPresentLocationButton()
        setMapCenter()
        
    }
    
}
```

```swift:mapFunction.swift
import MapKit

extension ViewController {
    
    func createPresentLocationButton() {
        presentLocationButton = UIButton(frame: CGRect(x: self.view.frame.width - 50, y: mapView.frame.height / 3, width: 40, height: 40))
        presentLocationButton.backgroundColor = .white
        presentLocationButton.layer.cornerRadius = 3.0
        presentLocationButton.setImage(UIImage(named: "presentLocation"), for: .normal)
        presentLocationButton.tintColor = UIColor(red: 64/255, green: 192/255, blue: 240/255, alpha: 1)
        presentLocationButton.addTarget(self, action: #selector(presentLocationButton_TouchUpInside), for: .touchUpInside)
        mapView.addSubview(presentLocationButton)
    }

    @objc func presentLocationButton_TouchUpInside(sender: UIButton) {
        setMapCenter()
    }

    func setMapCenter() {
        mapView.setCenter(mapView.userLocation.coordinate, animated: true)
        mapView.userTrackingMode = MKUserTrackingMode.follow
        mapView.userTrackingMode = MKUserTrackingMode.followWithHeading
    }
    
    func createMapView() {
        mapView = MKMapView()
        mapView.frame = view.frame
        mapView.translatesAutoresizingMaskIntoConstraints = false
        mapView.setCenter(mapView.userLocation.coordinate, animated: true)
        mapView.userTrackingMode = MKUserTrackingMode.follow
        mapView.userTrackingMode = MKUserTrackingMode.followWithHeading
        mapView.delegate = self
        //compass
        let compass = MKCompassButton(mapView: mapView)
        compass.compassVisibility = .visible
        compass.frame = CGRect(x: self.view.frame.width - 50, y: (mapView.frame.height / 3) - 60 , width: 40, height: 40)
        mapView.addSubview(compass)
        mapView.showsCompass = false
        //scale
        let scale = MKScaleView(mapView: mapView)
        scale.legendAlignment = .leading
        scale.frame = CGRect(x: 40, y: 100 , width: mapView.frame.width / 2, height: 5)
        mapView.addSubview(scale)
        mapView.mapType = .standard
        self.view.addSubview(mapView)
        
        mapView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
        mapView.bottomAnchor.constraint(equalTo: underTabBar.topAnchor, constant: 0).isActive = true
        mapView.widthAnchor.constraint(equalToConstant: view.frame.width).isActive = true
        
        self.view.bringSubviewToFront(mapView)
    }
    
    func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
        let renderer = MKPolylineRenderer(overlay: overlay)
        renderer.strokeColor = UIColor.blue
        renderer.lineWidth = 4.0
        return renderer
    }
    
    func createRoute(sourceLocation:CLLocationCoordinate2D, destinationLocation:CLLocationCoordinate2D) {
        let destinationPin = customPin(pinTitle: "Here!", pinSubTitle: "", location: destinationLocation)
        mapView.removeAnnotation(destinationPin)
        self.mapView.addAnnotation(destinationPin)
        let sourcePlaceMark = MKPlacemark(coordinate: sourceLocation)
        let destinationPlaceMark = MKPlacemark(coordinate: destinationLocation)
        let directionRequest = MKDirections.Request()
        directionRequest.source = MKMapItem(placemark: sourcePlaceMark)
        directionRequest.destination = MKMapItem(placemark: destinationPlaceMark)
        directionRequest.transportType = .automobile
        let directions = MKDirections(request: directionRequest)
        directions.calculate { (response, error) in
            guard let directionResonse = response else {
                if let error = error {
                    print("we have error getting directions==\(error.localizedDescription)")
                }
                return
            }
            let route = directionResonse.routes[0]
            self.mapView.addOverlay(route.polyline, level: .aboveRoads)
            let rect = route.polyline.boundingMapRect
            self.mapView.setRegion(MKCoordinateRegion(rect), animated: true)
        }
    }
    
    @objc func routeButton_TouchUpInside() {
        if locationFlag {
            createRoute(sourceLocation: location.coordinate, destinationLocation: destinationLocation)
        }
    }
    
}
```

```swift:locationFunction.swift
import CoreLocation

extension ViewController {
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        
        location = locations[locations.count - 1]
        if location.horizontalAccuracy > 0 {
            self.locationManager.stopUpdatingLocation()
            print("longitude = \(location.coordinate.longitude), latitude = \(location.coordinate.latitude)")
        }
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error)
    }
    
}
```

```swift:getDataFunction.swift
import Starscream
import CoreLocation

extension ViewController : WebSocketDelegate {
    
    @objc func getData_TouchUpInside() {
        webSocketGetData()
    }
    
    //WebSocket
    func webSocketGetData() {
        self.socket = WebSocket(url: NSURL(string: "wss://api.sakura.ioから始まるURL(sakura.ioコントロールパネルで確認して)")! as URL)
        self.socket?.delegate = self
        self.socket?.connect()
    }
    
    func websocketDidConnect(socket: WebSocketClient) {
        print("did connect")
    }
    
    func websocketDidDisconnect(socket: WebSocketClient, error: Error?) {
    }
    
    func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {
        print("message:", text)
        upDateData(data: text)
    }
    
    func websocketDidReceiveData(socket: WebSocketClient, data: Data) {
        print("data:", data)
    }
    
    func upDateData(data : String) {
        
        let nsData = NSString(string: data)
        if data.contains(#"{"channel":0,"type":"f","value":"#) {
            let latMarkerLocation = nsData.range(of: #"{"channel":0,"type":"f","value":"#).location
            let latMarkerLength = nsData.range(of: #"{"channel":0,"type":"f","value":"#).length
            if Float(data[data.index(data.startIndex, offsetBy: latMarkerLength + latMarkerLocation)...data.index(data.startIndex, offsetBy: latMarkerLength + latMarkerLocation + 8)]) != nil {
                moduleLat = Float(data[data.index(data.startIndex, offsetBy: latMarkerLength + latMarkerLocation)...data.index(data.startIndex, offsetBy: latMarkerLength + latMarkerLocation + 8)])!
            }
            latFlag = true
        }
        if data.contains(#"{"channel":1,"type":"f","value":"#) {
            let lngMarkerLocation = nsData.range(of: #"{"channel":1,"type":"f","value":"#).location
            let lngMarkerLength = nsData.range(of: #"{"channel":1,"type":"f","value":"#).length
            if Float(data[data.index(data.startIndex, offsetBy: lngMarkerLength + lngMarkerLocation)...data.index(data.startIndex, offsetBy: lngMarkerLength + lngMarkerLocation + 8)]) != nil {
                moduleLng = Float(data[data.index(data.startIndex, offsetBy: lngMarkerLength + lngMarkerLocation)...data.index(data.startIndex, offsetBy: lngMarkerLength + lngMarkerLocation + 8)])!
            }
            lngFlag = true
        }
        if latFlag && lngFlag && moduleLat != 0.0 && moduleLng != 0.0 {
            destinationLocation = CLLocationCoordinate2D(latitude:CLLocationDegrees(moduleLat), longitude: CLLocationDegrees(moduleLng))
            locationFlag = true
        }
        
    }
    
}
```

```swift:tabBarFunction.swift
import UIKit

extension ViewController : UITabBarDelegate {
    
    func createTabBar() {
        underTabBar = UITabBar()
        underTabBar.frame = view.frame
        underTabBar.translatesAutoresizingMaskIntoConstraints = false
        underTabBar.barTintColor = UIColor.white
        underTabBar.unselectedItemTintColor = UIColor(red: 64/255, green: 192/255, blue: 240/255, alpha: 1)
        underTabBar.tintColor = UIColor(red: 64/255, green: 192/255, blue: 240/255, alpha: 1)
        let getData:UITabBarItem = UITabBarItem(title: nil, image: UIImage(named:"get"), tag: 1)
        let makeRoute:UITabBarItem = UITabBarItem(title: nil, image: UIImage(named:"route"), tag: 2)
        underTabBar.items = [getData,makeRoute]
        underTabBar.delegate = self
        self.view.addSubview(underTabBar)
        underTabBar.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
        underTabBar.widthAnchor.constraint(equalToConstant: view.frame.width).isActive = true
        underTabBar.heightAnchor.constraint(equalToConstant: 90).isActive = true
        self.view.bringSubviewToFront(underTabBar)
    }
    
    func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
        switch item.tag{
        case 1:
            getData_TouchUpInside()
        case 2:
            routeButton_TouchUpInside()
        default : return
        }
    }

}
```
```ruby:Podfile
platform :ios, '9.0'

target 'PoSSo' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for PoSSo

    pod 'Starscream'

end
```

Info.plistは、
Privacy - Location When In Use Usage Description
Privacy - Location Usage Description
を加えて。


ライブラリをCocoaPodsで入れるんで、pod initしてからPodfileを編集して、pod install。そして、.xcworkspaceから開くことを忘れないように。

あ、.storyboardは使いません。全部コードでの実装です。
データを取るとき、StarscreamでJSONを受信できればよかったんですけど、できないので(ただのStringになってる)、非常にクレバーとは言い難い原始的な方法でデータを取得しています。許して。
データ取得などがただのUIButtonじゃなくてUITabBarにしているのは、この後機能を追加する際にやりやすくするためだから、UIButtonにしても良い。
#追補
あとでデータの受信だけのはこれを見て
https://qiita.com/LilyMameoka/items/5402270fd3031d6dd4ad