Tutorial 3 MyLocations page 220 ' exercise

What’s the solution?

Thanks in advance

May not be the best solution,

if let dictionary = notification.userInfo {
if (dictionary[“inserted”] != nil) || (dictionary[“deleted”] != nil){
if self.isViewLoaded() {
self.updateLocations()
}
}
}

If it works, it works. :slight_smile: However, the idea was that you’d look at what objects are "inserted" and then add those, and look at what objects are "deleted" and remove those. So instead of reloading all objects, you only insert/delete those that have changed.

Thank you for your hint.Maybe I should figure out specific method to implement it with your help

it seems updateLocations method will fetch all the objects… but anyway,I will try it.

How do we turn the notification.userInfo[“updated”] or any other key into a Location type?

Try this:

let locations = notification.userInfo[“updated”] as! [Location]

Note that it is an array, not a single Location object.

I have some problem.

Here is my code:

if self.isViewLoaded(){
if let locations = dictionary[“inserted”] as? [Location]{
self.mapView.addAnnotations(locations)
print(“\n add”)
}
if let locations = dictionary[“deleted”] as? [Location]{
self.mapView.removeAnnotations(locations)
print(“\n remove”)
}
if let locations = dictionary[“updated”] as? [Location]{
if let index = self.locations.indexOf(locations[0]){
let location = self.locations[index]
self.mapView.removeAnnotation(location)
self.mapView.addAnnotations(locations)
print(“\n update”)
}
}

When I added a location, it doesn’t appear.

And when I delete a location, the location is deleted but a longitude and latitude of 0 annotation is added
And too, my print function doesn’t show up.

I try another method here

if let location = dictionary[“inserted”] {
self.mapView.addAnnotations(location as! [Location])
}

but it say it can’t cast NSCFSet to NSArray

Ah, if it’s a set then you can’t cast it to an array, indeed. My memory was failing me there for a moment when I said it was an array.

Try this:

if let locations = dictionary["inserted"] as? Set<Location> {
    for location in locations {
       // add the location to the map
    }
}

No idea if it works or not, but it’s something like that. :slight_smile: You can also search the forums (and the old forums) for answers that other readers have posted.

Yes it works. Not sure it is the best answer, mind review for me?

if let dictionary = notification.userInfo{
if let locations = dictionary[“inserted”] as? Set {
for location in locations {
let entity = NSEntityDescription.entityForName(“Location”, inManagedObjectContext: self.managedObjectContext)
let fetchRequest = NSFetchRequest()
fetchRequest.entity = entity
self.locations = try! self.managedObjectContext.executeFetchRequest(fetchRequest) as! [Location]

self.mapView.addAnnotation(location) }
}
if let locations = dictionary[“deleted”] as? Set {
for location in locations {
let entity = NSEntityDescription.entityForName(“Location”, inManagedObjectContext: self.managedObjectContext)
let fetchRequest = NSFetchRequest()
fetchRequest.entity = entity
self.locations = try! self.managedObjectContext.executeFetchRequest(fetchRequest) as! [Location]

self.mapView.removeAnnotation(location) } }
if let locations = dictionary[“updated”] as? Set {
for location in locations {
self.mapView.removeAnnotation(location)
self.mapView.addAnnotation(location)
}

The reason I re-fetch the locations is for the regioForAnnotations: because it looks at the locations array to calculate the region.

One more question, I might not be sure about the “updated”, I assume that MK annotation can only put one pinMark at the same coordinate, and it is who came 1st who have the spot at the map. Is this true?

1 Like

This absolutely working but after deleting or updating a location object, clicking the disclosure button a particular pin shows different location object. The button tag needs to be updated. Thanks man!

Hello, Raywenderlich Forum User :slight_smile:!
Hope you are doing ok!
I was hoping you could fix that error with button tags :smiley:. It’s just so nagging.
I couldn’t get my head around it.
I would be really thankful for any help.
Thank you

The thread was started more than 3 years ago and still no solution for the exercise from the chapter 29 is published. It’s time to fill this gap.:grinning:

The code, proposed by szekirk is not optimal. Indeed, it’s less effective , than the original code in the book, as it fetches ALL the locations for each deleted or added location. For example, after going to Map tab, you can add a couple locations in Tag window, then delete a couple of other locations in Locations screen and if then you go back to Map screen, all the locations will be fetched 4 times! Meanwhile the code from the book fetches all the locations only once. The idea of the exercise is not to fetch all the locations from the database, but reload only the changed ones.
Besides, as serendipityf pointed out, szekirk’s solution has a bug: detailed disclosure button of the annotation for added location opens the Edit Location window of another location.

The reason for the bug is that the locations[Location] in MapViewController is created by fetching from database in viewDidLoad() method when the Map window is loaded for the first time. If after that you switch to Locations or Tag screen and then go back to Map screen, viewDidLoad() is not called, so the locations[Location] array is still the same. When new location is added at Tag screen, it is saved to the database, but not added to location[Location] array of MapViewController. If MKAnnotationView for new location is created with the help of notification.userInfo dictionary from NotificationCenter observer, it has correct info for the coordinates, the title( locationDescription of the location) and the subtitle ( category of the location ) and it looks quite correct at Map screen. But the action for the button in MKAnnotationView is configured wrong: The code in
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) → MKAnnotationView? sets the wrong tag for button in rightCalloutAccessoryView.

let button = annotationView.rightCalloutAccessoryView
as! UIButton
if let index = locations.index(of: annotation
as! Location) {
button.tag = index
}

The condition for if statement is false for new location as it is does not exist in locations[Location] of MapViewController. The code inside if statement is not executed and the tag of the button is appeared to be 0 by default. As a result, the function func prepare(for segue: UIStoryboardSegue, sender: Any?) puts the reference to first element of locations array ( locations[0]) in locationToEdit property of LocationsDetailesViewController and we can see it in the Edit Location screen.
The bug is resolved by adding new location to locations[Location] array.

In case the location is deleted, it also has to be removed from locations[Location] array. It does not affect MKAnotationView at Map window, but it is needed to let the method region(for:) calculate MKCoordinateRegion correctly.

The code for the exercise’s solution might be the following:
13%202

Please note, that since the topic started, index(of:) method was deprecated and replaced by firstIndex(of:).

@gavr Thank you for sharing your solution - much appreciated! :]