class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var objArr : [jsonObject] =
internal func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
internal func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: "Cell")
cell.textLabel?.text = "Row Number: \(indexPath.row + 1)"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "toSecondFromFirst", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var tempArr : [jsonObject] = []
var exampleObject = downloadJson(min: 2140, max: 2145) { (jsonDataObject, error) -> [jsonObject] in
tempArr.append(jsonDataObject!)
print(tempArr)
return tempArr
}
print(exampleObject)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func downloadJson(min: Int, max: Int,completionHandler completion : @escaping (jsonObject?, Error?) -> [jsonObject]){
var tempObject = jsonObject()
var achievID : Int = min
for _ in min...max {
achievID += 1
let url = URL(string: "https://us.api.battle.net/wow/achievement/\(achievID)? locale=en_US&apikey=wh7sqpen5a5gps5sp9mjvp7s9tnrtbsf")
let urlRequest = URLRequest(url: url!)
URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if error != nil {
print("error")
} else {
guard let httpResponse = response as? HTTPURLResponse else {
print("Error converting response as http url response or response is nil")
return
}
if httpResponse.statusCode == 200 {
guard let dataUnwrapped = data else {
print("data is nil ")
return
}
do {
tempObject = try JSONDecoder().decode(jsonObject.self, from: dataUnwrapped)
completion(tempObject,nil)
} catch {
print("Error decoding json")
}
}
}
}.resume()
}
}
}
I can't seem to return the array of objects into the variable and don't understand why. Looking for some pointers and help here. What I want to do is return an array of the jsonObjects so I can determine the amount of rows to draw etc. the whole basis of the listview application kind of relies on getting an array back of usable objects so I populate the list cells. Thanks in advance! Also, jsonObject is defined outside of the code that I linked.
The HTTP requests you are doing run asynchronously, so directly after calling downloadJson, there is no result that could be returned. Your completion handler is also running multiple times, once for each successful request you send, so there wouldn't even be one single value to return.
From what I see in your code sample, you probably should change your completion handler to take a [jsonObject] as parameter (or [JsonObject], please don't start type names with a lowercase letter ) and return Void. In your downloadJson implementation, you can then collect the responses into an array and call the completion handler once, when all the requests are done.
How do I run multiple HTTP requests inside of the downloadJson function so I can append it to an array and then return it so I can use that array outside of the scope of the closure?
Then move the whole tempArr stuff into downloadJson. You are already running multiple requests in there. You'll have to do a little book-keeping to keep track of the requests to know when all of them have finished, a Set<Int> of ids you're still waiting for an answer to should do fine.
So the high-level summary of downloadJson would be:
Create a temporary array to hold the results and a Set to keep track of which ids are still waiting for the result
For each id (you can use for achievID in min...max btw):
Add the id to the set
Start the request
In the request completion handler: add decoded result to temp array, remove id from Set, if Set is now empty (=> all requests have finished), call your completion handler with the temp array as parameter
Oh and at some point you'll have to think about how you want to handle errors (does one error mean the result is failed, or do you ignore objects with errors?) and adapt your code accordingly.
is this how you're suggesting I edit my function? I don't really understand how I would implement the set ID thing you're talking about I've never really used Sets. Also, how am I going to return an array from the downloadJson function if it returns void?
func downloadJson(min: Int, max: Int, completionHandler completion: @escaping ([JsonObject]?, Error?) -> Void){
var achievID : Int = min
var tempArray : [JsonObject] = []
for achievID in min...max {
let url = URL(string: "https://us.api.battle.net/wow/achievement/\(achievID)?locale=en_US&apikey=wh7sqpen5a5gps5sp9mjvp7s9tnrtbsf")
let urlRequest = URLRequest(url: url!)
URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if error != nil {
print("error")
} else {
guard let httpResponse = response as? HTTPURLResponse else {
print("Error converting response as http url response or response is nil")
return
}
if httpResponse.statusCode == 200 {
guard let dataUnwrapped = data else {
print("data is nil ")
return
}
do {
let tempObject = try JSONDecoder().decode(JsonObject.self, from: dataUnwrapped)
tempArray.append(tempObject)
completion(tempArray, nil)
} catch {
print("Error decoding json")
}
}
}
}.resume()
}
}
Except for the Set, yes. Right now, the completion handler (the one that is passed to downloadJson that is, not the one passed to dataTask(...)) would still be called once for each request where you only want it called for the last one that finishes. Look at the docs for Set, it's pretty easy.
You're not. Remember that asynchronous completion handlers are not called before the method you pass the completion handler to returns, but at some later point (when the HTTP request finishes in this case). Because of that, you can not return the resulting value directly. Instead, you pass the resulting value into the completion handler which can then use the value to do whatever.
So is there a way I can update the amount of rows in the tableview and the information inside of it after the the asynchronous completion handler finishes downloading the data and puts it into an array? I want to be able to download the data into an array and then use the array with all the json objects in other parts of my code. What other instruments are available for me to be able to do that?
The simplest way would be something like this (assuming this is called somewhere from the table view controller, viewDidLoad, viewDidAppear, something like that):
Use objArray as basis for all your table view methods, so when the view controller comes on screen, it will first display no cells, since nothing is loaded yet and self.objArray is empty. Then the completion handler gets called, updates self.objArray with the received objects, and reloads the table view to display the new data.
Oh, right, wrap the code inside the completion handler in DispatchQueue.main.async { ... } (import Dispatch if needed), to get back from the url session delegate queue to the main thread.
Almost. You only want the download to happen once, when the view controller appears, not every time the table view queries the count, which can happen multiple times.
So the code is somewhat functioning at this point but with some weird and definitely unusable functionality. the table is constantly changing title values and is spamming itself. It's seems like there is an endless update loop
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
downloadJson(min: 2140, max: 2145) { (jsonObjects, error) in
DispatchQueue.main.async {
self.objArray = jsonObjects ?? []
self.tableView.reloadData()
}
}
}
This code throws an error because of an "Ambiguous reference to member 'tableView(_:numberOfRowsInSection:)'"
I think I figured it out.... I didnt' have an IBOutlet for my tableView, which is why I couldn't reference the reloadData() and I didn't realize that is what you were talking about. This is what the final code looks like... Thanks a lot for the help and let me kn ow if there is anything else that you would add. I appreciate your patience on getting me through all of this...
//
// ViewController.swift
// Tableviews
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var objArray : [JsonObject] = []
@IBOutlet weak var tableViewDataOutlet: UITableView!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: "Cell")
cell.textLabel?.text = "\(objArray[indexPath.row].title)"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "toSecondFromFirst", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
downloadJson(min: 2140, max: 2145) { (jsonObjects, error) in
DispatchQueue.main.async {
self.objArray = jsonObjects ?? []
self.tableViewDataOutlet.reloadData()
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func downloadJson(min: Int, max: Int, completionHandler completion: @escaping ([JsonObject]?, Error?) -> Void){
var achievID : Int = min
var tempArray : [JsonObject] = []
for achievID in min...max {
let url = URL(string: "https://us.api.battle.net/wow/achievement/\(achievID)?locale=en_US&apikey=wh7sqpen5a5gps5sp9mjvp7s9tnrtbsf")
let urlRequest = URLRequest(url: url!)
URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if error != nil {
print("error")
} else {
guard let httpResponse = response as? HTTPURLResponse else {
print("Error converting response as http url response or response is nil")
return
}
if httpResponse.statusCode == 200 {
guard let dataUnwrapped = data else {
print("data is nil ")
return
}
do {
let tempObject = try JSONDecoder().decode(JsonObject.self, from: dataUnwrapped)
tempArray.append(tempObject)
completion(tempArray,nil)
} catch {
print("Error decoding json")
}
}
}
}.resume()
}
}