Trouble understanding how to get index from 'annotation as! Location'

Please help

on pg 690 of v6

I’m trying to understand how

locations.index(of: annotation as! Location)

gives you the index of the array?

how does annotation which is an MKAnnotation parameter operate with Location which is a class referencing MKAnnotation

locations is an array of Location objects and index(of:) returns the index position for a given object in the array, that part is clear, correct? And if you notice, the code casts the annotation object to be of type Location in index(of:) - so as long as the annotation object can be cast to type Location successfully, you are in effect passing a Location object to index(of:).

And if you look at the updateLocations method (the last line) you will notice that the annotations added to the map view are the locations array. So we are indeed passing Location objects to the map view as annotations. So the casting to Location in index(of:) should indeed succeed.

Hope that helps? (If not, also look at the declaration of Location to see what the base class for Location is …)

1 Like

Thank you fahim, that makes things clearer.

So ‘annotation as! Location’ equates to an object of the locations array

I’m trying to extract the index of an array I’ve stored in iCloud, it keeps prompting me for a CKRecord object.

Any ideas what I should use?

No, ‘annotation as! Location’ does not equate to an object of the locations array - at least, in one sense in that the code does not simply make the annotation object equal to an object in the locations array. You are casting an object of type MKAnnotation to be of type Location since Swift is a strongly typed language. This means that whenever you use a variable or pass something as a function argument, Swift checks that it is of the correct type. And since the locations array is of type Location, you need to pass it an object of type Location when you call index(of:).

Forgive me for jumping to conclusions, but It feels to me as if you are just looking at this specific bit of code and have not gone through the code up to this point since if you had, you should understand about casting one type of object to another since that is what is happening here. So you might want to go through this section of the book once again if it does not make sense …

If this is not the case and I have not explained things properly, do let me know and I will try to explain better.

As far as your other question goes, since that has nothing at all to do with the code from “iOS Apprentice” and I don’t have enough context to answer that question, I am not able to answer that one, sorry.

1 Like

Thank you again fahim

You have been very helpful, I will go back and try to learn more deeply about casting, I thought I had a handle on it.

Sure thing :slight_smile: If you have any additional questions, feel free to post here or on a new thread and I’ll try to clarify as much as I can.

@soxapps,

Check out this example:

protocol Dog {
    func bark() -> Void
}

class Beagle: Dog {
    func bark() {
        print("Woof, woof!")
    }
}

class Poodle: Dog {
    func bark() {
        print("Bow wow!")
    }
}

class Chihuahua: Dog {
    func bark() {
        print("Yap, yap!")
    }
}

let dog1: Dog = Beagle()
let dog2: Dog = Poodle()
let dog3: Dog = Chihuahua()

let dogs = [dog1, dog2, dog3]   //=> dogs: [Dog]
dogs.forEach {$0.bark()}  //Ugly, weird Swift syntax

--output:--
Woof, woof!
Bow wow!
Yap, yap!

That demonstrates how different classes that implement a protocol can all be stored in a variable whose type is the protocol name.

Similarly, you can do this:

protocol MyAnnotation {
    var coordinate: (Int, Int) {get}
}

class Location: NSObject, MyAnnotation {
    var coordinate: (Int, Int)
    
    init(_ x: Int, _ y: Int) {
        coordinate = (x, y)
    }
    
    func describe() {
        print("I am a Location object.")
    }
}

var annotation: MyAnnotation = Location(1, 2)
//annotation.describe() //=> error: value of type 'MyAnnotation has no member 'describe'
(annotation as! Location).describe()  //=> "I am a Location object."

In that example, a Location object that is stored in a variable of type MyAnnotation can be cast to a Location type, then the Location method can be called on the Location object.

On p. 690 the method:

func mapView(_ mapView: MKMapView, 
              viewFor annotation: MKAnnotation) -> MKAnnotationView?

has a parameter variable viewFor/annotation that is of type MKAnnotation, which is a protocol. Because the code later in the example tries to cast the annotation variable to type Location, you can assume that a Location object (remember the Location class implements the MKAnnotation protocol), was assigned to the annotation: MKAnnotation variable. We know that is possible because of the previous examples I posted.

