1

I am new to SwiftUI and Realm (using flexible sync), please excuse if it sounds like an elementary question I have location data saved in a MongoDB Atlas collection - Centre

class Centre: Object, ObjectKeyIdentifiable {
    @Persisted var _id: ObjectId = ObjectId.generate()
    @Persisted var centreName = ""
    @Persisted var centreDesc = ""
    @Persisted var centreLocation: Coordinates?
    
    override static func primaryKey() -> String? {
          return "_id"
      }
    convenience init(centreName: String, centreDesc: String, centreLocation: Coordinates) {
         self.init()
         self.centreName = centreName
         self.centreDesc = centreDesc
         self.centreLocation = centreLocation
     }
}

and Coordinates are embedded objects where "x" is longitude and "y" is latitude

class Coordinates: EmbeddedObject, ObjectKeyIdentifiable {
    @Persisted var x: Double?
    @Persisted var y: Double?
}

I have created a Struct to conform to the requirements of MapAnnotation protocol as -

struct CustomAnnots: Identifiable {
    let id: UUID
    var nameCentreLoc: String
    var descCentreLoc: String
    let latitude: Double
    let longitude: Double
    var coordinate: CLLocationCoordinate2D {
        CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
        }
    }

I am trying to populate this struct from the data from Atlas collection My LocationView - not working

import SwiftUI
import MapKit
import RealmSwift

struct LocationView: View {
    @Environment(\.realm) var realm
    @ObservedResults(Centre.self) var centres
    @ObservedRealmObject var centre: Centre
    @State private var nameCentreLoc = ""
    @State private var descCentreLoc = ""
    @State private var latitude = 0.0
    @State private var longitude = 0.0
    @State private var annots = []
    
    @State private var region = MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: 24.681_858, longitude: 81.811_623),
        span: MKCoordinateSpan(latitudeDelta: 10, longitudeDelta: 10)
    )
    var body: some View {

        Map(coordinateRegion: $region, annotationItems: annots, annotationContent: { locations in
            MapPin(coordinate: locations.coordinate)
         })
        .onAppear {
            setSubscription()
           initData()
        }
       
        }
    
    
    private func setSubscription() {
        let subscriptions = realm.subscriptions
        subscriptions.write {
            if let currentSubscription = subscriptions.first(named: "all_centres") {
                currentSubscription.update(toType: Centre.self) { centre in
                    centre.centreName != ""
                }

            } else {
                subscriptions.append(QuerySubscription<Centre>(name: "all_centres") { centre in
                    centre.centreName != ""
                })
            }
        }
    }
    private func initData() {
        nameCentreLoc = centre.centreName
        descCentreLoc = centre.centreDesc
        latitude = (centre.centreLocation?.y)!
        longitude = (centre.centreLocation?.x)!
        let annots = [for centre in centres {
            CustomAnnots(id: UUID(), nameCentreLoc: nameCentreLoc, descCentreLoc: descCentreLoc, latitude: latitude, longitude: longitude)
        }]
    }
}

How do I populate the Struct with data from Centre collection?

Changed to (with no errors in Xcode)-

    var body: some View {
        let annots = [CustomAnnots(id: UUID(), nameCentreLoc: centre.centreName, descCentreLoc: centre.centreDesc, latitude: (centre.centreLocation?.y)!, longitude: (centre.centreLocation?.x)!)]
        Map(coordinateRegion: $region, annotationItems: annots, annotationContent: { locations in
            MapPin(coordinate: locations.coordinate)
         })
        .onAppear {
            setSubscription()
        }
       
        }

now getting runtime error "Force unwrapping nil value"

with this function I am able to print out the results to console

   func getLoc() {
        for centre in centres {
            var annots = [CustomAnnots.init(id: UUID(), nameCentreLoc: centre.centreName, descCentreLoc: centre.centreDesc, latitude: (centre.centreLocation?.y)!, longitude: (centre.centreLocation?.x)!)]
            print(annots)
        }
    }

I get this printed out - [ACCv5.CustomAnnots(id: 67E9DADA-0BCC-4D30-8136-8B666881E82D, nameCentreLoc: "HO", descCentreLoc: "Head Office Artemis Cardiac Care Gurgaon", latitude: 28.438694893842058, longitude: 77.10845294294181)] [ACCv5.CustomAnnots(id: 26DC0C63-5A17-49C7-B4BF-FD3AA1ABF65E, nameCentreLoc: "Panipat", descCentreLoc: "Artemis Heart Centre at Ravindra Hospital", latitude: 29.388306713854682, longitude: 76.95889693063663)] [ACCv5.CustomAnnots(id: D3A70E58-6B65-4F5D-A398-3394B7FB04DF, nameCentreLoc: "Ranchi", descCentreLoc: "Artemis Heart Centre at Raj Hospital", latitude: 23.35731237118492, longitude: 85.32288933068195)]

