iOS 標準の地図ライブラリ MapKit と位置情報取得クラス CLLocationManager を利用して地図アプリの土台になりそうなものを作ってみた。

Swift によるサンプルコード。ほぼコピペで動作する (他には Info.plist の編集が必要)。Storyboard は使わない。

今回の動作確認環境: Xcode 7.2 + Swift 2.1.1 + 実機 iPhone 6 + iOS 9.2

サンプルコードを動かすためにやること概要

  1. Xcode で新規に Single View Application のプロジェクトを作成
  2. ViewController.swift にサンプルコードをコピペ
  3. Info.plist に NSLocationWhenInUseUsageDescription または NSLocationAlwaysUsageDescription を追加

プロジェクト作成と ViewController.swift

Xcode で新規に Single View Application のプロジェクトを作成。

Swift + MapKit + CLLocationManager

プロジェクトに特別な設定は必要なし。

Swift + MapKit + CLLocationManager

以下のコードを ViewController.swift に上書きで貼り付ける。


//
//  ViewController.swift
//

import UIKit
import MapKit

class ViewController: UIViewController, UIToolbarDelegate, MKMapViewDelegate, CLLocationManagerDelegate {
    
    // 地図
    private var myMapView : MKMapView?
    
    // ツールバー
    private var myToolbar: UIToolbar?
    private var myLocationButton: UIBarButtonItem?
    
    // 位置情報
    private var myLocationManager: CLLocationManager?
    
    override func viewDidLoad() {
        
        super.viewDidLoad()
        
        // 地図
        myMapView = ViewController.createMapView(self.view, delegate: self)
        self.view.addSubview(myMapView!)
        
        // ツールバー
        (myToolbar, myLocationButton) =
            ViewController.createToolbar(
                self.view,
                target: self,
                locationButtonSelector: "onClickLocationButton:")
        self.view.addSubview(myToolbar!)
        
        // 位置情報
        myLocationManager = ViewController.createLocationManager(self)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    private static func createMapView(parentView: UIView, delegate: MKMapViewDelegate) -> MKMapView {
        
        // MKMapView の生成
        let mapView = MKMapView()
        
        // MapView のサイズを指定
        mapView.frame = parentView.bounds
        
        // MKMapView に Delegate を設定
        mapView.delegate = delegate
        
        // 中心点の緯度経度(名古屋駅)
        let lat: CLLocationDegrees = 35.1707223
        let lon: CLLocationDegrees = 136.882136
        let coordinate: CLLocationCoordinate2D = CLLocationCoordinate2DMake(lat, lon)
        
        // 縮尺
        let latDist : CLLocationDistance = 100
        let lonDist : CLLocationDistance = 100
        
        // Region を生成
        let region: MKCoordinateRegion =
            MKCoordinateRegionMakeWithDistance(coordinate, latDist, lonDist);
        
        // MKMapView に反映
        mapView.setRegion(region, animated: true)
        
        return mapView
    }
    
    private static func createToolbar(parentView: UIView,
        target: AnyObject, locationButtonSelector: Selector) -> (UIToolbar?, UIBarButtonItem?) {
        
        // ツールバー
        let toolbar =
            UIToolbar(frame: CGRectMake(0, 0, parentView.bounds.size.width, 44.0))
        toolbar.layer.position =
            CGPoint(x: parentView.bounds.width / 2, y: parentView.bounds.height - 22.0)
        
        // ツールバーの色
        toolbar.barStyle = .BlackTranslucent
        toolbar.tintColor = UIColor.whiteColor()
        toolbar.backgroundColor = UIColor.blackColor()
        
        // ボタンを生成
        let locationButton =
            UIBarButtonItem(
                title: "現在地",
                style:.Plain,
                target: target,
                action: locationButtonSelector)
        
        // ボタンをツールバーに入れる
        toolbar.items = [locationButton]
        
        return (toolbar, locationButton)
    }
    
    private static func requestLocationAuthorization(lm: CLLocationManager) {
        
        // アプリ使用中のみ位置情報を利用する場合 (「このApp使用中のみ許可」と設定情報に表示される)
        // Info.plist にキーと説明文を追加する必要がある
        // Key: NSLocationWhenInUseUsageDescription
        // Type: String
        // Value: 任意の説明文
        lm.requestWhenInUseAuthorization()

        // バックグラウンドでも位置情報を利用する場合 (「常に許可」と設定情報に表示される)
        // Info.plist にキーと説明文を追加する必要がある
        // Key: NSLocationAlwaysUsageDescription
        // Type: String
        // Value: 任意の説明文
        //lm.requestAlwaysAuthorization()
    }
    
    private static func createLocationManager(delegate: CLLocationManagerDelegate) -> CLLocationManager {
        
        // CLLocationManager に Delegate を設定
        let locationManager = CLLocationManager()
        locationManager.delegate = delegate
        
        // 位置情報取得の認証状況を取得
        let status = CLLocationManager.authorizationStatus()
        
        switch status {
        case .NotDetermined:
            // 未認証状態
            print("status: NotDetermined")
            // 位置情報取得リクエストダイアログを表示する
            requestLocationAuthorization(locationManager)
        case .Restricted:
            print("status: Restricted")
            break
        case .Denied:
            print("status: Denied")
            break
        case .AuthorizedAlways:
            // 認証済み
            print("status: AuthorizedAlways")
            break
        case .AuthorizedWhenInUse:
            // 認証済み
            print("status: AuthorizedWhenInUse")
            break
        }
        
        return locationManager
    }
    
    private static func startLocation(lm: CLLocationManager, button: UIBarButtonItem) {
        button.title = "現在地"
        button.style = .Done
        // 位置情報取得開始
        lm.startUpdatingLocation()
    }
    
    private static func stopLocation(lm: CLLocationManager, button: UIBarButtonItem) {
        button.title = "現在地"
        button.style = .Plain
        // 位置情報取得停止
        lm.stopUpdatingLocation()
    }
    
    func onClickLocationButton(sender: UIBarButtonItem) {
        if sender.style == .Done{
            ViewController.stopLocation(myLocationManager!, button: sender)
        }else{
            ViewController.startLocation(myLocationManager!, button: sender)
        }
    }
    
    // 位置情報取得成功時に実行される関数
    func locationManager(lm: CLLocationManager,
        didUpdateToLocation newLocation: CLLocation, fromLocation oldLocation: CLLocation) {
        
        // 緯度経度を取得
        let latitude = newLocation.coordinate.latitude
        let longitude = newLocation.coordinate.longitude
        print("latiitude: \(latitude) , longitude: \(longitude)")
        
        let location:CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitude, longitude)
        
        // 地図を移動
        myMapView!.setCenterCoordinate(location, animated: false)
        
        // 位置情報取得停止
        ViewController.stopLocation(lm, button: myLocationButton!)
    }
    
