# V5,My Locations ,Exercise page211

**URL:** <https://forums.kodeco.com/t/v5-my-locations-exercise-page211/40763>\
**Category:** iOS Apprentice\
**Created:** [March 25, 2018, 12:43pm UTC](https://forums.kodeco.com/t/v5-my-locations-exercise-page211/40763 "2018-03-25T12:43:56Z")\
**Posts on this page:** 19\
**Page:** 1

<div class="post-metadata">

**Author:** ![milad](https://assets.chunter.kodeco.com/user_avatar/forums.kodeco.com/milad/32/79544_2.png) [@milad](https://forums.kodeco.com/u/milad)\
**Post date:** [March 25, 2018, 12:43pm UTC](https://forums.kodeco.com/t/v5-my-locations-exercise-page211/40763/1 "2018-03-25T12:43:56Z")

</div>

Hi there, I couldn’t solve the exercise I tried to cast dictionary values to Location but it gave me this error

Could not cast value of type ‘\_\_NSCFSet’ (0x10eb7cd98) to ‘MyLocations.Location’ (0x10cc202d0).  
2018-03-25 16:57:44.815777+0430 MyLocations[3170:383284] Could not cast value of type ‘\_\_NSCFSet’ (0x10eb7cd98) to ‘MyLocations.Location’ (0x10cc202d0).

---

<div class="post-metadata">

**Author:** ![fahim](https://assets.chunter.kodeco.com/user_avatar/forums.kodeco.com/fahim/32/110943_2.png) [@fahim](https://forums.kodeco.com/u/fahim)\
**Post date:** [March 25, 2018, 12:58pm UTC](https://forums.kodeco.com/t/v5-my-locations-exercise-page211/40763/2 "2018-03-25T12:58:15Z")

</div>

> [@milad](#):
>
> Hi there, I couldn’t solve the exercise I tried to cast dictionary values to Location but it gave me this error

Just giving the error message is not usually helpful since I can tell you that you are trying to cast a set to a location but unless you know how to fix the code yourself, that is not going to help you 🙂 Either provide the code which is causing the error or provide a ZIP archive of your project and somebody can take a look and help you.

---

<div class="post-metadata">

**Author:** ![milad](https://assets.chunter.kodeco.com/user_avatar/forums.kodeco.com/milad/32/79544_2.png) [@milad](https://forums.kodeco.com/u/milad)\
**Post date:** [March 25, 2018, 3:11pm UTC](https://forums.kodeco.com/t/v5-my-locations-exercise-page211/40763/3 "2018-03-25T15:11:25Z")

</div>

@fahim @hollance [MyLocations.zip](https://raywenderlich-com-v3-forums.s3.amazonaws.com/original/3X/8/b/8bd5fe0845794162decf6efba98eac37837645b3.zip) (142.8 KB)

---

<div class="post-metadata">

**Author:** ![fahim](https://assets.chunter.kodeco.com/user_avatar/forums.kodeco.com/fahim/32/110943_2.png) [@fahim](https://forums.kodeco.com/u/fahim)\
**Post date:** [March 26, 2018, 12:35am UTC](https://forums.kodeco.com/t/v5-my-locations-exercise-page211/40763/4 "2018-03-26T00:35:10Z")

</div>

Do note what it says in the book about the inserted, deleted, updated objects - “This will print out an (optional) **array** of Location objects or nil if there were no changes.”

The value for each key is an array - in your case, an array of `Location` objects. So you cannot cast that array to a single `Location` object directly. That is why you get the crash. In your particular case you might always have one item in the array - but you still have to loop through the array and get each item, or get the first item from the array if you are confident that there’ll only be one item in the array for your particular code.

Hope that makes sense?

---

<div class="post-metadata">

**Author:** ![milad](https://assets.chunter.kodeco.com/user_avatar/forums.kodeco.com/milad/32/79544_2.png) [@milad](https://forums.kodeco.com/u/milad)\
**Post date:** [March 26, 2018, 8:56am UTC](https://forums.kodeco.com/t/v5-my-locations-exercise-page211/40763/5 "2018-03-26T08:56:02Z")

</div>

I try that but it doesn’t make sense because we can add array of Location directly with  
mapView.addAnnotations([MKAnnotation])  
and we don’t need to loop through array of Location and add it one by one

---

<div class="post-metadata">

**Author:** ![fahim](https://assets.chunter.kodeco.com/user_avatar/forums.kodeco.com/fahim/32/110943_2.png) [@fahim](https://forums.kodeco.com/u/fahim)\
**Post date:** [March 26, 2018, 9:10am UTC](https://forums.kodeco.com/t/v5-my-locations-exercise-page211/40763/6 "2018-03-26T09:10:32Z")

</div>

> [@milad](#):
>
> I try that but it doesn’t make sense because we can add array of Location directly with
> 
> mapView.addAnnotations([MKAnnotation])
> 
> and we don’t need to loop through array of Location and add it one by one

I don’t know what you are trying to do since you were assigning an array of `Location` objects to a variable which is simply of type `Location`. And when I point this out, you mention something totally unrelated about `mapView.addAnnotations` 🙂 Either I don’t understand what you want, or you might need to go back and look at your code …

---

<div class="post-metadata">

**Author:** ![nobitachan88](https://s3.amazonaws.com/cdn.raywenderlich.com/community/forum-assets/noun-game-ghost-878668.png) [@nobitachan88](https://forums.kodeco.com/u/nobitachan88)\
**Post date:** [May 2, 2018, 1:17pm UTC](https://forums.kodeco.com/t/v5-my-locations-exercise-page211/40763/7 "2018-05-02T13:17:14Z")

</div>

I’m getting the “Could not cast value of type ‘\_\_NSCFSet’… error as well.  
I have tried casting the content of dictionary[”(state)"] into either Location or [Location] and both do not work.  
I get where OP milad is coming from, because theoretically it does not matter if we obtain a Location or an array of Location. I think because we can use mapView.addAnnotation(location) if the object is a single Location, or we can use mapView.addAnnotations(locations) - note the ‘s’ - if the object is an array of Location.  
My code is as below.

```
var managedObjectContext: NSManagedObjectContext! {
        didSet{
            NotificationCenter.default.addObserver(forName: Notification.Name.NSManagedObjectContextObjectsDidChange, object: managedObjectContext, queue: OperationQueue.main) { notification in
                if let dictionary = notification.userInfo {
                    if self.isViewLoaded {
                        if dictionary["inserted"] != nil {
                            let locationToProcess = dictionary["inserted"] as! [Location]
                            self.mapView.addAnnotations(locationToProcess)
                        }
                        if dictionary["deleted"] != nil {
                            let locationToProcess = dictionary["deleted"] as! [Location]
                            self.mapView.removeAnnotations(locationToProcess)
                        }
                        if dictionary["updated"] != nil {
                            let locationToProcess = dictionary["updated"] as! [Location]
                            self.mapView.removeAnnotations(locationToProcess)
                            self.mapView.addAnnotations(locationToProcess)
                        }
                    }
                }
            }Preformatted text

```

---

<div class="post-metadata">

**Author:** ![fahim](https://assets.chunter.kodeco.com/user_avatar/forums.kodeco.com/fahim/32/110943_2.png) [@fahim](https://forums.kodeco.com/u/fahim)\
**Post date:** [May 2, 2018, 1:52pm UTC](https://forums.kodeco.com/t/v5-my-locations-exercise-page211/40763/8 "2018-05-02T13:52:40Z")

</div>

> [@nobitachan88](#):
>
> I’m getting the “Could not cast value of type ‘\_\_NSCFSet’… error as well.
> 
> I have tried casting the content of dictionary[”(state)"] into either Location or [Location] and both do not work.

The issue with this type of error report is that without knowing which line the crash occurs on, it is hard to know what is going on 🙂 I know you provided your code, but does the crash occur in there? Or does it happen elsewhere?

With the previous issue, I did take a look at @milad’s code and I believe I found the issue and told him what the issue was. In your case, if you can provide a ZIP of your project, I can do the same. Alternatively, you can try the supplied final project for the book and see if that crashes as well. If not, then you can compare your code to the final project code to see what might be going wrong.

---

<div class="post-metadata">

**Author:** ![nobitachan88](https://s3.amazonaws.com/cdn.raywenderlich.com/community/forum-assets/noun-game-ghost-878668.png) [@nobitachan88](https://forums.kodeco.com/u/nobitachan88)\
**Post date:** [May 2, 2018, 3:15pm UTC](https://forums.kodeco.com/t/v5-my-locations-exercise-page211/40763/9 "2018-05-02T15:15:05Z")

</div>

After looking at some of the older answers for this exercise I found the problem.  
The object inside the dictionary[] is not an Array but a Set.  
So instead of

```
let locationToProcess = dictionary["inserted"] as! [Location]
                            self.mapView.addAnnotations(locationToProcess)

```

Cast object to Set before casting the Set to an Array

```
let set = dictionary["inserted"] as! Set<Location>
let locationToProcess = Array(set)
self.mapView.addAnnotations(locationToProcess)

```

---

<div class="post-metadata">

**Author:** ![h1i2j3](https://assets.chunter.kodeco.com/user_avatar/forums.kodeco.com/h1i2j3/32/100893_2.png) [@h1i2j3](https://forums.kodeco.com/u/h1i2j3)\
**Post date:** [June 23, 2018, 12:14am UTC](https://forums.kodeco.com/t/v5-my-locations-exercise-page211/40763/10 "2018-06-23T00:14:56Z")

</div>

> [@nobitachan88](#):
>
> The object inside the dictionary is not an Array but a Set.

Thank you for this!  
I had a controlled crash: Thread1: SIGABRT

LLDeBugger output:

> Could not cast value of type ‘\_\_NSCFSet’ (0x104245e78) to ‘NSArray’ (0x104246008).  
> 2018-06-22 16:30:19.855458-0400 MyLocations[6132:151071] Could not cast value of type ‘\_\_NSCFSet’ (0x104245e78) to ‘NSArray’ (0x104246008).  
> (lldb)

Source of compile error:

```auto
if userInfo["inserted"] != nil {
      let locations = userInfo["inserted"] as! [Location] // <- Breakpoint
      self.mapView.addAnnotations(locations)
    }

```

* * *

## My solution attempt

My solution was to overload `updateLocations()` with `updateLocations(from:)` - a new one that passes the Notification.userInfo dictionary.

For starters **I added a description property to Location+CoreDataProperties.swift** to make the printout reader friendly:

> **Location+CoreDataProperties :: var description: String**
>
> ```auto
> extension Location {
> ...
>   
> override public var description: String {
> return
> """
>       
> Entity: \(locationDescription), of category: \(category), at \(date).
> Coordinate(latitude: \(latitude), longitude: \(longitude))
> """
> }
> 
> ```
> 
> * * *

In **MapViewController.swift**  
Within `managedObjectContext`’s property observer:  
I assigned `note.userInfo` to the constant `dictionary`, and if `self.isViewLoaded` is true, `dictionary` is passed it into `self.updateLocations(from:)`

> **var managedObjectContext: NSManagedObjectContext!**
>
> ```auto
> var managedObjectContext: NSManagedObjectContext! {
> didSet { // property observer
> NotificationCenter.default.addObserver(
> forName: Notification.Name.NSManagedObjectContextObjectsDidChange,
> object: managedObjectContext,
> queue: OperationQueue.main) { note in
> /// 697 Exercise
> if let dictionary = note.userInfo {
> if self.isViewLoaded {
> self.updateLocations(from: dictionary) // takes an argument
> }
> }
> }
> }
> }
> 
> ```
> 
> * * *

I overloaded `func updateLocations()` to take `dictionary` type `[AnyHashable : Any]` as an argument.  
`func updateLocations(from userInfo: [AnyHashable : Any])`  
Within it I created a closure to do the work that was identical for getting the values from all 3 keys, so the keys were passed into the closure, so the logic adjusts.

> **Initial code :: updateLocations(from:)**
>
> ```auto
> func updateLocations(from userInfo: [AnyHashable : Any]) {
> // Selected code from updateLocations(), copied here for persistence
> let entity = Location.entity()
>   
> let fetchRequest = NSFetchRequest<Location>()
> fetchRequest.entity = entity
>   
> locations = try! managedObjectContext.fetch(fetchRequest)
>   
> /// Exercise: Only insert or delete the items that have changed.
>   
> let location: (String) -> [Location] = {
> let locations = userInfo[$0] as! Set<Location>
> print(" ! location \($0): \(locations)") // Prints Entity details
> return Array(locations) }
>   
> let states = ["inserted", "deleted", "updated"]
> states.forEach { state in
> if userInfo[state] != nil {
> switch state {
> case "inserted": self.mapView.addAnnotations(location("inserted"))
> case "deleted": self.mapView.removeAnnotations(location("deleted"))
> case "updated": self.mapView.removeAnnotations(location("updated"))
> self.mapView.addAnnotations(location("updated"))
> default: return
> }
> }
> }
> }
> 
> ```
> 
> * * *

I didn’t like the string repetition, and a default in the switch made no sense since the 3 options were exhaustive… so I made an enum `State`, and modified my closure to take the State type, and my switch statement looked cleaner. 😊👍

## Final Version :: updateLocations(from:)

```auto
func updateLocations(from userInfo: [AnyHashable : Any]) {
  // Selected code from updateLocations(), copied here for persistence
  let entity = Location.entity()
  
  let fetchRequest = NSFetchRequest<Location>()
  fetchRequest.entity = entity
  
  locations = try! managedObjectContext.fetch(fetchRequest)

/// Exercise: Only insert or delete the items that have changed.
enum State: String {
  case inserted, deleted, updated
}

let location: (State) -> [Location] = {
  let locations = userInfo[$0.rawValue] as! Set<Location>
  print(" ! location \($0): \(locations)") // Prints Entity details
  return Array(locations) }

let states: [State] = [.inserted, .deleted, .updated]

states.forEach { state in
  if userInfo[state.rawValue] != nil {
    switch state {
    case .inserted : self.mapView.addAnnotations(location(.inserted))
    case .deleted : self.mapView.removeAnnotations(location(.deleted))
    case .updated: self.mapView.removeAnnotations(location(.updated))
    self.mapView.addAnnotations(location(.updated))
    }
  }
}

```

I’m satisfied with the results.  
If anyone spots any errors, or areas where I can improve, please let me know!  
BTW, This took almost 4 HOURS to accomplish! Please tell me this gets easier, oh my goodness…

Cheers!  
Rebecca

---

<div class="post-metadata">

**Author:** ![h1i2j3](https://assets.chunter.kodeco.com/user_avatar/forums.kodeco.com/h1i2j3/32/100893_2.png) [@h1i2j3](https://forums.kodeco.com/u/h1i2j3)\
**Post date:** [June 23, 2018, 4:39pm UTC](https://forums.kodeco.com/t/v5-my-locations-exercise-page211/40763/11 "2018-06-23T16:39:20Z")

</div>

> [@fahim](#):
>
> In your particular case you might always have one item in the array - but you still have to loop through the array and get each item, or get the first item from the array if you are confident that there’ll only be one item in the array for your particular code.

This got me thinking that the answer should be much simpler than my previous attempt. I reread the chapter focusing specifically on `var locations`, `updateLocations()`, `var managedObjectContext`, and a bit on `prepare(for:sender:)` to understand how `managedObjectContext` & `locations` work together, and how `updateLocations()` ties them together.

## Second Solution Attempt

```auto
var managedObjectContext: NSManagedObjectContext! {
  didSet { // Property Observer
    NotificationCenter.default.addObserver(
      forName: Notification.Name.NSManagedObjectContextObjectsDidChange,
       object: managedObjectContext,
        queue: OperationQueue.main) { note in
        if let userInfo = note.userInfo {
          if self.isViewLoaded {
            /// Page 679 Exercise: Only insert or delete the items that have changed.
            let entity = Location.entity()
            let fetchRequest = NSFetchRequest<Location>()
            fetchRequest.entity = entity
            self.locations = try! self.managedObjectContext.fetch(fetchRequest)
            
            enum State: String { case inserted, deleted, updated }
            let states: [State] = [.inserted, .deleted, .updated]
            
            for location in self.locations {
              states.forEach { state in
                if userInfo[state.rawValue] != nil {
                  switch state {
                  case .inserted: self.mapView.addAnnotation(location)
                  case .deleted: self.mapView.removeAnnotation(location)
                  case .updated: self.mapView.removeAnnotation(location)
                                 self.mapView.addAnnotation(location)
                  }
                }
              }
            }
          }
        }
    }
  }
}

```

This is much simpler!  
I did away with extracting the sequence values from the dictionary (which was giving everyone their errors), it wasn’t a necessary step.  
Instead I looped through the location objects, and looped through each state within each location object. If one had a state that was not nil, update the mapView accordingly.

Cheers!  
Rebecca

## EDIT: Memory Comparison

I compared this code’s memory usage (avg 175MB) to the original code found in the book(avg 195MB), and **the difference was 20MB! So cool!**

---

<div class="post-metadata">

**Author:** ![sgerrard](https://assets.chunter.kodeco.com/user_avatar/forums.kodeco.com/sgerrard/32/93812_2.png) [@sgerrard](https://forums.kodeco.com/u/sgerrard)\
**Post date:** [June 24, 2018, 10:07pm UTC](https://forums.kodeco.com/t/v5-my-locations-exercise-page211/40763/12 "2018-06-24T22:07:23Z")

</div>

> [@h1i2j3](#):
>
> Instead I looped through the location objects, and looped through each state within each location object. If one had a state that was not nil, update the mapView accordingly.

I don’t think this code is doing that:

```
       for location in self.locations {
          states.forEach { state in
            if userInfo[state.rawValue] != nil {

```

1. loop through each location - check
2. loop through each possible state - check
3. If one had a state that was not nil - nope

I am curious if you think that looking at userInfo[state.rawValue] is somehow looking at the state specifically for the current location - because I want to know how people form these misunderstandings.

It is plain to me that userInfo(state.rawValue] will not be nil if there are any locations with that state; it will be a Set of locations that have that state. It wouldn’t be specific to the location you are looking at unless you told it to be, which you haven’t done.

If this code seems to work, it is because it is getting just one change at a time. As I see it, if there is one insert, it will insert all of the locations again. If there is one deletion, it will delete all of the locations. What happens when you run it with 3 locations in the list, and you delete one?

---

<div class="post-metadata">

**Author:** ![h1i2j3](https://assets.chunter.kodeco.com/user_avatar/forums.kodeco.com/h1i2j3/32/100893_2.png) [@h1i2j3](https://forums.kodeco.com/u/h1i2j3)\
**Post date:** [June 25, 2018, 8:52pm UTC](https://forums.kodeco.com/t/v5-my-locations-exercise-page211/40763/13 "2018-06-25T20:52:05Z")

</div>

> [@sgerrard](#):
>
> I am curious if you think that looking at userInfo[state.rawValue] is somehow looking at the state specifically for the current location - because I want to know how people form these misunderstandings.

I actually didn’t think it would work, but since I was only testing 1 or 2 locations at a time I assumed it was the way mapView was adding/removing annotations that was making it work.

> [@sgerrard](#):
>
> It wouldn’t be specific to the location you are looking at unless you told it to be, which you haven’t done.

In this new code I made sure it was specific to the location, and I created a global var  
`var currentIndex: Int?` to hold the current location index to sync the button touched to `locationToEdit`.

**But when I edit a location on the map, it doesn’t always load the correct location to edit.**

```auto
// MARK: - Navigation
extension MapViewController {
  override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    guard segue.identifier == "EditLocation" else { return }
    
    let controller = segue.destination as! LocationDetailsViewController
    controller.managedObjectContext = managedObjectContext
    
    let button = sender as! UIButton
    let index = currentIndex ?? button.tag // added nil coalescading operator
    let location = locations[index]
    controller.locationToEdit = location
  }
}

```

```auto
var managedObjectContext: NSManagedObjectContext! {
  didSet { // Property Observer
    NotificationCenter.default.addObserver(
      forName: Notification.Name.NSManagedObjectContextObjectsDidChange,
      object: managedObjectContext,
      queue: OperationQueue.main) { note in
        if let userInfo = note.userInfo {
          
          if self.isViewLoaded {
            /// Page 679 Exercise: Only insert or delete the items that have changed.
            let entity = Location.entity()
            let fetchRequest = NSFetchRequest<Location>()
            fetchRequest.entity = entity
            self.locations = try! self.managedObjectContext.fetch(fetchRequest)
            
            enum State: String { case inserted, deleted, updated }
            let states: [State] = [.inserted, .deleted, .updated]
            
            var inserted = [Location]()
            var deleted = [Location]()
            
            let location: (State) -> [Location] = {
              let locations = userInfo[$0.rawValue] as! Set<Location>
              return Array(locations)
            }
            
            states.forEach { state in
              if userInfo[state.rawValue] != nil {
                switch state {
                case .inserted: inserted.append(contentsOf: location(.inserted))
                case .deleted: deleted.append(contentsOf: location(.deleted))
                case .updated: inserted.append(contentsOf: location(.updated))
                               deleted.append(contentsOf: location(.updated))
                }
              }
            }
            
            self.locations.enumerated().forEach { (locationIndex, location) in
              if inserted.contains(location) {
                self.mapView.addAnnotation(location)
                self.currentIndex = locationIndex
                if let i = inserted.index(of: location) {
                  inserted.remove(at: i)
                }
                self.mapView.view(for: self.locations[self.currentIndex!])?.reloadInputViews() // refresh pin
              }
              if deleted.contains(location) {
                self.mapView.removeAnnotation(location)
                if let i = deleted.index(of: location) {
                  deleted.remove(at: i)
                }
              }
            }
          }
        }
    }
  }
}

```

Thank you for taking a look a my code and pointing out my errors!  
Where do you suggest I change my code to make it work as it should?  
Cheers!

---

<div class="post-metadata">

**Author:** ![sgerrard](https://assets.chunter.kodeco.com/user_avatar/forums.kodeco.com/sgerrard/32/93812_2.png) [@sgerrard](https://forums.kodeco.com/u/sgerrard)\
**Post date:** [June 26, 2018, 2:59am UTC](https://forums.kodeco.com/t/v5-my-locations-exercise-page211/40763/14 "2018-06-26T02:59:38Z")

</div>

> [@h1i2j3](#):
>
> But when I edit a location on the map, it doesn’t always load the correct location to edit.

Oh good, I’m glad you found this issue. It is worse than that, though. Figure out which location in your list is the last one, then go to the map and tap it so the annotation is showing. Now go back to the list and delete a location before the last one. Now go back to the map, where the last one is still showing, and tap it to edit. It should bust. (The list view is sorted by category and description, making it harder to test, unless you keep the same category for all, and label them A, B, C, or something).

When you delete a location from the list, the index of every location after it changes. The index of the last one goes down -1. The index stored in the annotation button tag is not only the wrong one, but is no longer a valid one. When you tap the button, you get nothing, and it breaks. (This doesn’t happen if you use the original updateLocations every time, as it just clears out all the annotations and loads them up again).

* * *

I think if you look at your code, you will realize you are using the sets that were passed in userInfo. So you might as well just use them directly. Then you don’t need to rerun the fetch.

> if self.isViewLoaded, let dictionary = notification.userInfo {
> 
> ```
> if let insertLocations = dictionary["inserted"] as? Set<Location> {
> for location in insertLocations {
> self.locations.append(location)
> self.mapView.addAnnotation(location)
> print("*** map added a location")
> }
> } 
> 
> ```

You can just get the sets and process the locations in them. A set is a lot like a list, same syntax to loop through. You could still use your nice enum for the state.

The deleteLocations loop will be very similar, except you also need the index of the location to remove it from the locations list:

> if let index = self.locations.index(of: location) {

Updated ones are interesting. What people tend to forget is that your Locations _are_ MKAnnotations. Unless the coordinates of the location changes, there is nothing that needs to be updated. Even then, I think the map display would pick up the change. There is not much point in removing them and adding them again - they are already there.

* * *

The visible annotation view, if there is one, is different. It is a view built from the location, so it should be updated. In my opinion, it should stay selected. So you do a bit of code to refresh the selected annotation if there is one:

```
      for location in self.mapView.selectedAnnotations {
        if let view = self.mapView.view(for: location), view.isSelected {
          self.mapView.deselectAnnotation(location, animated: false)
          self.mapView.selectAnnotation(location, animated: false)
          print("*** map updated a selected callout")
        }
      }

```

selectedAnnotations is an array, even though there is only ever 0 or 1 annotation in it. So we do a for loop anyway. (Maybe iOS 27 will support multiple selected annotations?).

The refresh causes it to rebuild the view for the annotation, so it gets the correct index into the button tag. It also will display your edit if you changed the title or category - even if you edited it from the list tab, not the map tab. It’s all wired up!

---

<div class="post-metadata">

**Author:** ![h1i2j3](https://assets.chunter.kodeco.com/user_avatar/forums.kodeco.com/h1i2j3/32/100893_2.png) [@h1i2j3](https://forums.kodeco.com/u/h1i2j3)\
**Post date:** [June 26, 2018, 1:45pm UTC](https://forums.kodeco.com/t/v5-my-locations-exercise-page211/40763/15 "2018-06-26T13:45:17Z")

</div>

@sgerrard Steve,

Implemented your changes and the app works flawlessly! Thank you for your help, I learned so much!

> [@sgerrard](#):
>
> selectedAnnotations is an array, even though there is only ever 0 or 1 annotation in it. So we do a for loop anyway. (Maybe iOS 27 will support multiple selected annotations?).

This got me thinking of multiple annotations - which ultimately got me thinking about how photos are “clustered”. I’d like to attempt that once I’ve completed the Locations app, or after I’ve completed the final tutorial/book.

Searching a bit on Apple’s online documentation lead me to this: [**→ Decluttering a Map with MapKit Annotation Clustering**](https://developer.apple.com/documentation/mapkit/mkannotationview/decluttering_a_map_with_mapkit_annotation_clustering)

A demo app is available for download _TANDm_ on that page.  
Note: It won’t build unless you modify two dictionary entries within ClusterAnnotationView.swift where the text is drawn:

```auto
let attributes = [ NSAttributedString.Key.foregroundColor: UIColor.black,
                   NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 20)]

```

NSAttributedString.Key needs to be changed to NSAttributedStringKey

```auto
let attributes = [ NSAttributedStringKey.foregroundColor: UIColor.black,
                   NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 20) ]

```

My plan is to cluster based on Categories.

Thank you again for your help!  
Rebecca

---

<div class="post-metadata">

**Author:** ![h1i2j3](https://assets.chunter.kodeco.com/user_avatar/forums.kodeco.com/h1i2j3/32/100893_2.png) [@h1i2j3](https://forums.kodeco.com/u/h1i2j3)\
**Post date:** [June 27, 2018, 3:14pm UTC](https://forums.kodeco.com/t/v5-my-locations-exercise-page211/40763/16 "2018-06-27T15:14:46Z")

</div>

Pages 717 & 718 introduces a Closure’s ownership cycle and a closure’s capture list → [weak self]

In **MapViewController.swift** I added a capture list [weak self] to `managedObjectContext`’s `addObserver(forName:object:queue:using:)`

```auto
var managedObjectContext: NSManagedObjectContext! {
    didSet {
      NotificationCenter.default.addObserver(
        forName: Notification.Name.NSManagedObjectContextObjectsDidChange,
         object: managedObjectContext,
          queue: OperationQueue.main) { [weak self] note in // capture list
          guard let weakSelf = self else { return } // weakSelf: MapViewController
          
          if let userInfo = note.userInfo, weakSelf.isViewLoaded { // weakSelf
            /// Page 679 Exercise: Only insert or delete the items that have changed.
            enum State: String { case inserted, deleted }

            ... replaced all instances of self with weakSelf ...

```

**Question** Was the capture list necessary - Is there a difference between a closure that captures a strong ref to self in _this_ `addObserver(forName:object:queue:using:)` and the one in the book example in **LocationDetailsViewController.swift** where `addObserver(forName:object:queue:using:)` is called within listen`ForBackgroundNotification()`.

**Question** If there is a difference - what makes them different?

**Question** If there is no difference - do I need to create an instance var similar to **LocationDetailsViewController.swift** ’s ` var observer: Any!` and `deinit{}` it here in this ViewController as well?

* * *

**Fatal Error** When I do a fresh install, and never select the Map tab (mapView is nil), I get a fatal error if I create a annotation because mapView is nil.

The code is breaking: Thread 1: Fatal error: Unexpectedly found nil while unwrapping an optional value  
It appears the mapVIew is nil.  
 ![image](https://raywenderlich-com-v3-forums.s3.amazonaws.com/original/3X/3/3/33ba19ce43193df92d36ab2055a1050ee5de4722.png)

![image](https://raywenderlich-com-v3-forums.s3.amazonaws.com/original/3X/3/a/3a0cbf2a9501d8551dce7a7aac67a7eca06edf95.png)

I managed to remedy this with a guard let statement.

```auto
// refresh the selected annotation (if exists)
guard let mapView = weakSelf.mapView else { return } // <- added
mapView.selectedAnnotations.forEach { location in
  if let view = mapView.view(for: location), view.isSelected {
    mapView.deselectAnnotation(location, animated: false)
    mapView.selectAnnotation(location, animated: false)
    print("*** map updated a selected callout: \(location)")
  }
}

```

Cheers!  
Rebecca

---

<div class="post-metadata">

**Author:** ![sgerrard](https://assets.chunter.kodeco.com/user_avatar/forums.kodeco.com/sgerrard/32/93812_2.png) [@sgerrard](https://forums.kodeco.com/u/sgerrard)\
**Post date:** [June 28, 2018, 12:50am UTC](https://forums.kodeco.com/t/v5-my-locations-exercise-page211/40763/17 "2018-06-28T00:50:26Z")

</div>

The MapViewController is located in one of the tabs of the app. That means it will be created at startup, and stay present until the app exits. That means self will always be something. (Technically it is an owned self). The class object is created at startup, and the managedObjectContext gets set, but the view doesn’t load until you first click the tab.

The LocationDetailsViewController, on the other hand, is created every time you start an edit, and discarded every time you close the edit view (Cancel or Done). So it will not always be something. That’s why it needs to check for self, and why it is good to remove the notification in DeInit.

The code for refreshing the annotation should be inside the closure for the notification, and also inside the test at the start of it:

> if let userInfo = note.userInfo, weakSelf.isViewLoaded {  
> // do inserts  
> // do deletes  
> // refresh annotations  
> }

Then it won’t get to the refresh annotations part if the view has not been loaded yet. That test at the beginning, by the way, is a good candidate for turning into a guard statement.

---

<div class="post-metadata">

**Author:** ![h1i2j3](https://assets.chunter.kodeco.com/user_avatar/forums.kodeco.com/h1i2j3/32/100893_2.png) [@h1i2j3](https://forums.kodeco.com/u/h1i2j3)\
**Post date:** [June 28, 2018, 3:01pm UTC](https://forums.kodeco.com/t/v5-my-locations-exercise-page211/40763/18 "2018-06-28T15:01:19Z")

</div>

> [@sgerrard](#):
>
> The code for refreshing the annotation should be inside the closure for the notification

This was my issue. At one point it was within it, but after so many edits, I lost track of that one bracket 😉

> [@sgerrard](#):
>
> The MapViewController is located in one of the tabs of the app. That means it will be created at startup, and stay present until the app exits. That means self will always be something. (Technically it is an owned self). The class object is created at startup, and the managedObjectContext gets set, but the view doesn’t load until you first click the tab.
> 
> The LocationDetailsViewController, on the other hand, is created every time you start an edit, and discarded every time you close the edit view (Cancel or Done). So it will not always be something. That’s why it needs to check for self, and why it is good to remove the notification in DeInit.

Steve, that was a perfect explanation that has fundamentally changed the way I think about ViewController objects and their life. I can’t thank you enough for this. 👍 👍 👍

Rebecca

---

<div class="post-metadata">

**Author:** ![system](https://s3.amazonaws.com/cdn.raywenderlich.com/community/forum-assets/noun-game-ghost-878668.png) [@system](https://forums.kodeco.com/u/system)\
**Post date:** [September 8, 2018, 4:51am UTC](https://forums.kodeco.com/t/v5-my-locations-exercise-page211/40763/19 "2018-09-08T04:51:10Z")

</div>

This topic was automatically closed after 166 days. New replies are no longer allowed.
