I've been attempting to populate a grouped table with data from an Alamofire request. I've so far managed to populate a table with static data from an array (as shown in the picture), but after hours of trying, looking up and experimenting, have still gotten no-where working out how to use the JSON data. It shouldn't make too much of a difference, but for the record this is in Swift 3.
Any help would be appreciated. Thanks.
Here is my static code, which is working great.
import UIKit
import Alamofire
import SwiftyJSON
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
//static Data Here:
var array = [ ["Clients", "John Doe", "Joe Bloggs"],["Departments", "HR", "Admin", "Finance"]]
let cellReuseIdentifier = "cell"
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
tableView.delegate = self
tableView.dataSource = self
super.viewDidLoad()
}
func numberOfSections(in tableView: UITableView) -> Int {
return array.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array[section].count - 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:AreasCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! AreasCustomCell
cell.areasPreview.contentMode = .scaleAspectFit
request(.GET, "https://url.here.com", parameters: ["file": "default.png"]).response { (request, response, data, error) in
cell.areasPreview.image = UIImage(data: data!, scale:0.5)
}
cell.areasCellLabel.text = array[indexPath.section][indexPath.row + 1]
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return array[section][0]
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You tapped cell number \(indexPath.row).")
//More things planned here later!
}
}
Here is also the format of the JSON I'm working with.
content = {
clients = (
{
desc = "Description here";
group = client;
id = "group_7jsPXXAcoK";
name = "John Doe";
},
{
desc = "Description here";
group = client;
id = "group_19MrV7OLuu";
name = "Joe Bloggs";
}
);
departments = (
{
desc = "Description here";
group = department;
id = "group_PhAeQZGyhx";
name = "HR";
},
{
desc = "Description here";
group = department;
id = "group_RMtUqvYxLy";
name = "Admin";
},
{
desc = "Description here";
group = department;
id = "group_T50mxN6fnP";
name = "Finance";
}
);
};
state = success;
I've so far added a new class to hold the JSON data which I believe is stepping in the right direction.
class Group {
let id : String
let name : String
let desc : String
let group : String
init(dictionary : [String : AnyObject]) {
id = dictionary["id"] as? String ?? ""
desc = dictionary["desc"] as? String ?? ""
name = dictionary["name"] as? String ?? ""
group = dictionary["group"] as? String ?? ""
}
}
And finally here is my function to get the JSON data in the first place which should be called from viewDidLoad.
func getData()
{
let defaults = UserDefaults.standard()
let token = defaults.string(forKey: defaultsKeys.userToken)
let email = defaults.string(forKey: defaultsKeys.userEmail)
request(.POST, "https://url.here.com/api/v2.php", parameters: ["type": "areas", "uEmail": email!, "token": token!])
.responseJSON { response in
var json = JSON(response.result.value!)
let state = json["state"].stringValue
if(state == "error"){
print(json["message"].stringValue)
} else {
print(response.result.value)
//Send JSON data here to Table!
}
}
}
typealias APIResultHandler = ((response: Int, json: JSON) -> Void)
func performAPICall(url: NSURL, method: String, body: String?, resultHandler: APIResultHandler) {
let session = NSURLSession(configuration: .defaultSessionConfiguration())
let tokenRequest = NSMutableURLRequest(URL: url)
tokenRequest.HTTPMethod = method
if body != nil && method == Constant.POST {
tokenRequest.HTTPBody = body!.dataUsingEncoding(NSUTF8StringEncoding)
}
let dataTask = session.dataTaskWithRequest(tokenRequest) {
(let data, let response, let error) in
if let httpResponse = response as? NSHTTPURLResponse {
if error == nil {
let json = JSON(data: data!)
resultHandler(response: httpResponse.statusCode, json: json)
} else {
print("Error during \(method) Request to the endpoint \(url).\nError: \(error)")
}
}
}
dataTask.resume()
}
struct Client {
var id: String
var desc: String
var name: String
init() {
id = ""
desc = ""
name = ""
}
}
var clientArray: [Client] = []
let body = "key1=(value1)&key2=(value2)&key3=(value3)&key4=(value4)&key5=(value5)" etc...
override func viewDidLoad() {
super.viewDidLoad()
performAPICall(url, method: "POST", body: body) {
json in
//this will have your full response
print(json)
//put this as a class variable instead in the call
var clientArray: [Client] = []
let clients = json["clients"]
for client in clients {
var thisClient = Client()
thisClient.id = json["id"].string
thisClient.desc = json["desc"].string
thisClient.name = json["name"].string
clientArray.append(thisClient)
}
tableview.reloadData()
}
}
func tableView(tableView: UITableView, cellForRowAtindexPath: IndexPath) -> UITableViewCell {
if section == 0 {
let cell:AreasCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! AreasCustomCell
cell.areasPreview.contentMode = .scaleAspectFit
cell.areasCellLabel.text = clientArray[indexPath.row]
}
if section == 1 {
//do stuff for the other cell
}
}
Alright, when you receive a response from a request, the closure gives you back 3 values.
example:
request(gibberish: DoesntMatter) {
data, response, error in
}
you generally want to check the response for a 200 result like so
if let httpResponse = response as? NSHTTPURLResponse {
if httpResponse.statusCode == 200 {
//do something with data
}
}
At this point, when working with swiftyJSON, you can get the data like so:
if let httpResponse = response as? NSHTTPURLResponse {
if httpResponse.statusCode == 200 {
let json = JSON(data: data)
}
}
Now the best way to retrieve the json at this point is with a closure since API calls are done asynchronously and we need to know when the response is finished.
func performAPICall(url: NSURL, resultHandler: ((json: JSON) -> Void)) {
let session = NSURLSession(configuration: .defaultSessionConfiguration())
let tokenRequest = NSMutableURLRequest(URL: url)
tokenRequest.HTTPMethod = "GET"
let dataTask = session.dataTaskWithRequest(tokenRequest) {
(let data, let response, let error) in
if let httpResponse = response as? NSHTTPURLResponse {
if error == nil {
if httpResponse.statusCode == 200 {
let json = JSON(data: data!)
resultHandler(json)
} else {
print("Failed request with response: \(httpResponse.statusCode)")
}
} else {
print("Error during GET Request to the endpoint \(url).\nError: \(error)")
}
}
}
dataTask.resume()
}
Then you call the function and do what you like to with the data like so:
performAPICall(url) {
json in
//this will have your full response
print(json)
//parse the json easily by doing something like
var clientArray: [Group] = []
let clients = json["clients"]
for client in clients {
var thisClient = Group()
thisClient.id = json["id"].string
thisClient.desc = json["desc"].string
thisClient.name = json["name"].string
//not quite sure how to store this one
thisClient.group = json["group"].anyObject
clientArray.setByAddingObject(thisClient)
}
//make sure to call tableView.reloadData() when you give the tableViews //it's value.
}
You can do this through the init as well, but make sure you setup that function appropriately. Otherwise I would just initialize the values of your object to nil or empty. Also your JSON response is returning a value that is not a string. Make sure you mess with it to find a proper way of storing it. Hope this helps!
I use this a lot. Just pass in "POST" or "GET" into the method parameter and type the body into the body parameter or nil if it is a GET request. You can remove the Reachability part, but I generally like to use some form of checking for network connection with an API call so if I am not connected I immediately can diagnose the error. There are a several different git project's that you can use for that. Present alert controller is just an extension that I place on UIViewController for easier alert messages.
The only tricky part here is the body. If if follows RESTful design than just pass the body as a String that follows this design
"key1=(value1)&key2=(value2)&key3=(value3)&key4=(value4)&key5=(value5)" etc...
You can also do this through json serialization I believe, which is cleaner, but I haven't found it necessary in any of my projects yet.
typealias APIResultHandler = ((response: Int, json: JSON) -> Void)
func performAPICall(url: NSURL, method: String, body: String?, resultHandler: APIResultHandler) {
if Reachability.isConnectedToNetwork() == true {
print("Internet connection OK")
let session = NSURLSession(configuration: .defaultSessionConfiguration())
let tokenRequest = NSMutableURLRequest(URL: url)
tokenRequest.HTTPMethod = method
if body != nil && method == Constant.POST {
tokenRequest.HTTPBody = body!.dataUsingEncoding(NSUTF8StringEncoding)
}
let dataTask = session.dataTaskWithRequest(tokenRequest) {
(let data, let response, let error) in
if let httpResponse = response as? NSHTTPURLResponse {
if error == nil {
let json = JSON(data: data!)
resultHandler(response: httpResponse.statusCode, json: json)
} else {
print("Error during \(method) Request to the endpoint \(url).\nError: \(error)")
}
}
}
dataTask.resume()
} else {
print("Internet connection FAILED")
presentAlertController("No Internet Connection", message: "Make sure your device is connected to the internet.")
}
}
It doesn't take me long, but I don't have time right now. Monday after work will be the next time I even have a few minutes to look at code. My email is sethmr21#gmail.com. Send me an email, and I will help you then if you still can't figure it out. Messing with closures, asynchronous things, API calls, and whatnot can be confusing when you first start. There is a lot of good articles on them though. I would advise getting a nice size book like SWIFT I'm sure you can find free pdf's online of some of the books. Reading them from back to front will give you a foundation its hard to get by skimming stack overflow.
Related
I am trying to save "author" data to global variable named "authors" from json(Link:"https://learnappmaking.com/ex/books.json") with these two libraries. But it only works at the trailing closure of func Alamofire.request(url).responseJSON. When I access the global variable named "authors" from somewhere except the trailing closure, what I get is an empty array of string.
Can someone explain the reason behind this werid situation?
Thanks a lot.
class ViewController: UIViewController {
var authors = [String]()
let url = "https://learnappmaking.com/ex/books.json"
func getAuthorsCount() {
print("the number of authors : \(authors.count)") // I hope that here, the number of authors should be 3 too! actually, it is 0. Why?
// this for loop doesn't get excuted
for author in authors {
print(author)
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
Alamofire.request(url).responseJSON { response in
if let data = response.data {
if let json = try? JSON(data: data) {
for item in json["books"].arrayValue {
var outputString: String
print(item["author"])
outputString = item["author"].stringValue
//urlOfProjectAsset.append(outputString)
self.authors.append(outputString)
print("authors.count: \(self.authors.count)")
}
}
}
}
getAuthorsCount()
print("-------------")
}
}
the actual output is:
Update:
I adjusted my code:
class ViewController: UIViewController {
var authors = [String]()
let url = "https://learnappmaking.com/ex/books.json"
func getAuthorsCount() {
print("the number of authors : \(authors.count)")
// this for loop doesn't get excuted
for author in authors {
print(author)
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
Alamofire.request(url).responseJSON { response in
if let data = response.data {
if let json = try? JSON(data: data) {
for item in json["books"].arrayValue {
var outputString: String
//print(item["author"])
outputString = item["author"].stringValue
//urlOfProjectAsset.append(outputString)
self.authors.append(outputString)
//print("authors.count: \(self.authors.count)")
}
self.getAuthorsCount() // I added this line of code.
}
}
}
getAuthorsCount()
print("-------------")
}
}
But why does the func getAuthorsCount() (not self. version) still print an empty array of strings ? I think the result should be the same as the result which
func self.getAuthorsCount() printed.
I am so confused now...
Again, I want to use the data kept in the variable named "authors", but what I only got is an empty array of strings.
I'll try to answer all your questions :
The data is persistant
You are doing the following : Alamo.request (Network call) -> getAuthors(print result - empty) ->
response (receive response) -> self.authors.append(save response) -> self.authors (print result)
You need to do : Alamo.request (Network call) -> response (receive response) -> self.authors.append(save response) -> self.getAuthors or getAuthors(same) (inside the response {})
You need to call getAuthors once you have your result, inside the response callback :
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
Alamofire.request(url).responseJSON { response in
if let data = response.data {
if let json = try? JSON(data: data) {
for item in json["books"].arrayValue {
var outputString: String
print(item["author"])
outputString = item["author"].stringValue
//urlOfProjectAsset.append(outputString)
self.authors.append(outputString)
print("authors.count: \(self.authors.count)")
}
self.getAuthorsCount()
print("-------------")
//Do whatever you want from here : present/push
}
}
}
Then you can use the saved data :
To send the data to another ViewController you can use various methods (present/push, closure/callback, ...)
Usually you will have a loading spinner to wait for the network to
answer then you will show your next controller
As requested via direct message: a Swift-only approach. Just paste this in a blank Playground:
import Foundation
final class NetworkService {
enum ServiceError: LocalizedError {
case invalidUrl
case networkingError(error: Error)
case parsingError
var localizedDescription: String? { return String(describing: self) }
}
func request(completion: #escaping (Result<[UserObject], Error>) -> Void ) {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/users") else {
completion(.failure(ServiceError.invalidUrl))
return
}
let dataTask = URLSession.shared.dataTask(with: url) { (jsonData, response, error) in
if let jsonData = jsonData {
let jsonDecoder = JSONDecoder()
do {
let users = try jsonDecoder.decode([UserObject].self, from: jsonData)
completion(.success(users))
} catch {
completion(.failure(ServiceError.parsingError))
}
} else if let error = error {
completion(.failure(ServiceError.networkingError(error: error)))
}
}
dataTask.resume()
}
}
struct UserObject: Codable {
let id: Int
let name: String
let username: String
let email: String?
let website: String?
}
let networkService = NetworkService()
networkService.request { result in
switch result {
case .success(let users):
debugPrint("Received \(users.count) users from REST API")
debugPrint(users)
case .failure(let error):
debugPrint(error.localizedDescription)
}
}
I have a table that is populated by a search function. There are two buttons within the cell, a checkmark to say yes to a user and an X to say no. There is an insert function that inserts the selection into the database. Unfortunately the value from the table is not being passed to the insert function. Within the insert function, I'm using guestusername.text which is the name of the label in my cell. I'm getting the error 'Use of unresolved identifier guestusername'. I've tried everything I can think of, code below.
class MyShotsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var guest = [AnyObject]()
var avas = [UIImage]()
var valueToPass:String!
var revieweduser:String!
var age = [AnyObject]()
var city = [AnyObject]()
var state = [AnyObject]()
#IBOutlet var tableView: UITableView!
var cell: MyShotsCell?
var index = 0
override func viewDidLoad() {
super.viewDidLoad()
doSearch("")
}
// cell numb
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return guest.count
}
// cell config
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MyShotsCell
// get one by one user related inf from users var
let guest2 = guest[indexPath.row]
let ava = avas[indexPath.row]
// shortcuts
let guestname = guest2["username"] as? AnyObject
let age = guest2["age"]
let city = guest2["city"] as? String
let state = guest2["state"] as? String
// refer str to cell obj
cell.guestusername.text = guestname as! String
cell.ageLbl.text = (NSString(format: "%#", age as! CVarArg) as String)
cell.cityLbl.text = city
cell.stateLbl.text = state
cell.avaImg.image = ava as? UIImage
return cell
}
// search / retrieve users
public func doSearch(_ guestusername : String) {
// shortcuts
let username = user?["username"] as! String
let url = URL(string: "http://www.xxxxx.com/xxxxx.php")!
var request = URLRequest(url: url) // create request to work with users.php file
request.httpMethod = "POST" // method of passing inf to users.php
let body = "revieweduser=\(username)" // body that passes inf to users.php
request.httpBody = body.data(using: .utf8) // convert str to utf8 str - supports all languages
// launch session
URLSession.shared.dataTask(with: request) { data, response, error in
// getting main queue of proceeding inf to communicate back, in another way it will do it in background
// and user will no see changes :)
DispatchQueue.main.async(execute: {
if error == nil {
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
// clean up
self.guest.removeAll(keepingCapacity: false)
self.avas.removeAll(keepingCapacity: false)
self.tableView.reloadData()
// delcare new secure var to store json
guard let parseJSON = json else {
print("Error while parsing")
return
}
guard let parseUSERS = parseJSON["users"] else {
print(parseJSON["message"] ?? [NSDictionary]())
return
}
self.guest = parseUSERS as! [AnyObject]
print(self.guest)
// for i=0; i < users.count; i++
for i in 0 ..< self.guest.count {
// getting path to ava file of user
let ava = self.guest[i]["ava"] as? String
let revieweduser = self.guest[i]["username"] as? String
let age = (NSString(format: "%#", self.guest[i]["age"] as! CVarArg) as String)
let city = self.guest[i]["city"] as? String
let state = self.guest[i]["state"] as? String
self.tableView.reloadData()
} catch {
DispatchQueue.main.async(execute: {
let message = "\(error)"
appDelegate.infoView(message: message, color: colorSmoothRed)
})
return
}
} else {
DispatchQueue.main.async(execute: {
let message = error!.localizedDescription
appDelegate.infoView(message: message, color: colorSmoothRed)
})
return
}
})
} .resume()
}
// custom body of HTTP request to upload image file
func createBodyWithParams(_ parameters: [String: String]?, boundary: String) -> Data {
let body = NSMutableData();
if parameters != nil {
for (key, value) in parameters! {
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.appendString("\(value)\r\n")
}
}
return body as Data
}
func insertShot(_ rating : String) {
self.tableView.reloadData()
let reviewer = user?["username"] as! String
// url path to php file
let url = URL(string: "http://www.xxxxxx.com/xxxxxxx.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
// param to be passed to php file
let param = [
"user" : reviewer,
"revieweduser" : cell?.guestusername.text,
"rating" : rating
] as [String : Any]
// body
let boundary = "Boundary-\(UUID().uuidString)"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
// ... body
request.httpBody = createBodyWithParams(param as? [String : String], boundary: boundary)
// launch session
URLSession.shared.dataTask(with: request) { data, response, error in
// get main queu to communicate back to user
DispatchQueue.main.async(execute: {
if error == nil {
do {
// json containes $returnArray from php
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
// declare new var to store json inf
guard let parseJSON = json else {
print("Error while parsing")
return
}
// get message from $returnArray["message"]
let message = parseJSON["message"]
//print(message)
// if there is some message - post is made
if message != nil {
// reset UI
// self.msgTxt.text = ""
// switch to another scene
//self.tabBarController?.selectedIndex = 3
_ = self.navigationController?.popViewController(animated: true)
}
} catch {
// get main queue to communicate back to user
DispatchQueue.main.async(execute: {
let message = "\(error)"
appDelegate.infoView(message: message, color: colorSmoothRed)
})
return
}
} else {
// get main queue to communicate back to user
DispatchQueue.main.async(execute: {
let message = error!.localizedDescription
appDelegate.infoView(message: message, color: colorSmoothRed)
})
return
}
})
}.resume()
return
}
#IBAction func yesBtn_clicked(_ sender: UIButton) {
self.insertShot("Yes")
}
#IBAction func noBtn_clicked(_ sender: UIButton) {
self.insertShot("No")
}
}
I have the following two functions in my first ViewController. They load a UITableView with over 300 rows. I call the loadRemoteData function inside the ViewDidLoad. Everything works fine in the first ViewController.
// MARK: - parseJSON
func parseJSON(data: NSData) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers)
if let rootDictionary = json as? [NSObject: AnyObject], rootResults = rootDictionary["results"] as? [[NSObject: AnyObject]] {
for childResults in rootResults {
if let firstName = childResults["first_name"] as? String,
let lastName = childResults["last_name"] as? String,
let bioguideId = childResults["bioguide_id"] as? String,
let state = childResults["state"] as? String,
let stateName = childResults["state_name"] as? String,
let title = childResults["title"] as? String,
let party = childResults["party"] as? String {
let eachLegislator = Legislator(firstName: firstName, lastName: lastName, bioguideId: bioguideId, state: state, stateName: stateName, title: title, party: party)
legislators.append(eachLegislator)
}
}
}
} catch {
print(error)
}
}
// MARK: - Remote Data configuration
func loadRemoteData() {
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let url = "https://somedomain.com/legislators?order=state_name__asc,last_name__asc&fields=first_name,last_name,bioguide_id"
if let url = NSURL(string: url) {
let task = session.dataTaskWithURL(url, completionHandler: { (data, response, error) -> Void in
if let error = error {
print("Data Task failed with error: \(error)")
return
}
if let http = response as? NSHTTPURLResponse, data = data {
if http.statusCode == 200 {
dispatch_async(dispatch_get_main_queue()) {
self.parseJSON(data)
self.tableView.reloadData()
}
}
}
})
task.resume()
}
}
In the second ViewController, I want to display more information about the individual listed in the cell that is tapped, for that I use a different URL such as https://somedomain.com/legislators?bioguide_id=\"\(bioguideId)\" which provides me with a lot more detail. (The data being requested from the JSON Dictionary is different)
The code I use in the second ViewController is just like shown above with the only difference being the URL. I can print the url coming from the previous ViewController and it is displayed in the console log but no json data is shown.
I would appreciate any help.
Thanks
Below is the code for my second ViewController:
import UIKit
class DetailViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var bioguideId: String?
var currentLegislator: Legislator? = nil
var currentLegislatorUrl: String?
let reuseIdentifier = "Cell"
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var tableView: UITableView!
// MARK: - parseJSON
private func parseJSON(data: NSData) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers)
if let rootDictionary = json as? [NSObject: AnyObject],
rootResults = rootDictionary["results"] as? [[NSObject: AnyObject]] {
for childResults in rootResults {
if let firstName = childResults["first_name"] as? String,
let lastName = childResults["last_name"] as? String,
let bioguideId = childResults["bioguide_id"] as? String,
let state = childResults["state"] as? String,
let stateName = childResults["state_name"] as? String,
let title = childResults["title"] as? String,
let party = childResults["party"] as? String {
currentLegislator = Legislator(firstName: firstName, lastName: lastName, bioguideId: bioguideId, state: state, stateName: stateName, title: title, party: party)
}
}
}
} catch {
print(error)
}
}
// MARK: - Remote Data configuration
func loadRemoteData(url: String) {
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let url = currentLegislatorUrl
if let url = NSURL(string: url!) {
let task = session.dataTaskWithURL(url, completionHandler: { (data, response, error) -> Void in
if let error = error {
print("Data Task failed with error: \(error)")
return
}
print("Success")
if let http = response as? NSHTTPURLResponse, data = data {
if http.statusCode == 200 {
dispatch_async(dispatch_get_main_queue()) {
self.parseJSON(data)
self.tableView.reloadData()
}
}
}
})
task.resume()
}
}
func loadImage(urlString:String) {
let imgURL: NSURL = NSURL(string: urlString)!
let request: NSURLRequest = NSURLRequest(URL: imgURL)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request){
(data, response, error) -> Void in
if (error == nil && data != nil) {
func display_image() {
self.imageView.image = UIImage(data: data!)
}
dispatch_async(dispatch_get_main_queue(), display_image)
}
}
task.resume()
}
override func viewDidLoad() {
super.viewDidLoad()
print(currentLegislatorUrl!)
loadRemoteData(currentLegislatorUrl!)
loadImage("https://theunitedstates.io/images/congress/225x275/\(bioguideId!).jpg")
self.title = bioguideId
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath)
cell.textLabel!.text = currentLegislator?.firstName
return cell
}
}
Thanks to Adam H. His comment made me reevaluate the URL I was using and by adding additional operators, now the data is shown in my second ViewController.
First of all, to avoid my bad English, I uploaded a video showing my problem
http://www.mediafire.com/download/j6krsa274o80ik9/Screen_Recording.mov
Second, I have a UITableViewController, that uses a remote API to download data. the data contains many image URLs, my first problem is that the tableView is not being updated even though i am doing .reloadData() function
my second problem is that in the function:
tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
I download the images, which I had got their urls from the first call, then eventhing works good but I can't see the image unless I clicked on the row
Please see the video, it is easier to understand
Here is my code: (I gave you the full code of my UITableView because, it is simple, and because it has two functions, and they are the ones that making me problems)
class Physicst: NSObject {
let image : String
var imageData: UIImage?
let name : NSString
init(image: String, name: NSString) {
self.image = image
self.name = name
}
}
class PhysicistTableViewController: UITableViewController {
var physicsts : [Physicst]?
#IBOutlet var physicstsTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
loadDataFromDBPedia()
}
func loadDataFromDBPedia() {
let session = NSURLSession.sharedSession()
var url = "http://dbpedia.org/sparql/"
let query = "http://dbpedia.org&query=select * {?o dbo:thumbnail ?p . ?o dbo:award dbr:Nobel_Prize_in_Physics} limit 10"
url = url + "?default-graph-uri=" + query.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet())!
url = url + "&format=JSON&CXML_redir_for_subjs=121&CXML_redir_for_hrefs=&timeout=30000&debug=on"
let request = NSMutableURLRequest(URL: NSURL(string: url)!)
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response ,error) in
if let error = error {
print ("\(error)")
}
if let response = response {
let httpResponse = response as! NSHTTPURLResponse
let statusCode = httpResponse.statusCode
print("Status code = \(statusCode)")
}
if let data = data {
do {
let jsonResponse = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions())
let binding = jsonResponse["results"]!!["bindings"] as! NSArray
for oneBinding in binding {
let name = oneBinding["o"]!!["value"] as! NSString
var image = oneBinding["p"]!!["value"] as! String
image = image.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
let physicst = Physicst(image: image, name: name)
if self.physicsts == nil {
self.physicsts = [Physicst]()
}
self.physicsts!.append(physicst)
}
self.physicstsTableView.reloadData()
}catch _ {
print ("not well json-formatted response")
}
}
})
task.resume()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.physicsts == nil {
return 0
}else {
return self.physicsts!.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("physicstCell")! as UITableViewCell
let row = indexPath.row
let physicst = self.physicsts![row]
cell.textLabel?.text = physicst.name as String
if (physicst.imageData == nil) {
let session = NSURLSession.sharedSession()
let url = NSURL(string: physicst.image as String)
if let url = url {
let request = NSMutableURLRequest(URL: url)
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if let data = data {
let imageData = UIImage(data: data)
cell.imageView?.image = imageData
physicst.imageData = imageData
self.physicstsTableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
}
})
task.resume()
}else {
print ("nill URL \(physicst.image)")
}
}else {
cell.imageView?.image = physicst.imageData!
}
return cell
}
}
**fell free to copy/paste it, there is no custom cell, so it should work **
tableView reload should be called on main thread. session.dataTaskWithRequest completion block is called on background thread performing UI Operations on background thread might lead to serious consequences. I believe the problem you are facing is just one of those consequences. Modify the code as follow.
func loadDataFromDBPedia() {
let session = NSURLSession.sharedSession()
var url = "http://dbpedia.org/sparql/"
let query = "http://dbpedia.org&query=select * {?o dbo:thumbnail ?p . ?o dbo:award dbr:Nobel_Prize_in_Physics} limit 10"
url = url + "?default-graph-uri=" + query.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet())!
url = url + "&format=JSON&CXML_redir_for_subjs=121&CXML_redir_for_hrefs=&timeout=30000&debug=on"
let request = NSMutableURLRequest(URL: NSURL(string: url)!)
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response ,error) in
if let error = error {
print ("\(error)")
}
if let response = response {
let httpResponse = response as! NSHTTPURLResponse
let statusCode = httpResponse.statusCode
print("Status code = \(statusCode)")
}
if let data = data {
do {
let jsonResponse = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions())
let binding = jsonResponse["results"]!!["bindings"] as! NSArray
for oneBinding in binding {
let name = oneBinding["o"]!!["value"] as! NSString
var image = oneBinding["p"]!!["value"] as! String
image = image.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
let physicst = Physicst(image: image, name: name)
if self.physicsts == nil {
self.physicsts = [Physicst]()
}
self.physicsts!.append(physicst)
}
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.physicstsTableView.reloadData()
}
}catch _ {
print ("not well json-formatted response")
}
}
})
task.resume()
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("physicstCell")! as UITableViewCell
let row = indexPath.row
let physicst = self.physicsts![row]
cell.textLabel?.text = physicst.name as String
if (physicst.imageData == nil) {
let session = NSURLSession.sharedSession()
let url = NSURL(string: physicst.image as String)
if let url = url {
let request = NSMutableURLRequest(URL: url)
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if let data = data {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let imageData = UIImage(data: data)
cell.imageView?.image = imageData
physicst.imageData = imageData
self.physicstsTableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
})
}
})
task.resume()
}else {
print ("nill URL \(physicst.image)")
}
}else {
cell.imageView?.image = physicst.imageData!
}
return cell
}
TIP
Downloading the images manually for each cell and then loading it to tableViewCell and handling caching in order to improve the performance of scroll is like re inventing the wheel when you have tubless tires availabe :) Please consider using SDWebImage or AFNetworking I have personlly used SDWebImage and its caching feature works perfectly.
I'm populating my tableView with JSON data, most of the time the data shows but for some strange reason other times it doesn't. I tested the JSON data in Chrome and the info is there. I also made print statements to print the info after it has downloaded and it appears to download correctly. I can't figure out why 80% of the time the data populates the tableView correctly and 20% of the time it doesn't. Here is a sample of my code, there are many more cells but I shortened it to 2 for this example:
var task : NSURLSessionTask?
var newURL : String?
var bannerArray: [String] = []
var overViewArray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
getJSON(newURL!)
}
func getJSON (urlString: String) {
let url = NSURL(string: urlString)!
let session = NSURLSession.sharedSession()
task = session.dataTaskWithURL(url) {(data, response, error) in
dispatch_async(dispatch_get_main_queue()) {
if (error == nil) {
self.updateDetailShowInfo(data)
}
else {
"Not getting JSON"
}
}
}
task!.resume()
}
func updateDetailShowInfo (data: NSData!) {
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
guard let banner = jsonResult["banner"] as? String,
let overview = jsonResult["overview"] as? String
else { return }
_ = ""
print(overview)
bannerArray.append(banner)
overViewArray.append(overview)
}
catch {
print("It ain't working")
}
self.DetailTvTableView.reloadData()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 2
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0: return bannerArray.count
case 1: return overViewArray.count
default: fatalError("Unknown Selection")
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
switch indexPath.section {
case 0:
let cell = tableView.dequeueReusableCellWithIdentifier("bannerCell", forIndexPath: indexPath) as! BannerCell
cell.bannerImage.sd_setImageWithURL(NSURL(string: bannerArray[indexPath.row]))
self.DetailTvTableView.rowHeight = 100
DetailTvTableView.allowsSelection = false
return cell
case 1:
let cell = tableView.dequeueReusableCellWithIdentifier("overviewCell", forIndexPath: indexPath) as! OverviewCell
let overViewText = overViewArray[indexPath.row]
if overViewText != "" {
cell.overView.text = overViewText
} else {
cell.overView.text = "N/A"
}
self.DetailTvTableView.rowHeight = 200
print(overViewArray[indexPath.row])
return cell
default: ""
}
return cell
}
I'm just doing this off the web. And I think there are some errors. You need to debug them yourself.
Your understanding of fetching the JSON and GCD is totally wrong. I believe these codes you got somewhere off the web. Go read up what is dispatch_async.
Basically, you need to create session to fetch JSON data, which you have done it correctly, however, within the NSJSONSerialization, you need to store them in a variable and append it to your array. This is fetched asynchronously. Your dispatch_async will reload data serially.
func getJSON (urlString: String) {
let url = NSURL(string: urlString)!
let session = NSURLSession.sharedSession()
task = session.dataTaskWithURL(url) {(data, response, error) in
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
guard let banner = jsonResult["banner"] as? String,
let overview = jsonResult["overview"] as? String
bannerArray.append(banner)
overViewArray.append(overview)
} dispatch_async(dispatch_get_main_queue()) {
if (error == nil) {
self.DetailTvTableView.reloadData()
}
else {
"Not getting JSON"
}
}
catch {
print("It ain't working")
}
}
}
task!.resume()
}