The mapView(_:viewFor:) method is a delegate method, so iOS is calling that method, and iOS must be calling the delegate method with one of your Location objects as the viewFor/annotation argument. Therefore, later in the code you are able to cast the annotation: MKAnnotation variable to a Location type.

1 Like

thank you 7stud

those are some very interesting examples which I will investigate further but I’m not sure they answer my problem

I probably didn’t emphasise enough that I’m still trying to understand how the code that follows

locations.index(of: ...

gives values from zero to x number of annotations?

I can see that the code is repeated for every annotation in the array and I know that index(of: ... ) will search for the exact matching data but how in this case is the integer generated?

the examples I have for using index(of: ... ) use strings like “cat” in an array of animals, which is perfectly clear, how does annotation as! Location equate in this case?

The array method index(of:) takes some object as an argument, then it searches the array for that object. The method returns an Int?. If the object is not in the array, the method returns nil otherwise it returns an Int. Here is a simple example:

protocol MyAnnotation {
    var coordinate: (Int, Int) {get}
}

class Location: NSObject, MyAnnotation {
    var coordinate: (Int, Int)
    
    init(_ x: Int, _ y: Int) {
        coordinate = (x, y)
    }
    
    func describe() {
        print("I am a Location object with coordinates: (\(coordinate.0), \(coordinate.1))")
    }
}


let loc1 = Location(1,2)
let loc2 = Location(3,4)
let loc3 = Location(5,6)
let loc4 = Location(7,8)

let locations = [loc1, loc2, loc3]

if let index = locations.index(of: loc3) {
    print(index)
}
else {
    print("not found")
}

if let index = locations.index(of: loc4) {
    print(index)
}
else {
    print("not found")
}

--output:--
2
not found

Now, here is an example with a cast thrown in:

protocol MyAnnotation {
    var coordinate: (Int, Int) {get}
}

class Location: NSObject, MyAnnotation {
    var coordinate: (Int, Int)
    
    init(_ x: Int, _ y: Int) {
        coordinate = (x, y)
    }
    
    func describe() {
        print("I am a Location object with coordinates: (\(coordinate.0), \(coordinate.1))")
    }
}

let loc1 = Location(1,2)
let loc2 = Location(3,4)
let loc3 = Location(5,6)
let loc4 = Location(7,8)

let locations = [loc1, loc2, loc3]

let a1: MyAnnotation = loc2
let someLocation = a1 as! Location

if let index = locations.index(of: someLocation) {
    print(index)
}
else {
    print("not found")
}

--output:--
1

Or, equivalently:

...
let locations = [loc1, loc2, loc3]
let a1: MyAnnotation = loc2

if let index = locations.index(of: a1 as! Location) {
    print(index)
}
else {
    print("not found")
}

--output:--
1
1 Like

I’ve just found in the Apple Developer Documentation this definition

Return Value
The first index where element is found. If element is not found in the collection, returns nil.

Does this mean that the code searches through the locations array looking for an annotation as! Location element and every time it finds it increases index by 1?

The index(of:) method returns Int?, which means that if the object is not found in the array, the return value is nil, otherwise it is an Int.

Arrays work like this:

      index 1
        |
        V
[locA, locB, locC]
  ^            ^
  |            |    
index 0      index 2

If you call locations.index(of:locB) the return value will be Optiona(1).

and every time it finds it increases index by 1?

Absolutely not! When the index(of:) method finds the object it stops and returns the index:

...
let loc1 = Location(1,2)
let loc2 = Location(3,4)
let loc3 = Location(5,6)
let loc4 = Location(7,8)

let locations = [loc3, loc2, loc3, loc3]  //Note how many times loc3 is present in the array

if let index = locations.index(of: loc3) {
    print(index)
}
else {
    print("not found")
}

--output:--
0

From the docs:

func index(of: Element)
Returns the first index where the specified value appears in the collection.

looking for an annotation as! Location element

The annotation variable, which is of type MKAnnotation, was assigned a Location object by iOS. But in order to compare the annotation variable to variables of type Location, like the ones in the locations array, you have to cast the annotation variable to the type Location. Here is an example:

...
let loc1 = Location(1,2)
let loc2 = Location(3,4)
let loc3 = Location(5,6)
let loc4 = Location(7,8)

let locations = [loc3, loc2, loc3, loc3]
let annotation: MyAnnotation = loc3

if let index = locations.index(of: annotation) {  //=> error: ... expected argument type Location
    print(index)
}
else {
    print("not found")
}

You cannot look for an MKAnnotation object in an array populated with Location objects. If the annotation variable was actually assigned a Location object (other classes could have implemented the MKAnnotation protocol and therefore could have been assigned to the annotation variable as well), then you can cast the annotation variable to type Location, then search for the object in the array.

1 Like

Ah!

the mists are clearing, sorry for my ignorance, I’ve been going round in circles

so, let me try to clarify this into one sentence

the code searches the locations array for an annotation as! Location element which refers to the annotation that was tapped, when it finds it it returns the index value

the annotation as! Location element looks like this

<Location: 0x604000484d80> (entity: Location; id: 0xd000000000080000 <x-coredata://73C1E754-08A9-45DB-94E6-567E8D3B51BF/Location/p2> ; data: {
    category = "No Category";
    date = "2018-01-07 00:41:53 +0000";
    latitude = 38;
    locationDescription = "";
    longitude = "-122.408227";
    photoID = nil;
    placemark = "San Pablo Bay, San Pablo Bay, Richmond, United States @ <+38.00000000,-122.40822700> +/- 100.00m, region CLCircularRegion (identifier:'<+37.99999999,-122.40822700> radius 141.72', center:<+37.99999999,-122.40822700>, radius:141.72m)";
})