    // 位置情報取得失敗時に実行される関数
    func locationManager(lm: CLLocationManager, didFailWithError error: NSError) {
        
        print("locationManager: :\(error)");

        // 位置情報取得停止
        ViewController.stopLocation(lm, button: myLocationButton!)
        
        // 位置情報設定画面へのダイアログ表示
        ViewController.showLocationConfigureDialog(self)
    }
    
    // 位置情報設定画面へのダイアログ表示
    private static func showLocationConfigureDialog(view: UIViewController) {
        
        let alert = UIAlertController(title: "位置情報が取得できません。",
            message: "位置情報を利用する場合は設定をしてください。",
            preferredStyle: .Alert)

        // 選択肢を構築
        alert.addAction(
            UIAlertAction(
                title: "位置情報の設定をする",
                style: .Default,
                handler: { action in
                    // 位置情報設定画面へ遷移する
                    let url = NSURL(string: UIApplicationOpenSettingsURLString)
                    UIApplication.sharedApplication().openURL(url!)
        }))
        alert.addAction(UIAlertAction(title: "キャンセル", style: .Cancel, handler: nil))
        
        // ダイアログを表示する
        dispatch_async(dispatch_get_main_queue(), {
            view.presentViewController(alert, animated: true, completion: nil)
        })
    }
}

Info.plist (information property list file) の編集

位置情報を取得するためには Info.plist にキーと説明文を追加する必要がある。

Xcode の左側のファイル一覧から Info.plist を選択。

Swift + MapKit + CLLocationManager

項目の追加は、右クリックして Add Row で可能。

Swift + MapKit + CLLocationManager

Info.plist の編集: 「このApp使用中のみ許可」の場合

アプリ使用中のみ位置情報を利用する場合 (「このApp使用中のみ許可」と設定情報に表示される) に追加する内容。


Key: NSLocationWhenInUseUsageDescription
Type: String
Value: 任意の説明文

位置情報の取得許可をユーザーに得る際には、アプリの中で CLLocationManager.requestWhenInUseAuthorization() をコールして、ダイアログを表示する。

Swift + MapKit + CLLocationManager

Info.plist の編集: 「常に許可」の場合

バックグラウンドでも位置情報を利用する場合 (「常に許可」と設定情報に表示される) に追加する内容。


Key: NSLocationAlwaysUsageDescription
Type: String
Value: 任意の説明文

位置情報の取得許可をユーザーに得る際には、アプリの中で CLLocationManager.requestAlwaysAuthorization() をコールして、ダイアログを表示する。

Swift + MapKit + CLLocationManager

ユーザーが位置情報取得を許可しなかった場合の挙動

位置情報取得に失敗したときに呼ばれるメソッドがある。


func locationManager(lm: CLLocationManager, didFailWithError error: NSError)

サンプルコードでは、この関数の中で「位置情報が取得できません」と、ダイアログを表示している。

Swift + MapKit + CLLocationManager

ユーザーがダイアログの「位置情報の設定をする」をタップすれば、設定アプリの該当箇所が開くようにした。

Swift + MapKit + CLLocationManager

ユーザーは、設定画面から位置情報の利用を許可できる。

Swift + MapKit + CLLocationManager

iOS の「設定」アプリ → [プライバシー] → [位置情報サービス] からも、設定状況を確認・変更できる。

Swift + MapKit + CLLocationManager

参考資料

tags: swift map

Posted by NI-Lab. (@nilab)