Daten aus CoreData auslesen und in ein MKAnnotation Objekt umwandeln

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • Daten aus CoreData auslesen und in ein MKAnnotation Objekt umwandeln

    Hallo Zusammen,

    ich hab wieder mal eine Frage zu meinem Projekt. Nachdem ihr mir das letzte mal gut geholfen habt, hoffe ich das ihr mir hier auch gut helfen könnt.

    Ich möchte GeoCaches auf einer Karte anzeigen lassen, dazu habe ich bisher meine Caches über eine API heruntergeladen und in mein CoreData framework abgespeichert. Jedoch liegen die da nicht als MKAnnotation Object vor. so das ich das nachträglich noch zusammenbauen muss.

    Hier stelle ich mir nun zwei fragen:
    1. Ist es möglich das Annotation Objekt vom Typ MKAnnotation in der Datenbank abzulegen, so das ich es später nur noch abrufen muss?
    2. Wie hole ich mir die Annotations, aus der Datenbank, bevor ich mir meine Karte anzeigen lasse, ansonsten ist mein annotationsArray leer?

    Hat jemand eine Idee?

    Quellcode

    1. import SwiftUI
    2. import MapKit
    3. import Combine
    4. struct MapView: View {
    5. @Environment(\.managedObjectContext) var viewContext
    6. @FetchRequest(sortDescriptors: []) var caches: FetchedResults<OpenCache>
    7. @ObservedObject private var locationManager = LocationManager()
    8. @State private var region = MKCoordinateRegion.defaultRegion
    9. @State private var cancellable: AnyCancellable?
    10. @State private var trackingMode: MKUserTrackingMode = .none
    11. @State private var mapType: MKMapType = .standard
    12. @State var hasLoadedData = false
    13. @State private var annotationsArray: [Annotation] = []
    14. // Extrakte Methode zum erzeugen der einzelnen CoreData Einträge
    15. fileprivate func createDataEntries(_ response: (JSONCacheModel)) {
    16. let lat = response.location?.split(separator: "|")
    17. let vC = PersistenceController.shared.container.viewContext
    18. let newCacheModel = OpenCache(context: vC)
    19. newCacheModel.id = UUID()
    20. newCacheModel.code = response.code
    21. newCacheModel.name = response.name
    22. newCacheModel.status = response.status
    23. newCacheModel.type = response.type
    24. newCacheModel.latitude = Double(lat![0])!
    25. newCacheModel.longitude = Double(lat![1])!
    26. try? viewContext.save()
    27. }
    28. var body: some View {
    29. VStack {
    30. // Anzeigen der Karte, sobald der Standort gefunden worden ist
    31. if locationManager.location != nil {
    32. MapModel(
    33. region: $region,
    34. mapType: mapType,
    35. userTrackingMode: $trackingMode,
    36. showsUserLocation: true,
    37. annotation: annotationsArray
    38. ).edgesIgnoringSafeArea([.top]).onAppear{
    39. if !hasLoadedData {
    40. // Abrufen der Caches
    41. NetworkManager.shared.getAllCaches { (results) in
    42. switch results {
    43. case .success(let response):
    44. response.results.forEach { cache in
    45. NetworkManager.shared.getOneCache(cacheCode: cache) { (result) in
    46. switch result {
    47. case .success(let response):
    48. // CoreData einträge erzeugen
    49. createDataEntries(response)
    50. break
    51. case .failure(let error):
    52. print(error.rawValue)
    53. break
    54. }
    55. }
    56. }
    57. break
    58. case .failure(let error):
    59. print(error)
    60. }
    61. }
    62. }
    63. }
    64. } else {
    65. Text("Location user location ...")
    66. }
    67. Picker("", selection: $mapType) {
    68. Text("Standard").tag(MKMapType.standard)
    69. Text("Satellite").tag(MKMapType.satellite)
    70. Text("Hybrid").tag(MKMapType.hybrid)
    71. }
    72. .pickerStyle(SegmentedPickerStyle())
    73. .opacity(0.5)
    74. Spacer()
    75. }
    76. .onAppear{
    77. setCurrentLocation()
    78. }
    79. }
    80. private func setCurrentLocation() {
    81. cancellable = locationManager.$location.sink { location in
    82. region = MKCoordinateRegion(center: location?.coordinate ?? CLLocationCoordinate2D(), latitudinalMeters: 100,longitudinalMeters: 100)
    83. }
    84. }
    85. }
    Alles anzeigen
  • Hey,

    ich muss gestehen, mit MapKit noch nicht allzu viel gearbeitet zu haben. Aber vielleicht kann ich dir dennoch ein paar Gedankenanregungen geben.

    cnc24 schrieb:

    1. Ist es möglich das Annotation Objekt vom Typ MKAnnotation in der Datenbank abzulegen, so das ich es später nur noch abrufen muss?

    Ich kennen dein Annotation Objekt nicht, aber ich gehe mal davon aus dass das eine Klasse ist, die Annotation heißt und dem Protocol MKAnnotation conformed? Und zusätzlich hast du eine CoreData Entity namens OpenCache, in welcher du die dazugehörigen Informationen speicherst? Diese rufst du dann aus der lokalen Datenbank ab und möchtest sie in Annotation objects "umkonvertieren", um sie in der Map anzuzeigen?

    Dann ist die Antwort "na klar"! Dann kannst du das ganze doch vereinfachen, denn du benötigst lediglich eine der beiden Klassen. Du kannst ja deine OpenCache Entity-Klasse einfach nutzen und musst diese nicht zu einer Annotation umwandeln. Dafür musst du lediglich eine Extension hinzufügen, die OpenCache conformable zu MKAnnotation macht (sprich laut Doku eine Koordinate und Titel / Subtitel (optional) hat). Du hast zwei Möglichkeiten:
    • Entweder du änderst die Namen in deinem OpenCache-Datenmodell um, sodass diese bereits dem Protokoll entsprechen. Heißt, anstatt von longitude und latitude nutzt du coordinate (CLLocationCoordinate2D kannst du über Transformables in CoreData speichern, siehe hier), anstatt name nutzt du title.
    • Oder du erweiterst OpenCache um Computed Properties, die deine gespeicherten Daten zu den erforderlichen Protokoll-Properties umwandelt. Das könnte in etwa so aussehen (die Implementierung ist "frei Hand" und daher bestimmt nicht ganz frei von Fehlern)

    Quellcode

    1. extension OpenCache: MKAnnotation {
    2. var coordinate: CLLocationCoordinate2D {
    3. return CLLocationCoordinate2D(latitude: CLLocationDegrees(self.latitude), longitude: CLLocationDegrees(self.longitude))
    4. }
    5. var title: String? {
    6. return self.name
    7. }
    8. var subtitle: String? {
    9. return nil
    10. }
    11. }
    Alles anzeigen

    cnc24 schrieb:

    2. Wie hole ich mir die Annotations, aus der Datenbank, bevor ich mir meine Karte anzeigen lasse, ansonsten ist mein annotationsArray leer?
    Die Frage verstehe ich nicht ganz, da du in deinem Code ja bereits ein @FetchRequest hast und du die Frage damit eigentlich schon selbst beantwortet hast.. ;) Aber vielleicht ergibt sich das ja auch mit der Beantwortung der ersten Frage - du musst nun nichts mehr umwandeln und kannst direkt das FetchedResult caches nutzen, um es deiner MapModel als annotations zu übergeben.

    Viele Grüße
  • Top, Vielen Dank. Die extension hat geholfen um mein Problem zu Vereinfachen :)

    Ich hab noch eine weitere Frage:

    - Die Caches werden jetzt erfolgreich auf der Karte mit den klassischen Pins angezeigt. Wenn man jetzt drauf tippt werden die Pins nur größer und wackeln. Meine Idee ist es, das sich bei drauf tippen eine neue View öffnet und diese dann Details anzeigt. Die Details kommen dann aus meinem CoreData Framework. Des Weiteren möchte ich dann auch die Pin-Images anpassen.

    - Dazu denke ich, muss ich in meiner Wrapper klasse für meine Map MapModel in der viewFor annotation-Funktion der Coordinator Klasse die Pin-Images anpassen. Jedoch weiß ich. nicht wo ich in der Wrapper Klasse am besten die Detail View aufrufe?

    Hab mal das Project Hochgeladen.

    Quellcode: MapModel.swift

    1. import SwiftUI
    2. import MapKit
    3. struct MapModel: UIViewRepresentable {
    4. @Binding var region: MKCoordinateRegion
    5. @Binding var userTrackingMode: MKUserTrackingMode
    6. var mapType : MKMapType
    7. var showsUserLocation: Bool
    8. var annotation: FetchedResults<OpenCache>
    9. var updateCounter = 0
    10. init(
    11. region: Binding<MKCoordinateRegion>,
    12. mapType: MKMapType,
    13. userTrackingMode: Binding<MKUserTrackingMode>,
    14. showsUserLocation: Bool = true,
    15. annotation: FetchedResults<OpenCache>
    16. ){
    17. self._region = region
    18. self.mapType = mapType
    19. self._userTrackingMode = userTrackingMode
    20. self.showsUserLocation = showsUserLocation
    21. self.annotation = annotation
    22. }
    23. // MARK - Initial Function
    24. func makeUIView(context: Context) -> MKMapView {
    25. let mapView = MKMapView()
    26. mapView.setRegion(region, animated: false)
    27. mapView.setUserTrackingMode(userTrackingMode, animated: false)
    28. mapView.mapType = mapType
    29. mapView.showsUserLocation = showsUserLocation
    30. mapView.delegate = context.coordinator
    31. // Add annotation to the map
    32. for annotation in annotation {
    33. mapView.addAnnotation(annotation)
    34. }
    35. return mapView
    36. }
    37. func updateUIView(_ mapView: MKMapView, context: Context) {
    38. mapView.mapType = mapType
    39. // werden die Detail Views hier aufgerufen?
    40. }
    41. func makeCoordinator() -> Coordinator {
    42. Coordinator(self)
    43. }
    44. class Coordinator: NSObject, MKMapViewDelegate {
    45. var parent: MapModel
    46. init(_ parent: MapModel) {
    47. self.parent = parent
    48. }
    49. func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
    50. // We should handle dequeue of annotation view's properly so we have to write this boiler plate.
    51. // This basically dequeues an MKAnnotationView if it exists, otherwise it creates a new
    52. // MKAnnotationView from our annotation.
    53. guard annotation is MKPointAnnotation else { return nil }
    54. let identifier = "Annotation"
    55. guard let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) else {
    56. let annotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier)
    57. annotationView.canShowCallout = true
    58. return annotationView
    59. }
    60. annotationView.annotation = annotation
    61. // Anpassen der Pin-Images hier, diese sind abhängig vom Typ des Caches
    62. // oder werden hier die Detail Views aufgerufen? (Denke nicht, oder?)
    63. return annotationView
    64. }
    65. func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    66. // We need to update the region when the user changes it
    67. // otherwise when we zoom the mapview will return to its original region
    68. DispatchQueue.main.async {
    69. self.parent.region = mapView.region
    70. }
    71. }
    72. }
    73. }
    Alles anzeigen
    Update:

    Habe den folgenden Link Stackoverflow gefunden und versucht die Lösung in mein Projekt einzubinden, jedoch Scheiter ich an der übergäbe der beiden @state variablen und der init()-Funktion meines MapModel. Bekomme die Meldung, das das Binding nicht klappt, aufgrund Type mismatch.
    Hab ich das in dem Link richtig verstanden, das ich das Group element um meinen kompletten Picker und mein MapModel packen muss?

    Quellcode

    1. Group {
    2. NavigationLink(destination: LocationDetailsView(), isActive: self.$isActive) {
    3. EmptyView()
    4. }
    5. // Anzeigen der Karte, sobald der Standort gefunden worden ist
    6. if locationManager.location != nil {
    7. MapModel(
    8. region: $region,
    9. mapType: mapType,
    10. userTrackingMode: $trackingMode,
    11. showsUserLocation: true,
    12. annotation: caches,
    13. // isClicked: ...
    14. // selectedAnnotation: ...
    15. )
    16. .edgesIgnoringSafeArea([.top]).onAppear(perform: apiCall)
    17. } else {
    18. Text("Location user location ...")
    19. }
    20. }
    Alles anzeigen
    Dateien
    • CGeoIOS.zip

      (71,74 kB, 94 mal heruntergeladen, zuletzt: )

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von cnc24 ()

  • cnc24 schrieb:

    Hat keiner eine Idee, oder denke ich zu kompliziert ?
    Hi, deine Fragen waren sind für mich zu "Projekt-spezifisch", als dass ich dir helfen könnte, ohne es selbst mit größeren Aufwand auszuprobieren. Das klingt für mich eher nach Programmierer-Alltag, wo du scheinbar ja schon auf einem ganz guten Weg bist! Ausprobieren, Probleme bei Google/Stackoverflow suchen, Workarounds ausprobieren etc.

    cnc24 schrieb:

    Habe den folgenden Link Stackoverflow gefunden und versucht die Lösung in mein Projekt einzubinden, jedoch Scheiter ich an der übergäbe der beiden @state variablen und der init()-Funktion meines MapModel. Bekomme die Meldung, das das Binding nicht klappt, aufgrund Type mismatch.
    Wenn der Compiler wegen Type-Mismatches meckert, hast du das Problem doch bereits auf der Hand? Du übergibst scheinbar eine Variable mit einem Typ, der sich von dem erforderlichen Typ unterscheidet. Sieh doch mal nach, welche Typen das konkret sind (z.B. in den jeweiligen Deklarationen oder mit Alt-Klick auf die Variable). Bei Bindings aufpassen, wann davor ein $-Zeichen sein muss und wann nicht.

    cnc24 schrieb:

    Hab ich das in dem Link richtig verstanden, das ich das Group element um meinen kompletten Picker und mein MapModel packen muss?
    Das steht zumindest so auf Stackoverflow. Ein anderer Comment besagt, dass Group nicht funktioniert hätte und stattdessen ein ZStack genutzt werden müsse (ich gehe davon aus das hängt davon ab, wo die View eingebettet wird) - hast du das mal ausprobiert?

    VG
  • Vielen Dank für die Antwort, also wenn ich das, so wie unten zu sehen, mache springt er nicht in die Funktion]func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) des Coordinators in meinem MapModel.

    Habt ihr eine Idee wann die oben genannte Funktion überhaupt aufgerufen wird?


    Ich hab beide Dateien mal angehängt.

    @Osxer muss ich das Thema dann in die Rubrik Projekt verschieben?

    Quellcode

    1. import SwiftUI
    2. import MapKit
    3. import Combine
    4. struct MapView: View {
    5. @Environment(\.managedObjectContext) var viewContext
    6. @FetchRequest(sortDescriptors: []) var caches: FetchedResults<OpenCache>
    7. @ObservedObject private var locationManager = LocationManager()
    8. @State private var region = MKCoordinateRegion.defaultRegion
    9. @State private var cancellable: AnyCancellable?
    10. @State private var trackingMode: MKUserTrackingMode = .none
    11. @State private var mapType: MKMapType = .standard
    12. @State var hasLoadedData: Bool = false
    13. @State var isActive: Bool = false
    14. @State var selectedAnnotation: MKAnnotation?
    15. // Extrakte Methode zum erzeugen der einzelnen CoreData Einträge
    16. fileprivate func createDataEntries(_ response: (JSONCacheModel)) {
    17. let lat = response.location?.split(separator: "|")
    18. let vC = PersistenceController.shared.container.viewContext
    19. let newCacheModel = OpenCache(context: vC)
    20. newCacheModel.id = UUID()
    21. newCacheModel.code = response.code
    22. newCacheModel.name = response.name
    23. newCacheModel.status = response.status
    24. newCacheModel.type = response.type
    25. newCacheModel.latitude = Double(lat![0])!
    26. newCacheModel.longitude = Double(lat![1])!
    27. try? viewContext.save()
    28. self.hasLoadedData = true
    29. }
    30. var body: some View {
    31. VStack {
    32. ZStack {
    33. NavigationLink(destination: LocationDetailsView(), isActive: self.$isActive) {
    34. EmptyView()
    35. }
    36. .onAppear(perform: apiCall)
    37. // Anzeigen der Karte, sobald der Standort gefunden worden ist
    38. if locationManager.location != nil {
    39. MapModel(
    40. region: $region,
    41. mapType: mapType,
    42. userTrackingMode: $trackingMode,
    43. showsUserLocation: true,
    44. annotation: caches,
    45. isClicked: self.$isActive,
    46. selectedAnnotation: self.$selectedAnnotation
    47. )
    48. .edgesIgnoringSafeArea([.top])
    49. } else {
    50. Text("Location user location ...")
    51. }
    52. }
    53. Picker("", selection: $mapType) {
    54. Text("Standard").tag(MKMapType.standard)
    55. Text("Satellite").tag(MKMapType.satellite)
    56. Text("Hybrid").tag(MKMapType.hybrid)
    57. }
    58. .pickerStyle(SegmentedPickerStyle())
    59. .opacity(0.9)
    60. Spacer()
    61. }
    62. .onAppear{
    63. setCurrentLocation()
    64. }
    65. }
    66. private func apiCall(){
    67. if caches.isEmpty {
    68. // Abrufen der Caches
    69. NetworkManager.shared.getAllCaches { (results) in
    70. switch results {
    71. case .success(let response):
    72. response.results.forEach { cache in
    73. NetworkManager.shared.getOneCache(cacheCode: cache) { (result) in
    74. switch result {
    75. case .success(let response):
    76. // CoreData einträge erzeugen
    77. createDataEntries(response)
    78. break
    79. case .failure(let error):
    80. print(error.rawValue)
    81. break
    82. }
    83. }
    84. }
    85. break
    86. case .failure(let error):
    87. print(error)
    88. }
    89. }
    90. }
    91. }
    92. private func setCurrentLocation() {
    93. cancellable = locationManager.$location.sink { location in
    94. region = MKCoordinateRegion(center: location?.coordinate ?? CLLocationCoordinate2D(), latitudinalMeters: 100,longitudinalMeters: 100)
    95. }
    96. }
    97. }
    98. struct MapView_Previews: PreviewProvider {
    99. static var previews: some View {
    100. MapView()
    101. }
    102. }
    Alles anzeigen


    Quellcode

    1. import SwiftUI
    2. import MapKit
    3. struct MapModel: UIViewRepresentable {
    4. @Binding var region: MKCoordinateRegion
    5. @Binding var userTrackingMode: MKUserTrackingMode
    6. @Binding var isClicked: Bool
    7. @Binding var selectedAnnotation: MKAnnotation?
    8. var mapType : MKMapType
    9. var showsUserLocation: Bool
    10. var annotation: FetchedResults<OpenCache>
    11. var updateCounter = 0
    12. init(
    13. region: Binding<MKCoordinateRegion>,
    14. mapType: MKMapType,
    15. userTrackingMode: Binding<MKUserTrackingMode>,
    16. showsUserLocation: Bool = true,
    17. annotation: FetchedResults<OpenCache>,
    18. isClicked: Binding<Bool>,
    19. selectedAnnotation: Binding<MKAnnotation?>
    20. ){
    21. self._region = region
    22. self.mapType = mapType
    23. self._userTrackingMode = userTrackingMode
    24. self.showsUserLocation = showsUserLocation
    25. self.annotation = annotation
    26. self._isClicked = isClicked
    27. self._selectedAnnotation = selectedAnnotation
    28. }
    29. // MARK - Initial Function
    30. func makeUIView(context: Context) -> MKMapView {
    31. let mapView = MKMapView()
    32. mapView.setRegion(region, animated: false)
    33. mapView.setUserTrackingMode(userTrackingMode, animated: false)
    34. mapView.mapType = mapType
    35. mapView.showsUserLocation = showsUserLocation
    36. mapView.delegate = context.coordinator
    37. // Add annotation to the map
    38. for annotation in annotation {
    39. mapView.addAnnotation(annotation)
    40. }
    41. return mapView
    42. }
    43. func updateUIView(_ mapView: MKMapView, context: Context) {
    44. mapView.mapType = mapType
    45. print(_isClicked)
    46. }
    47. func makeCoordinator() -> Coordinator {
    48. Coordinator(self)
    49. }
    50. class Coordinator: NSObject, MKMapViewDelegate {
    51. var parent: MapModel
    52. init(_ parent: MapModel) {
    53. self.parent = parent
    54. }
    55. func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
    56. // We should handle dequeue of annotation view's properly so we have to write this boiler plate.
    57. // This basically dequeues an MKAnnotationView if it exists, otherwise it creates a new
    58. // MKAnnotationView from our annotation.
    59. guard annotation is MKPointAnnotation else { return nil }
    60. let identifier = "Annotation"
    61. guard let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) else {
    62. let annotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier)
    63. annotationView.canShowCallout = true
    64. return annotationView
    65. }
    66. annotationView.annotation = annotation
    67. // Anpassen der Pin-Images hier, diese sind abhängig vom Typ des Caches
    68. return annotationView
    69. }
    70. func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
    71. self.parent.isClicked = true
    72. self.parent.selectedAnnotation = view.annotation
    73. }
    74. func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    75. // We need to update the region when the user changes it
    76. // otherwise when we zoom the mapview will return to its original region
    77. DispatchQueue.main.async {
    78. self.parent.region = mapView.region
    79. }
    80. }
    81. }
    82. }
    Alles anzeigen