0

In a nutshell, I want to determine a user's location, get some JSON data from source (that requires the location to be known first), and then create a table based on the returned data.

I have a custom UITableView with one prototype cell in Xcode 7 using Swift. When this table view was the initial view, it loaded fine.

Now, I'm trying to 1) embed it in a container in another UIView and 2) have it update whenever its data changes. Neither works... the table in the container never appears. I've spent nearly a week on this and am going a bit nutty (it's my first iOS app).

Something things I've noticed:

  1. reloadData, setNeedsDisplay, and setNeedsLayout don't appear to do anything.
  2. cellForRowAtIndexPath isn't being called, but numberOfRowsInSection is.
  3. I've tried calling all of the functions listed in bullet 1 from both the parent UIView as well as from the TableViewController.

Here is what the code currently looks like: ViewController:

import UIKit
import MapKit
import CoreLocation
var currentLatLong: String = ""



class ViewController: UIViewController, CLLocationManagerDelegate  {
    // global var for location
    let locationManager = CLLocationManager()
    var currentLocation = CLLocation()
    var lastLatLong: String = ""

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        // get permission and user's location
        locationManager.delegate = self
        locationManager.requestWhenInUseAuthorization()
        if CLLocationManager.locationServicesEnabled() {
            locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
            locationManager.startUpdatingLocation() 
        } else {
            print("Location services are not enabled")
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let location = locations.last {
            currentLocation = locations.last!
            currentLatLong = "\(currentLocation.coordinate.latitude)%2C\(currentLocation.coordinate.longitude)"
            if lastLatLong != currentLatLong {
                lastLatLong = currentLatLong
                NSNotificationCenter.defaultCenter().postNotificationName("updateOverview", object: nil)
                self.view.setNeedsDisplay()
                self.view.setNeedsLayout()
                print("Found user's location: \(location)")

            }
            //print("Found user's location: \(location)")
        }
    }

    func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
             print("Failed to find user's location: \(error.localizedDescription)")
        }
    }

UITableViewController:

import UIKit
import MapKit
import CoreLocation

class OverviewTableViewController: UITableViewController {

    var LocRepositories = [LocationJSON]()
    var TimesRepositories = [TimesJSON]()
    var retrieved = [dataModelDefn]()

    override func viewDidLoad() {
        super.viewDidLoad()


        NSNotificationCenter.defaultCenter().removeObserver(self, name: "updateOverview", object: nil)
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "checkRes", name: "updateOverview", object: nil)


        // fetch location data

        let backgroundQueue:dispatch_queue_t = dispatch_queue_create("com.example.workQueue", DISPATCH_QUEUE_SERIAL);
        dispatch_async(backgroundQueue, {
            self.getLocations()
            dispatch_async(dispatch_get_main_queue(), {self.tableView.reloadData()})
        })
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // MARK: - Table view data source

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
            return 1
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return retrieved.count
        }



    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        // initialize data & cell
        var cell = tableView.dequeueReusableCellWithIdentifier("EDOverviewCell", forIndexPath: indexPath) as! OverviewTableCell
        var rowData = retrieved[indexPath.row]


        /* update cell */

        // border for cell
        self.tableView.separatorStyle = UITableViewCellSeparatorStyle.SingleLine
        self.tableView.separatorColor = UIColor(red:(0/255.0), green:(128/255.0), blue:(128/255.0), alpha:1)


        // alternating row colors
        if indexPath.row % 2 == 0 {
            cell.backgroundColor = UIColor(red:(249/255.0), green:(238/255.0), blue:(188/255.0), alpha:0.5)
        }
        else{
            cell.backgroundColor = UIColor(red:(252/255.0), green:(244/255.0), blue:(220/255.0), alpha:0.5)
        }

        return cell
    }

    func checkRes()
    {
        // fetch new data because location available
        getLocations()
        //self.tableView.reloadData()
        //dispatch_async(dispatch_get_main_queue(),{ self.tableView.reloadData() })
    }

    func getLocations(){
         // fetch demographic/location info

        // abort call if no location
        if currentLatLong == "" {
            return
        }

        /* get JSON data */
    }
}

Here are just some of the related questions I've attempted to mimic the solution to: Loading data after one second in uitableview. Any alternate way to eliminate this delay

How to stop UITableView from loading until data has been fetched?

UITableView doesn't populate when waiting for CLLocationManagerDelegate 's didUpdateToLocation method on iPhone SDK

It's a lengthy post, but if I've missed any necessary details please let me know. I appreciate any help.

Community
  • 1
  • 1
ilija
  • 3
  • 1
  • is currentLatLong ever a non empty string in getLocations()? When you embed a uitableview inside a new view, you usually don't keep the entire uitableviewcontroller. You simply make the tableview a subview of the first view controller, and implement the UITableViewDataSource and UITableViewDelegate as necessary. – Msencenb Feb 23 '16 at 23:20
  • Try the [Xcode View Debugger](https://developer.apple.com/library/ios/documentation/DeveloperTools/Conceptual/debugging_with_xcode/chapters/special_debugging_workflows.html#//apple_ref/doc/uid/TP40015022-CH9-SW2) to see where the tableview is. Maybe theres a constraint forcing it offscreen. Also, is the value returned from numberOfRowsInSection ever not 0? – Craig Siemens Feb 23 '16 at 23:23
  • @Msencenb - Something good to know and to keep in mind for the rest of the app's development. – ilija Feb 26 '16 at 03:32
  • @Clever - I wasn't even aware that existed. It did help clear up an issue once I got the data in the table to appear but where it was smaller and completely off target. Tis a super useful utility. – ilija Feb 26 '16 at 03:36

1 Answers1

0

You currently have the following code:

    let backgroundQueue:dispatch_queue_t = dispatch_queue_create("com.example.workQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(backgroundQueue, {
        self.getLocations()
        dispatch_async(dispatch_get_main_queue(), {self.tableView.reloadData()})
    })

This will run getLocations in a background thread and then refresh table tableView on the main thread. All good. However, getLocations itself does a JSON call (currently just a comment), and this is undoubtedly also run on another background thread. Therefore, getLocations will return almost immediately, well before the JSON has finished.

You should instead be passing a completion handler to getLocations, and when the JSON finishes (in its completion handler), call your own completion handler. Along the lines of:

    let backgroundQueue:dispatch_queue_t = dispatch_queue_create("com.example.workQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(backgroundQueue, {
        self.getLocations(completion: {
            dispatch_async(dispatch_get_main_queue(), {
                self.tableView.reloadData()
            })
        })
    })

(I may have the braces mismatched here). Then getLocations is defined as:

func getLocations(completion completion: (() -> Void)){
     // fetch demographic/location info

    // abort call if no location
    if currentLatLong == "" {
        return
    }

    getTheJsonAndInItsCompletion(... success: {
        completion()
    })
}

The actual JSON implementation will depend on the library you are using.

Michael
  • 8,891
  • 3
  • 29
  • 42
  • Thanks for the solution, @Michael, and the brief intro to callback hell. I've been reading about it since you posted your answer. I'm not using a 3rd-party library for the JSON, just Swift. But that JSON call has another JSON call in it, as well as an ETA (MapKit framework) call. Not sure how normally handled, but a nightmare. – ilija Feb 26 '16 at 03:48