But I am unable to display MapAnnotations with this -

    @Environment(\.realm) var realm
    @ObservedResults(Centre.self) var centres
    @State public var annots: [CustomAnnots]
    @State private var region = MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: 24.681_858, longitude: 81.811_623),
        span: MKCoordinateSpan(latitudeDelta: 10, longitudeDelta: 10)
    )
    var body: some View {
        ForEach (centres) { centre in
            var annots = [CustomAnnots.init(id: UUID(), nameCentreLoc: centre.centreName, descCentreLoc: centre.centreDesc, latitude: (centre.centreLocation?.y)!, longitude: (centre.centreLocation?.x)!)]
        }
       // Text("\(annots.count)")
        Map(coordinateRegion: $region, annotationItems: annots, annotationContent: { locations in
            MapMarker(coordinate: locations.coordinate)
         })

Final code after Jay's suggestions

class Centre: Object, ObjectKeyIdentifiable {
    @Persisted var _id: ObjectId = ObjectId.generate()
    @Persisted var centreName = ""
    @Persisted var centreDesc = ""
    @Persisted var centreLocation: Coordinates?
    
    override static func primaryKey() -> String? {
          return "_id"
      }
  
    convenience init(centreName: String, centreDesc: String, x: Double, y: Double) {
         self.init()
         self.centreName = centreName
         self.centreDesc = centreDesc
         self.centreLocation?.x = x
         self.centreLocation?.y = y
     }
    var coordinate: CLLocationCoordinate2D {
        CLLocationCoordinate2D(latitude: (centreLocation?.y)!, longitude: (centreLocation?.x)!)
        }
}


 Map(coordinateRegion: $region, annotationItems: centres, annotationContent: { centre in
            MapMarker(coordinate: centre.coordinate)
         })
  • 1
    Welcome to SO. Telling us something - *doesn't work* - is vague. What does't work about it? What line doesn't work as expected? What troubleshooting have you done? Additionally, it seems like you're dealing with a lot of duplicate data. Lastly, why not store the data in the Realm object and have a function that returns a CustomAnnot object directly? Also, please note this from the [MapAnnotation Protocol Documentation](https://developer.apple.com/documentation/mapkit/mapannotationprotocol) **Don’t create types conforming to this protocol.** Use the MapAnnotation framework instead. – Jay Jun 07 '22 at 18:19
  • Thank you for replying, Jay. the line "let annots = [for centre..." throws "Expected expression in container literal" and "annotationItems: annots" initial " could not find it in scope. then I created an empty array " @State private var annots = []" to which the error is " Protocol 'Any' as a type cannot conform to 'Identifiable'" – Manjinder Sandhu Jun 08 '22 at 02:58
  • Yeah - I would expect those errors based on the code. Seems over complicated for what you're trying to do based on the description. There's no need to protocols in this use case and your objects are really just duplicate data. I think you can do this entire task with one object that returns the MapAnnotation data you need. – Jay Jun 08 '22 at 17:22
  • I have simplified code and managed to retrieve data conforming to struct but still unable to display MapAnnotations - Updated code is above – Manjinder Sandhu Jun 10 '22 at 11:22

1 Answers1

1

Simplifying may produce a more streamlined solution.

If the objective is to populate a map from Realm objects, then I think you're on the right track but there are too many moving parts - lets reduce that code. Let me know if I misunderstood the question..

Here's some pseudo code:

Start with the Realm object that contains the data. Note we don't need the primary key because we are using ObjectId's for that. This object has everything needed to track the pin on the map, it can be observed for changes and returns a calculated var containing the MapAnnotation to be used on the map itself, based on the data in the object

class Centre: Object, ObjectKeyIdentifiable {
    @Persisted var _id: ObjectId = ObjectId.generate()
    @Persisted var centreName = ""
    @Persisted var centreDesc = ""
    @Persisted var x: Double!
    @Persisted var y: Double!

    var mapAnno: MapAnnotation {
        //build the map annotation from the above properties
        return //return the map annotation
    }

    convenience init(centreName: String, centreDesc: String, x: Double, y: Double) {
        self.init()
        self.centreName = centreName
        self.centreDesc = centreDesc
        self.x = x
        self.y = y
     }
}

Then in the view - load up all of the centers and iterate over them retreiving the MapAnnotation from each

@ObservedResults(Centre.self) var centres //loads all of the centrs

var body: some View {
        ForEach (centres) { centre in
             let mapAnnotation = centre.mapAnno
             //add the annotation to the map

Note this pattern is pretty common as well

Map(coordinateRegion: $region,
    showsUserLocation: true,
    annotationItems: centres) { center in
                    MapAnnotation(coordinate: //get coords from centre)
Jay
  • 34,438
  • 18
  • 52
  • 81