jonthornham
jonthornham

Reputation: 3335

Can You Get Latitude and Longitude From A Database for MapKit in SwiftUI

I am working on a SwiftUI project and want to place a map in a view that uses coordinates stored in Firestore. Apple's example for MapKit in SwiftUI uses static latitude and longitude parameters in the @State property and then binds the property to the Map() view.

struct BusinessMapView: View {
    @State private var region: MKCoordinateRegion = {
        var mapCoordinates = CLLocationCoordinate2D(latitude: 44.621754, longitude: -66.475873)
        var mapZoomLevel = MKCoordinateSpan(latitudeDelta: 5.00, longitudeDelta: 5.00)
        var mapRegion = MKCoordinateRegion(center: mapCoordinates, span: mapZoomLevel)
        return mapRegion
    }()
            
    var body: some View {
        Map(coordinateRegion: $region)
    }
}

What I want to do is the following but clearly this is not allowed since you cannot access other properties in another property.

struct BusinessMapView: View {
    @ObservedObject var businessAddressRowViewModel: BusinessAddressRowViewModel

    @State private var region: MKCoordinateRegion = {
        var mapCoordinates = CLLocationCoordinate2D(latitude: businessAddressRowViewModel.businessAddress.latitude, longitude: businessAddressRowViewModel.businessAddress.longitude)
        var mapZoomLevel = MKCoordinateSpan(latitudeDelta: 5.00, longitudeDelta: 5.00)
        var mapRegion = MKCoordinateRegion(center: mapCoordinates, span: mapZoomLevel)
        return mapRegion
    }()
            
    var body: some View {
        Map(coordinateRegion: $region)
    }
}

So my question is, is there a way to set the coordinates from a database for a Map() in SwiftUI or is the only option to use static values for latitude and longitude?

EDIT ADDED FOR MORE INFORMATION

class BusinessAddressRowViewModel: ObservableObject, Identifiable {
    
    // Properties
    var id: String = ""
    
    public static let shared = BusinessAddressRowViewModel()
    
    // Published Properties
    @Published var businessAddress: BusinessAddress
    
    // Combine Cancellable
    private var cancellables = Set<AnyCancellable>()
    
    
    // Initializer
    init(businessAddress: BusinessAddress) {
        self.businessAddress = businessAddress
        self.startCombine()
    }
    
    // Starting Combine
    func startCombine() {
        // Get Bank Account
        $businessAddress
            .receive(on: RunLoop.main)
            .compactMap { businessAddress in
                businessAddress.id
            }
            .assign(to: \.id, on: self)
            .store(in: &cancellables)
    }
}

The shared property gives an error stating the parameter businessAddress is missing.

The data is coming from Firebase Firestore here.

class BusinessAddressRepository: ObservableObject {
    let db = Firestore.firestore()
    private var snapshotListener: ListenerRegistration?
    
    @Published var businessAddresses = [BusinessAddress]()
    
    init() {
        startSnapshotListener()
    }
        
    func startSnapshotListener() {
        // Get the currentUserUid
        guard let currentUserId = Auth.auth().currentUser else {
            return
        }
        
        if snapshotListener == nil {
            // Add a SnapshotListener to the BusinessAddress Collection.
            self.snapshotListener = db.collection(FirestoreCollection.users).document(currentUserId.uid).collection(FirestoreCollection.businessAddresses).addSnapshotListener { (querySnapshot, error) in
                // Check to see if an error occured and print it. IMPLEMENT ERROR HANDLING LATER
                if let error = error {
                    print("Error getting documents: \(error)")
                } else {
                    print("BusinessAddressRepository - snapshotListener called")
                    
                    // Check to make sure the Collection contains Documents
                    guard let documents = querySnapshot?.documents else {
                        print("No Business Addresses.")
                        return
                    }
                    
                    // Documents exist.
                    self.businessAddresses = documents.compactMap { businessAddress in
                        do {
                            return try businessAddress.data(as: BusinessAddress.self)
                        } catch {
                            print(error)
                        }
                        return nil
                    }
                }
            }
        }
    }

    func stopSnapshotListener() {
        if snapshotListener != nil {
            snapshotListener?.remove()
            snapshotListener = nil
        }
    }
}

Data is being passed to BusinessAddressRowViewModel from the BusinessAddressViewModel. BusinessAddressView holds the list that creates all the rows.

class BusinessAddressViewModel: ObservableObject {
    var businessAddressRepository: BusinessAddressRepository

    // Published Properties
    @Published var businessAddressRowViewModels = [BusinessAddressRowViewModel]()
    
    // Combine Cancellable
    private var cancellables = Set<AnyCancellable>()
        
    // Intitalizer
    init(businessAddressRepository: BusinessAddressRepository) {
        self.businessAddressRepository = businessAddressRepository
        self.startCombine()
    }
    
    // Starting Combine - Filter results for business addresses created by the current user only.
    func startCombine() {
        businessAddressRepository
            .$businessAddresses
            .receive(on: RunLoop.main)
            .map { businessAddress in
                businessAddress
                    .map { businessAddress in
                        BusinessAddressRowViewModel(businessAddress: businessAddress)
                    }
            }
            .assign(to: \.businessAddressRowViewModels, on: self)
            .store(in: &cancellables)
    }
}

Upvotes: 0

Views: 365

Answers (1)

Yrb
Yrb

Reputation: 9725

You have an initialization problem here, having nothing to do with the Map(). You are trying to use businessCoordinates the instantiated ObservedObject variable in the initializer, and, I am sure, are getting a Cannot use instance member 'businessCoordinates' within property initializer; property initializers run before 'self' is available error.

If you don't need 'businessCoordinates' anywhere in the view, other than the data, I would recommend this:

class BusinessCoordinates: ObservableObject {
    
    public static let shared = BusinessCoordinates()
...
}

This will give you a Singleton you can use at will. Then you use it like this:

struct BusinessMapView: View {
    @State private var region: MKCoordinateRegion

    init() {
        let mapCoordinates = CLLocationCoordinate2D(latitude: BusinessCoordinates.shared.latitude, longitude: BusinessCoordinates.shared.longitude)
        var mapZoomLevel = MKCoordinateSpan(latitudeDelta: 5.00, longitudeDelta: 5.00)
        _region = State(initialValue: MKCoordinateRegion(center: mapCoordinates, span: mapZoomLevel))

    }
            
    var body: some View {
        Map(coordinateRegion: $region)
    }
}

Upvotes: 0

Related Questions