#はじめに
お久しぶりです。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に入れといてください。
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:.
}
}
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()
}
}
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)
}
}
}
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)
}
}
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
}
}
}
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
}
}
}
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