Search code examples
iosswiftmkmapviewcloudkitckrecord

What to use instead of indexPath.row? for non-table?


I'm working with a mapview not a tableview, but I don't know what to use to replace indexPath.row.

I have a mapview with annotations, when the info button of an annotation is pressed I then query my CK database and return the record that has a name field matching the name of the annotation pressed. This returns an [CKRecord] with a single record, as there are no matching names.

At this point, with a tableview I would do the following to access the data...

let placeInfo = selectedData[indexPath.row]
let placeName = placeInfo.objectForKey("Name") as! String
let placeCity = placeInfo.objectForKey("City") as! String

However, since I'm not using a tableview, I don't have an indexPath to use. Since my [CKRecord] object only contains a single record, I thought I could replace indexPath.row with the array location of the record...

let placeInfo = selectedPlace[0] //also tried 1

That lines produces an Index out of range error.
I've tried everything that I know, and as you may imagine, I'm not exactly great at swift or programming in general at this point.

Here is the full mapView function I am using...

    func mapView(mapView: MKMapView, annotationView: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {

    let cloudContainer = CKContainer.defaultContainer()
    let publicData = cloudContainer.publicCloudDatabase

    let tappedPlace = annotationView.annotation!.title!! as String

    let predi = NSPredicate(format: "Name = %@", tappedPlace)
    let iquery = CKQuery(recordType: "Locations", predicate: predi)

    publicData.performQuery(iquery, inZoneWithID: nil, completionHandler: {
        (results, error) -> Void in

        if error != nil {
            print(error)
            return
        }

        if let results = results {
            print("Downloaded data for selected location for \(tappedPlace)")

            NSOperationQueue.mainQueue().addOperationWithBlock() {
                self.selectedPlace = results
            }
        }
    })

    let placeInfo = selectedPlace[0]
    let placeName = placeInfo.objectForKey("Name") as! String
    //returns Index out of range error for placeInfo line


    //need data before segue
    //performSegueWithIdentifier("fromMap", sender: self)
}

Solution

  • Your problem is that, you try to access selectedPlace before it is actually signed by your completion handler. Your 'publicData.performQuery' seems to be an asynchronous operation, and this means that, the control will come out from this call even before the completion handler gets executed(this is expected in case of an asynchronous call). And you reach the line immediately-

    let placeInfo = selectedPlace[0]
    

    But the data is not ready yet, and you get the exception. Now to solve this, move place info extraction, and perform segue code inside the completion handler as shown-

    publicData.performQuery(iquery, inZoneWithID: nil, completionHandler: {
        (results, error) -> Void in
    
        if error != nil {
            print(error)
            return
        }
    
        if let results = results {
            print("Downloaded data for selected location for \(tappedPlace)")
    
            NSOperationQueue.mainQueue().addOperationWithBlock() {
                self.selectedPlace = results
                   if(results.count > 0){
    
                   let placeInfo = selectedPlace[0]
                   let placeName = placeInfo.objectForKey("Name") as! String
                   //Do any other computations as needed.
                   performSegueWithIdentifier("fromMap", sender: self)
                }
            }
        }
    })
    

    This should fix your problem.