so when it finds this same information in the array it returns its index position int

thank you for your patience and help

That’s the idea, :sunny:, but that is not how it is implemented. If Swift had to compare all that information for an array of 100,000 Location objects, it would take too long. Instead, all objects have a unique integer id, say 1003421. So all swift does is look at the integer id of the object you are searching for and the integer id of each object in the array. Because comparing integers is extremely fast, Swift can search large arrays very quickly.

beautiful

that’s exactly what I was hoping you would say

That is actually the integer id (in hex format).

1 Like

I’m afraid that that’s slightly incorrect :slight_smile: What you see after the variable is the memory location of the variable - not its ID.

And you are also somewhat incorrect about how arrays are searched as well since Swift has no way to know which property in an object is the unique ID when you search the array for a specific item. There are methods which do allow you to do the checking yourself and in those cases, you can do the check via a value you know to be unique. But otherwise, I believe Swift does not do ID checking for array items except perhaps in specific cases where there might be exceptions.

I was just reading your earlier posts again:

annotation as! Location returns a Location object (or your app will crash). That is what the ! means–you are requesting that if the annotation variable, which is of type MKAnnotation, was not assigned a Location object (the Location class implements the MKAnnotation protocol so it can be assigned to a variable of type MKAnnotation), then please crash my app. Presumably because of how things are arranged in the code, iOS will always assign a Location object to the annotation variable, so you can safely use the ! without fear that your app will crash.

I should have used the word element instead of object

and is Fahim saying that all the attributes in the data model are compared?

This too is incorrect I’m afraid - at least the part about “then please crash my app”. That is not the intent here at all. A force unwrap does not mean “please crash my app if this is not the case” - a crash should not be the expected result if something goes wrong. You should always try to avoid crashing your app.

The reason you force unwrap annotation objects as Location objects is because Location objects are the only type of annotation that the app supports. So you know you can safely force unwrap the annotation without fear of a crash. If you do have multiple types of annotation objects, then you would write the same code to safely unwrap the annotation object, something like this:

if let loc = annotation as? Location {
    // Do something with the unwrapped loc variable
}

When I said ‘integer id’, I was trying to simplify the concept of a pointer.

And you are also somewhat incorrect about how arrays are searched

I assumed Swift compared pointers.

A force unwrap does not mean “please crash my app if this is not the case”

I don’t see how that is possible–otherwise wouldn’t you use as?. As far as I can tell, whenever you use a !, you are saying that the app cannot continue onward if the cast or unwrapping cannot be performed. And in my limited experience, my app always crashes if the cast or unwrapping cannot be performed.