2021/04/24
SwiftUIでGoogle Maps SDKを使う
概要
Google MapをSwiftUIでお試し実装しました。
公式チュートリアルはSwiftUIではないのですが、それを読み替えてSwiftUIでチュートリアルを実施してみました。
実装内容
API keyの取得
- ここを参考に
- GCPプロジェクトを作成
- APIを作成にして、アクセス制限等をつける
- APIキーを取得する
必要なライブラリのインストール
まずはXcodeで新規iOSプロジェクト作成し、cocoapods経由で、GoogleMaps
とGooglePlaces
をインストールします。
% rbenv install 3.0.1
% rbenv local 3.0.1
% rbenv exec gem install bundler
% rbenv exec bundler -v
Bundler version 2.2.16
% cat <<EOF > Gemfile
source "https://rubygems.org"
gem 'rexml'
gem 'cocoapods'
EOF
% rbenv exec bundle install --path=vendor/bundle
% rbenv exec bundle exec pod init
% cat <<EOF > Podfile
source 'https://github.com/CocoaPods/Specs.git'
target 'your-app-name' do
pod 'GoogleMaps', '4.2.0'
pod 'GooglePlaces', '4.2.0'
end
% rbenv exec bundle exec pod install
プロジェクトの設定
- TARGETS > your-app-name > BuildPhase > Link Binary With Libraries から
CoreLocation.framework
を追加する - Info.plistに以下を追加する
<dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string>このアプリは位置情報を取得します</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>googlechromes</string>
<string>comgooglemaps</string>
</array>
</dict>
Swiftファイル等
- entrypointの作成
- AppDelegateを適用します
import SwiftUI
import GoogleMaps
let APIKey = "<YOUR-GCP-API-KEY-HERE>"
@main
struct Application: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
- AppDelegateの定義
import Foundation
import GooglePlaces
import GoogleMaps
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
GMSPlacesClient.provideAPIKey(APIKey)
GMSServices.provideAPIKey(APIKey)
return true
}
}
- メイン画面
- 位置情報のアクセスが許可されない場合はアラートを出してあげます
import SwiftUI
import GooglePlaces
struct ContentView: View {
@State var manager = CLLocationManager()
@State var alert = false
var body: some View {
GoogleMapsView(manager: $manager, alert: $alert).alert(isPresented: $alert) {
Alert(title: Text("設定機能から位置情報へのアクセスを許可してください!"))
}
}
}
UIViewRepresentable
を使ってSwiftUIでも使えるようにViewをラップしてあげます。- coordinatorクラスを定義して、
CLLocationManager
の処理を委譲してあげます。 - coordinatorクラスに操作したいmapViewのインスタンスを渡してあげて、位置情報が更新した時にmapを更新できるようにしてあげます。
- 現在地情報が取得できたら、マップを表示し、現在地にマーカーを表示します。
import SwiftUI
import UIKit
import GoogleMaps
struct GoogleMapsView: UIViewRepresentable {
@Binding var manager : CLLocationManager
@Binding var alert : Bool
@State var preciseLocationZoomLevel: Float = 15.0
@State var approximateLocationZoomLevel: Float = 10.0
let mapView = GMSMapView(frame: CGRect.zero)
func makeCoordinator() -> Coordinator {
return Coordinator(mapView: self)
}
/// Creates a `UIView` instance to be presented.
func makeUIView(context: Self.Context) -> GMSMapView {
mapView.isHidden = true
manager.delegate = context.coordinator
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
manager.distanceFilter = 50
manager.startUpdatingLocation()
return mapView
}
/// Updates the presented `UIView` (and coordinator) to the latest
/// configuration.
func updateUIView(_ mapView: GMSMapView, context: Self.Context) {
}
class Coordinator : NSObject, CLLocationManagerDelegate {
var parent: GoogleMapsView
init(mapView: GoogleMapsView) {
parent = mapView
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .authorizedAlways, .authorizedWhenInUse:
parent.alert = false
case .notDetermined, .denied, .restricted:
parent.alert = true
// show default location
// parent.mapView.isHidden = false
// coordinate -33.86,151.20 at zoom level 6.
// let camera = GMSCameraPosition.camera(withLatitude: -33.86, longitude: 151.20, zoom: 6.0)
// mapView.camera = camera
@unknown default:
fatalError()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location: CLLocation = locations.last!
print("Location: \(location)")
let zoomLevel = manager.accuracyAuthorization == .fullAccuracy ? parent.preciseLocationZoomLevel : parent.approximateLocationZoomLevel
let camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude, longitude: location.coordinate.longitude, zoom: zoomLevel)
if parent.mapView.isHidden {
parent.mapView.isHidden = false
parent.mapView.camera = camera
} else {
parent.mapView.clear()
let marker = GMSMarker()
marker.position = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
marker.title = "Your Place"
marker.snippet = "Home"
marker.map = parent.mapView
parent.mapView.animate(to: camera)
}
}
// Handle location manager errors.
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
manager.stopUpdatingLocation()
print("Error: \(error)")
}
}
}
以上です。
関連する記事
SwiftUIを使った開発のノウハウをまとめたiOSアプリをリリースしました
Kumanoteでよく使うSwiftUIコンポーネントの作り方をソースコードと共に公開しています。
SwiftUIでswiftのコードのシンタックスハイライトを表示する
SwiftのコードをSwiftUIベースのアプリでシンタックスハイライトされた状態で見れるViewを作ってみました
Tailwindcssで定義されている色からSwiftUI版の定義を自動生成する
Tailwindcssで定義されている便利な色たちをSwiftUIアプリでも利用したいので、jsonで定義されている色をSwiftUI Colorsの変数に変換するスクリプトを作りました
[SwiftUI]Lottieファイルを使ってSplash画面をつくる
Lottieファイルを使ってSwiftUIアプリのSplash画面を作成しました