infernouk
infernouk

Reputation: 1137

How to load JSON into TableView?

I am trying to load an exercises JSON into a table view, i have a service function that gets the data from a source as JSON, and a view controller with a table that I want to load the info into. There are no errors in the code however the table loads blank rows, the debug section shows the JSON data just fine via a print command. Im a beginner so im sure im missing a core element, but cant work it out!

api service

class ApiService {

static var swiftyJsonVar:JSON?

class func getExerciseData() {

    Alamofire.request("https://wger.de/api/v2/exercise/?format=json").responseJSON { (responseData) -> Void in
        if((responseData.result.value) != nil) {
            swiftyJsonVar = JSON(responseData.result.value!)
            print(swiftyJsonVar ?? nil)
        }

    }
}

View Controller

class ExerciseDatabaseController: UIViewController, UITableViewDataSource, UITableViewDelegate {

@IBOutlet weak var ExerciseSearchField: UISearchBar!
@IBOutlet weak var ExercisesTableView: UITableView!

var arrRes = [[String:AnyObject]]() // Array of dictionary

override func viewDidLoad() {
    super.viewDidLoad()

    let arrRes = ApiService.getExerciseData()

    if let resData = ApiService.swiftyJsonVar?["exercise"].arrayObject {
        self.arrRes = resData as! [[String:AnyObject]]
    }
    if self.arrRes.count > 0 {
        self.ExercisesTableView.reloadData()
    }

    print(arrRes)
    // Do any additional setup after loading the view.
}

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

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
    var dict = arrRes[indexPath.row]
    cell.textLabel?.text = dict["name"] as? String
    cell.detailTextLabel?.text = dict["description"] as? String
    return cell
}

Upvotes: 0

Views: 168

Answers (2)

Kayla Galway
Kayla Galway

Reputation: 672

You should be loading your JSON asynchronously, which means you should have a closure in the method that makes your alamofire call.

class ApiService {

class func getExerciseData(completion: @escaping ([[String: AnyObject]]) -> ()) {

  Alamofire.request("https://wger.de/api/v2/exercise/?format=json").responseJSON { (responseData) -> Void in
      guard let jsonResponse = responseData.result.value else {
        //possibly put some sort of protection, or what you want to do if there is not a response here
        return
      }
      //instead of creating a variable for swiftyJsonVar in this class, 
      //you want to use a completion to send the array of dictionaries to the tableview asynchronously, 
      //that way it doesn't load blank
      //I'm not super familiar with swifty json(sorry). What I normally do is below.
      let swiftyJsonVar = JSON(jsonResponse)
      guard let dictArray = swiftyJsonVar["exercise"].arrayObject as? [[String: AnyObject]] else {
        //some sort of protection here if this fails
        return
      }
      completion(dictArray)
  }
}

So now we have made our asynchronous call(generally you want to do this whenever you are displaying information visually from an internet call that was not already preloaded/saved somewhere in app).

Next, we want to display this information in our tableview upon tableview load.

    class ExerciseDatabaseController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    //these should start with lower cases(exerciseSearchField), never uppercased
    @IBOutlet weak var ExerciseSearchField: UISearchBar!
    @IBOutlet weak var ExercisesTableView: UITableView!

    var arrRes = [[String:AnyObject]]() // Array of dictionary

    override func viewDidLoad() {
      super.viewDidLoad()
      //you said you would use these delegates up top when you created the class, so you have to set them
      ExercisesTableView.delegate = self
      ExercisesTableView.dataSource = self
      fetchData()
      // Do any additional setup after loading the view.
    }

    //this method will make the api call
    //you'll notice that if you set breakpoints, it will reach the end of the method before hitting self?.arrRes = dictArray
    //this is normal and how asynchronous calls work, look into tableview threading for a deeper explanation of why that is. It is super important to understand threading in iOS
    //once it gets data back from the API call, it will go to the main thread and tell the tableview to reload with that data

    func fetchData() {
      ApiService.getExerciseData { [weak self] (dictArray) in
        self?.arrRes = dictArray
        print(self?.arrRes)
        if self?.arrRes.count > 0 {
          DispatchQueue.main.async {
            self?.ExercisesTableView.reloadData()
          }
        }
      }
    }

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
      let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
      var dict = arrRes[indexPath.row]
      cell.textLabel?.text = dict["name"] as? String
      cell.detailTextLabel?.text = dict["description"] as? String
      return cell
    }

You'll see I used [weak self] above. For more of an explanation of why that is necessary with asynchronous internet calls/whenever using closures, you can read here: http://krakendev.io/blog/weak-and-unowned-references-in-swift

There are a lot of other resources for reading about weak and strong references/parent child stuff in iOS with a quick google search. Also, pursue researching asynchronous/synchronous in iOS. Both of these topics are incredibly important to learn when beginning.

Upvotes: 1

SrB
SrB

Reputation: 363

Reload your tableView once the JSON data from your asynchronous request is received. So your

self.ExercisesTableView.reloadData()

will go inside

Alamofire.request("https://wger.de/api/v2/exercise/?format=json").responseJSON { (responseData) -> Void in
    if((responseData.result.value) != nil) {
        swiftyJsonVar = JSON(responseData.result.value!)
        print(swiftyJsonVar ?? nil)
    }
}

Upvotes: 0

Related Questions