Greetings,
I'm a long time programmer, but totally new to Swift. I have some experience with Objective-C, but most of my historical training and work has been done in procedural languages (C, Fortran, Pascal, etc.).
I've written a simple application on MacOS (Xcode 10.2, Mojave) to load a set of addresses from a file and plot them as annotations on a MapKit view. This is functionality that I wish were in the standard Maps app but, alas, it is not. It seemed rather simple, so I created a basic Map application (MyMaps) that has "Load Annotations..." and "Clear Annotations" functionality. Almost everything works exactly as I want -- I can load multiple files of annotations, and clear them successfully. But my problem is the SEQUENCE of execution... At the end of loading a set of annotations, I'd like to center the map on the "center of gravity" of the annotations, and have the zoom level encompass the collection of annotations. I think I know all the code to do this -- but it does not seem to be getting executed in the order I would expect -- hence, I'm getting unexpected results. The code is:
//
// MyAnnotations.swift
// MyMaps
//
// Created by Scott Hurd on 2019-07-03.
// Copyright © 2019 Scott Hurd. All rights reserved.
//
import Cocoa
import Foundation
import MapKit
var arrayOfStrings: [String]?
extension ViewController {
func createAnnotations(mapView: MKMapView!) {
let dialog = NSOpenPanel();
dialog.title = "Choose a file";
dialog.showsResizeIndicator = true;
dialog.showsHiddenFiles = false;
dialog.canChooseDirectories = true;
dialog.canCreateDirectories = true;
dialog.allowsMultipleSelection = false;
// dialog.allowedFileTypes = ["txt"];
if (dialog.runModal() == NSApplication.ModalResponse.OK) {
let result = dialog.url // Pathname of the file
if (result != nil) {
let path = result!.path
print("File Selected: ",path,"\n")
// var arrayOfStrings: [String]?
do {
let data = try String(contentsOfFile:path, encoding: String.Encoding.utf8)
arrayOfStrings = data.components(separatedBy: "\n")
print(arrayOfStrings ?? "")
} catch let err as NSError {
// do something with Error
print(err)
}
// filename_field.stringValue = path
}
} else {
// User clicked on "Cancel"
return
}
print("Starting to Process File")
var finalCenter: CLLocationCoordinate2D
var finalCenterSet: Bool = false
var rateCount: Int
rateCount = 0
finalCenter = CLLocationCoordinate2DMake(0, 0)
for loc in arrayOfStrings ?? [""] {
print ("Geocoder call: ",loc)
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(loc) { [weak self] placemarks, error in
if let placemark = placemarks?.first, let location = placemark.location {
let mark = MKPlacemark(placemark: placemark)
if (!finalCenterSet) {
finalCenter = location.coordinate
finalCenterSet = true
print ("Set final Center: ",finalCenter)
}
self?.mapView.addAnnotation(mark)
print("Annotation added")
} else
{
print ("Error ",error as Any," in Geocoding for: ",loc)
}
}
}
print ("Setting Region: Center at: ",finalCenter)
let coordRegion = MKCoordinateRegion(center: finalCenter,latitudinalMeters: 10000,longitudinalMeters: 10000)
self.mapView.setRegion(coordRegion, animated: true)
}
}
I would expect this to:
- open the file dialog to load the strings from the file (this works)
- use a FOR loop to go through the strings, calling CLGeocoder() for each (this seems to work)
- for each Geocoder call, if it is successful, create a placemark and annotate the map (this seems to work)
- if there is an error, don't add a mark, and print the error (this seems to work)
- in the FOR loop, I'm trying to retain the FIRST coordinates that get annotated using a simple flag and assignment
- AFTER the FOR loop, I want to set the Region Center and radius
All the annotations show up correctly on the map, and any errors are reported to the console (and are verifiable by checking them in the actual Maps application, where they also report an error). But the sequence of log entries in the console is:
"Starting to process file"
"Geocoder call" (repeated for each call)
"Setting Region: Center at: CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0)
"Set final Center: CLLocationCoordinate2D(latitude: -33.7199, longitude: 150.991)
"Annotation added" (repeated for each annotation)
This is what is puzzling me. How can I be seeing the "Geocoder call" for each entry in the FOR loop in order, but NOT the "Annotation added" confirmation at the same time?
Somehow, the order of execution seems to be that all the Geocoder calls are made, THEN the Region Center gets set (which is at the BOTTOM of the code, AFTER the FOR loop), and THEN the "final Center" gets set (which should be happening on the FIRST annotation being made), followed by ALL the "Annotation added" indicators.
So, I'm very confused as to this order of execution. I'm sorry if this is a very basic question -- but can anyone help me understand the flow here?
Many, many thanks!
Scott