【SwiftUI 3.0】LocationButton で「位置情報の利用許可」を簡単実装 & CLLocationManager で位置情報を取得

SwiftUI 3.0 で追加された LocationButton を利用すると、タップするだけで位置情報の利用許可を求めるダイアログを自動で表示することができます。

また、ボタンの見た目も予め定義されたプロパティを指定するだけでそれらしいボタンになります

動作確認環境

macOS Monterey(12.2.1)

Xcode 13.2.1

iOS 15.3(iPhone 13 mini シミュレータ)

【SwiftUI 3.0】LocationButton で「位置情報の利用許可」を簡単実装 & CLLocationManager で位置情報を取得

定義と使い方

CoreLocationUI のインポート
import CoreLocationUI
LocationButton の定義
LocationButton(title: LocationButton.Title?, action: (() -> Void))
使用例
LocationButton(.currentLocation) {
    print("Get Location")
}
.foregroundColor(.white)
  • title: LocationButton.Title?:ボタンのタイトルを以下の何れかを指定します。
    • .currentLocation:「Current Location」
    • .sendCurrentLocation:「Send Current Location」
    • .sendMyCurrentLocation:「Send My Current Location」
    • .shareCurrentLocation:「Share Current Location」
    • .shareMyCurrentLocation:「Share My Current Location」
  • action: (() -> Void) :ボタンを押下した際に行いたい処理を記述します。

ここで CLLocationManager を使って位置情報を取得すると良いと思います。

labelStyle プロパティ

.labelStyle を指定すると、アイコンのみ、タイトルのみ、アイコン&タイトルの何れかとすることができます。

LocationButton() {
}
.labelStyle(.iconOnly)
.labelStyle(.titleOnly)
.labelStyle(.titleAndIcon)
iconOnly にした場合の例
LocationButton {
}
.labelStyle(.iconOnly)
.foregroundColor(.white)
.cornerRadius(30)
.symbolVariant(.fill)
.tint(.blue)

補足ですが、通常の Button と同じで cornerRadiustint を指定することもできます

タップすると位置情報の利用許可ダイアログが表示される

LocationButton をタップすると、まだ位置情報の利用を許可していない場合に許可を求めるダイアログが表示されます。

一度OKを選択すると出なくなります。ちなみに、もう一度試したい場合は設定アプリから「許可しない」に戻せばまた出るようになります。

設定 > プライバシー > 位置情報サービス > 対象のアプリを選択 > 許可しないをタップ

CLLocationManager で位置情報を取得

LocationButton そのものには位置情報を取得する機能はありません。位置情報を取得するにはCLLocationManager を使います

LocationClient

位置情報を管理するクラスを作成しました。

import CoreLocation

class LocationClient: NSObject, ObservableObject, CLLocationManagerDelegate {
    
    let locationManager = CLLocationManager()
    
    @Published var location: CLLocationCoordinate2D?
    @Published var requesting: Bool = false
    
    override init() {
        super.init()
        locationManager.delegate = self;
    }
    
    func requestLocation() {
        request()
    }
    
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        request()
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        location = locations.first?.coordinate
        requesting = false
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error.localizedDescription)
        requesting = false
    }
    
    private func request() {
        if (locationManager.authorizationStatus == .authorizedWhenInUse) {
            requesting = true
            locationManager.requestLocation()
        }
    }
}

CLLocationManager の requestLocation() メソッドで位置情報の取得をリクエストします。

位置情報取得の許可がされていないとエラーが発生するためステータスをチェックしてから実行しています。

private func request() {
    if (locationManager.authorizationStatus == .authorizedWhenInUse) {
        requesting = true
        locationManager.requestLocation()
    }
}

リクエスト結果の受け取り、位置情報取得の許可状況の変化の検知、エラーハンドリングなどを行うには CLLocationManagerDelegate プロトコルの以下の3つのメソッドを実装します。

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus)
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error)

今回の例では、先ほどの request() メソッドを 外部からの呼び出し用の requestLocation() と didChangeAuthorization で呼び出しています。

func requestLocation() {
    request()
}
    
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
    request()
}

位置情報の取得に成功すると didUpdateLocations が呼ばれ、locations 引数に緯度経度のデータが格納されています。

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    location = locations.first?.coordinate
    requesting = false
}

LocationClient を使ったサンプル

LocationClient を @StateObject で画面側で保持し緯度経度を表示してみました。

import CoreLocationUI

struct ContentView: View {
    
    @StateObject var locationClient = LocationClient()
    
    var body: some View {
        VStack {
            VStack {
                Text("[位置情報]")
                if let location = locationClient.location {
                    Text("緯度:\(location.latitude)")
                    Text("経度:\(location.longitude)")
                } else {
                    Text("緯度:----")
                    Text("経度:----")
                }
            }
            LocationButton(.currentLocation) {
                locationClient.requestLocation()
            }
            .foregroundColor(.white)
            .cornerRadius(30)
            if (locationClient.requesting) {
                ProgressView()
            }
        }
    }
}

LocationButton のタップで位置情報の取得をリクエストしています。

リクエスト中は ProgressView が表示されるようにしておきました。

Apple本社付近の位置

ちなみに、シミュレータで位置情報を利用するには、iOS Simulator メニューから、Features > Location から任意の場所をセットしておく必要がありますので注意してください。