📍

SwiftUI + Mapkitで、Cloud Firestoreを使う

2024/07/04に公開

対象者

  • MapKitを勉強している
  • Cloud Firestoreの経験がある
  • 位置情報を保存するアプリを作ってみたい

SwiftUIで、新規プロジェクトを作成する。ShopMapAppとしておきましょうか。

バンドルIDの設定をして、Firebase SDKをSPMを使って追加する。

エントリーポイントのファイルを設定する。

import SwiftUI
import FirebaseCore

class AppDelegate: NSObject, UIApplicationDelegate {
  func application(_ application: UIApplication,
                   didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
    FirebaseApp.configure()

    return true
  }
}

@main
struct ShopMapAppApp: App {
    
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

もし、FirebaseCoreがインポートできなかったら、こちらの記事を参考に解決してみてください。
https://zenn.dev/joo_hashi/articles/4d1ecaa9bab3de

プロジェクトの説明

Cloud Firestoreに、ダミーの位置情報とお店の場所を入れておきましょうか。何件でも入れて良い。最初に表示する場所はお好きな場所を軽度・緯度を設定してください。

渋谷にあるお店の位置情報を入れますか。 GoogleMapを使って、右クリックでお店の緯度・軽度を表示できます。

この情報を Cloud Firestorenに保存しましょう。

お店情報を作成

shop collectionを作成しましょう。このような構造になっております。

/shop
|
-----shop_name: string
|
-----address: string
|
-----location: geopoint



[example]

import SwiftUI
import MapKit
import Firebase

struct ShopAnnotation: Identifiable {
    let id = UUID()
    var name: String
    var coordinate: CLLocationCoordinate2D
    var address: String
}

class LocationManager: NSObject, ObservableObject {
    // 渋谷駅近辺の座標
    @Published var region = MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: 35.657567537274865, longitude: 139.70215439869386),
        span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
    )
    @Published var shopAnnotations = [ShopAnnotation]()
    @Published var selectedShop: ShopAnnotation?
    // ページが呼ばれたタイミングで実行
    override init() {
        super.init()
        fetchShopLocation()
    }

    // cloud firestore ~ data fetch
    func fetchShopLocation() {
        let db = Firestore.firestore()
        db.collection("shop").getDocuments { [weak self] (querySnapshot, err) in
            guard let self = self else { return }
            
            if let err = err {
                print("Error getting documents: \(err)")
                return
            }
            
            var localAnnotations = [ShopAnnotation]()
            for document in querySnapshot!.documents {
                let data = document.data()
                if let shopName = data["shop_name"] as? String,
                   let location = data["location"] as? GeoPoint,
                   let address = data["address"] as? String {
                    let shopAnnotation = ShopAnnotation(
                        name: shopName,
                        coordinate: CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude),
                        address: address
                    )
                    localAnnotations.append(shopAnnotation)
                }
            }
            
            DispatchQueue.main.async {
                self.shopAnnotations = localAnnotations
            }
        }
    }
}

struct ContentView: View {
    @StateObject private var locationManager = LocationManager()

    var body: some View {
        // show Map View
        Map(coordinateRegion: $locationManager.region, annotationItems: locationManager.shopAnnotations) { shop in
            MapAnnotation(coordinate: shop.coordinate) {
                Image(systemName: "mappin.circle.fill")
                    .foregroundColor(.red)
                    .onTapGesture {
                        locationManager.selectedShop = shop
                    }
            }
        }// pin をタップするとお店の情報を表示
        .overlay(
            Group {
                if let selectedShop = locationManager.selectedShop {
                    VStack {
                        Text(selectedShop.name)
                            .font(.headline)
                        Text(selectedShop.address)
                            .font(.subheadline)
                    }
                    .padding()
                    .background(Color.white)
                    .cornerRadius(10)
                    .shadow(radius: 5)
                    .padding()
                    .transition(.move(edge: .bottom))
                    .animation(.spring())
                }
            }, alignment: .bottom
        )
    }
}

#Preview {
    ContentView()
}


感想

ネットに参考になる情報がなかったので、手探りで作りました。難しいですね。どうやってみんな作っているのだろうか...
もっと簡単に地図アプリ作れると良いのだが...

[以前参考にしていた情報]
https://firebase.google.com/docs/firestore/manage-data/data-types
https://firebase.google.com/docs/firestore/solutions/geoqueries?hl=ja#swift_1

Discussion