MapKit - Problem bei der Zentrierung der Map

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

Aufgrund der Corona-Krise: Die Veröffentlichung von Stellenangeboten und -gesuchen ist bis 31.3.2023 kostenfrei. Das beinhaltet auch Angebote und Gesuche von und für Freischaffende und Selbstständige.

  • MapKit - Problem bei der Zentrierung der Map

    Hallo Zusammen und einen guten Morgen,

    ich hab mir ein Projekt angelacht und versuche eine GeoCaching App in iOS umzusetzen, bisher klappt das auch soweit. Ziel der App ist es diverse Geocaches über eine API abzurufen und diese auf einer Karte darstellen zu lassen. Der Nutzer kann diese dann auswählen und Details sehen.

    Bei der bisherigen Implementierung, bin ich jedoch hier und da auf ein bis zwei Probleme gestoßen.

    1. Ich habe das Problem das ich den aktuellen Standort des Nutzers auf der Karte zwar sehe, jedoch nicht im Zentrum der Map. Sprich wenn ich die App starte zentriert die Map über den Hardgecodeden Ort (hier London) in der region-Variable meines LocationManagers. Wie behebe ich das, oder wo stehe ich hier auf dem Schlauch?

    Quellcode

    1. import MapKit
    2. import CoreLocation
    3. final class LocationManager: NSObject, ObservableObject {
    4. @Published var location: CLLocation?
    5. @Published var region = MKCoordinateRegion(
    6. center: CLLocationCoordinate2D(latitude: 51.508150, longitude: -0.1300),
    7. span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5)
    8. )
    9. private var hasSetRegion = false
    10. private let locationManager = CLLocationManager()
    11. override init() {
    12. super.init()
    13. self.locationManager.delegate = self
    14. self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
    15. self.locationManager.distanceFilter = kCLDistanceFilterNone
    16. self.locationManager.requestWhenInUseAuthorization()
    17. self.locationManager.startUpdatingLocation()
    18. }
    19. }
    20. extension LocationManager: CLLocationManagerDelegate {
    21. func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    22. if let location = locations.last {
    23. self.location = location
    24. print(location)
    25. if !hasSetRegion {
    26. self.region = MKCoordinateRegion(center: location.coordinate,
    27. span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
    28. print(region)
    29. hasSetRegion = true
    30. }
    31. }
    32. }
    33. }
    Alles anzeigen




    Quellcode

    1. import SwiftUI
    2. import MapKit
    3. struct MapModel: UIViewRepresentable {
    4. @Binding var region: MKCoordinateRegion
    5. var mapType : MKMapType
    6. var userTracking: MKUserTrackingMode
    7. var showsUserLocation: Bool
    8. var annotation: [Annotation]
    9. init(
    10. region: Binding<MKCoordinateRegion>,
    11. mapType: MKMapType,
    12. userTrackingMode: MKUserTrackingMode,
    13. showsUserLocation: Bool = true,
    14. annotation: [Annotation] = [Annotation(title: "test", subtitle: "subtest", coordinate: CLLocationCoordinate2D(latitude: 51.507222, longitude: -0.1275))]
    15. ){
    16. self._region = region
    17. self.mapType = mapType
    18. self.userTracking = userTrackingMode
    19. self.showsUserLocation = showsUserLocation
    20. self.annotation = annotation
    21. }
    22. func makeUIView(context: Context) -> MKMapView {
    23. let mapView = MKMapView()
    24. mapView.setRegion(region, animated: false)
    25. mapView.mapType = mapType
    26. mapView.showsUserLocation = showsUserLocation
    27. mapView.userTrackingMode = userTracking
    28. mapView.delegate = context.coordinator
    29. // Add annotation to the map
    30. for annotation in annotation {
    31. mapView.addAnnotation(annotation)
    32. }
    33. return mapView
    34. }
    35. func updateUIView(_ mapView: MKMapView, context: Context) {
    36. mapView.mapType = mapType
    37. // Update your region so that it is now your new region
    38. mapView.setRegion(region, animated: false)
    39. }
    40. func makeCoordinator() -> Coordinator {
    41. Coordinator(self)
    42. }
    43. class Coordinator: NSObject, MKMapViewDelegate {
    44. var parent: MapModel
    45. init(_ parent: MapModel) {
    46. self.parent = parent
    47. }
    48. func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
    49. guard annotation is MKPointAnnotation else { return nil }
    50. let identifier = "Annotation"
    51. guard let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) else {
    52. let annotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier)
    53. annotationView.canShowCallout = true
    54. return annotationView
    55. }
    56. annotationView.annotation = annotation
    57. return annotationView
    58. }
    59. func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    60. DispatchQueue.main.async {
    61. self.parent.region = mapView.region
    62. }
    63. }
    64. }
    65. }
    Alles anzeigen

    Quellcode

    1. import SwiftUI
    2. import MapKit
    3. struct MapView: View {
    4. @Environment(\.managedObjectContext) var viewContext
    5. var cacheAnnotation: [OpenCache]! = []
    6. @State var trackingMode: MKUserTrackingMode = .follow
    7. @ObservedObject private var managerDelegate = LocationManager()
    8. @State private var mapType: MKMapType = .standard
    9. private var apiResponses: () = NetworkManager.shared.getAllCaches { (results) in
    10. switch results {
    11. case .success(let response):
    12. print(response)
    13. break
    14. case .failure(let error):
    15. break
    16. }
    17. }
    18. var body: some View {
    19. VStack {
    20. MapModel(
    21. region: $managerDelegate.region,
    22. mapType: mapType,
    23. userTrackingMode: trackingMode,
    24. showsUserLocation: true
    25. ).edgesIgnoringSafeArea([.top])
    26. Picker("", selection: $mapType) {
    27. Text("Standard").tag(MKMapType.standard)
    28. Text("Satellite").tag(MKMapType.satellite)
    29. Text("Hybrid").tag(MKMapType.hybrid)
    30. }
    31. .pickerStyle(SegmentedPickerStyle())
    32. .opacity(0.5)
    33. Spacer()
    34. }
    35. }
    36. }
    37. struct MapView_Previews: PreviewProvider {
    38. static var previews: some View {
    39. MapView()
    40. }
    41. }
    Alles anzeigen

    2. Das Problem ist komplexer, ich versuche die Caches, aus einer API, in CoreData abzulegen und diese dann später als Annotations auf der Map darstellen zulassen. Jedoch versuche ich hier zu verstehen wo ich die Daten in CoreData abspeicher, mache ich das in meinem API-Request, dort wo die empfangenen JSON-Responses geparst werden?

    Quellcode

    1. ...
    2. URLSession.shared.dataTask(with: url) { data, response, error in
    3. // 1. Überprüfen ob Fehler vorliegt
    4. if error != nil {
    5. completion(.failure(.unableToComplete))
    6. return
    7. }
    8. // 2. Überprüfen ob die Response gültig ist
    9. guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
    10. completion(.failure(.invalidResponse))
    11. return
    12. }
    13. // 3. Überprüfen ob wir Daten erhalten haben
    14. guard let data = data else {
    15. completion(.failure(.invalidData))
    16. return
    17. }
    18. // Bearbeiten der empfangengen Daten
    19. // Decoder soll JSON-Daten in Swift-Objekte umwandeln
    20. do {
    21. let decoder = JSONDecoder()
    22. decoder.dateDecodingStrategy = .iso8601
    23. let cacheResponse = try decoder.decode(CacheResponseModel.self, from: data)
    24. // do something CoreData tasks here?
    25. completion(.success(cacheResponse))
    26. } catch {
    27. print(error)
    28. completion(.failure(.invalidData))
    29. }
    30. }.resume()
    31. ...
    Alles anzeigen

    Ich habe mein Project mal als zip-Archiv angefügt. Ich habe auch ein Github Repository, würde das aber ungern öffentlich machen.

    Sollte ich die zweite Frage in einem extra Thread packen, wenn ja werde ich das natürlich umgehend machen.

    Vielen Dank für eure Hilfe.
    Und sorry,das das nun doch so viel geworden ist :(

    Grüße
    cnc24
    Dateien
    • CGeo-iOS.zip

      (46,14 kB, 35 mal heruntergeladen, zuletzt: )
  • Die func <fetchLocations> enthält den URLSession.shared.dataTask und gibt einen ResultType als completion zurück. Über den machst du einen switch bei dem call von fetchLocations und im .success case bekommst du CacheResponseModel übergeben. Das mapst du dann so wie du es brauchst auf deine CoreData-Entity und fügst es in CoraData hinzu. Wahrscheinlich wirst du das mit Methoden in deinem PersistenceController für das adden von neuen Locations anlegen.

    Das Mappen der Daten auf den Entity-Typen würde ich nicht direkt im PersistenceController machen. Denn das ist eigentlich eine separate Aufgabe. Ein MapperUseCase würde sich da anbieten.
  • Okay, also muss ich das CacheResponseModel in dem API-Aufruf separat behandeln und kann das nicht in der URLSession.shared.dataTask verarbeiten. Sprich in so einer Funktion muss ich die Responses verarbeiten?

    Quellcode

    1. NetworkManager.shared.getAllCaches { (results) in
    2. switch results {
    3. case .success(let response):
    4. print(response)
    5. break
    6. case .failure(let error):
    7. break
    8. }
    9. }
  • jep, steht genau so in meinem MapView-struct habs als @State Variable gemacht um es später noch ändern zu können.

    Quellcode

    1. @State var trackingMode: MKUserTrackingMode = .follow



    Ich übergebe diese Variable dann meinem MapModel und weise diese Variable in meinem MapModel zu.

    Quellcode

    1. ...
    2. MapModel(
    3. region: $managerDelegate.region,
    4. mapType: mapType,
    5. userTrackingMode: trackingMode,
    6. showsUserLocation: true
    7. ).edgesIgnoringSafeArea([.top])
    8. ...

    Quellcode

    1. @Binding var region: MKCoordinateRegion
    2. var mapType : MKMapType
    3. var userTracking: MKUserTrackingMode
    4. var showsUserLocation: Bool
    5. var annotation: [Annotation]
    6. init(
    7. region: Binding<MKCoordinateRegion>,
    8. mapType: MKMapType,
    9. userTrackingMode: MKUserTrackingMode,
    10. showsUserLocation: Bool = true,
    11. annotation: [Annotation] = [Annotation(title: "test", subtitle: "subtest", coordinate: CLLocationCoordinate2D(latitude: 51.507222, longitude: -0.1275))]
    12. ){
    13. self._region = region
    14. self.mapType = mapType
    15. self.userTracking = userTrackingMode
    16. self.showsUserLocation = showsUserLocation
    17. self.annotation = annotation
    18. }
    Alles anzeigen
  • Hey,

    hast du mal deine didUpdateLocations delegate function im LocationManager gedebugged? So ein manueller hasSetRegion Flag kann oft etwas fehleranfällig sein. Probier doch übergangsweise mal aus, diesen rauszunehmen bzw. das if !hasSetRegion durch true zu ersetzen bzw. dir eben diese Stelle mittels Breakpoint im Debugger anzusehen. Dadurch kannst du das Problem etwas eingrenzen - liegt es an der Datenquelle oder an der View.

    Alternativ kannst du einfach from scratch ein Tutorial nehmen wie dieses hier (codewithchris.com/swiftui/swiftui-corelocation/) und dann schrittweise zu deinem Code transformieren. Auf dem Weg solltest du vermutlich leicht erkennen können, wo sich der Fehler eingeschlichen hat.

    Viel Erfolg & VG
  • okay, vielen Dank. Ich hab die MKUserTracking Mode nun als Binding übergeben, daran lag es aber nicht.

    @322Die didUpdateLocations habe ich nun durch dein genanntes Tutorial ersetzt. Es klappt nun! :thumbup:

    Leider habe ich nun das Problem das die Map als erstes, bevor ein Standort ermittelt wurde, im Blauen startet und dann zum Standort springt, das ist erstmal halb so schlimm, nur nicht schön. Klappt ja bei der hauseigenen App Maps auch, dass dort direkt der Standort angezeigt wird.

    Ein viel nervigeres Problem, was ich jetzt habe ist, ist das alle 5 Sekunden die func updateUIView meines MapModels aufgerufen wird, welche dann die didUpdateLocations aufruft. Dadurch kann der Nutzer sich nicht frei auf der Karte bewegen und springt andauernd zurück zu seinem Standort. Ich wollte ja lediglich das der eigene Standort einmalig geladen wird.

    Ein explizites locationManager.stopUpdatingLocation() in der updateUIView in der MapModel.swift bringt auch kein entsprechendes Ergebnis.

    Ich hab noch mal die Aktualisierten Dateien angehängt.
    Dateien
    • LocationManager.txt

      (1,07 kB, 60 mal heruntergeladen, zuletzt: )
    • MapModel.txt

      (3,3 kB, 17 mal heruntergeladen, zuletzt: )

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