Reputation: 21
I am very new in swift programming and I am not that familiar with Web Services. I want to create an iOS mobile application for poems.
I was able to retrieve the json encoded data but my problem is I can't transfer it to my tableview here. When I ran it, I think the problem was because of the asynchronous task. Since it is in asynchronous task, the delegated functions of the tableview (numberOfRows) is executed when name.count is still 0, because the task would just run after the delegated func is executed. That's why I can't see my data in my TableView... I hope someone can help me here. I have googled and tried Completion Handlers (which I have no idea what it's used for) and I tried changing it to Synchronous which lead me to errors.. Thank you very much!!!
import UIKit
import Foundation
class TimelineViewController: UIViewController {
@IBOutlet weak var contentTableView: UITableView!
var name = [String]()
var bio = [String]()
@IBOutlet weak var oPostBtn: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
getPoems()
}
func getPoems()
{
let url:NSURL = NSURL(string: "http://192.168.1.6/Test/feed1.php")!
let request:NSMutableURLRequest = NSMutableURLRequest(url:url as URL)
request.httpMethod = "GET"
NSURLConnection.sendAsynchronousRequest(request as URLRequest, queue: OperationQueue.main){(response, data, error) in }
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
if error != nil {
print("Error = \(error)")
return
}
do {
print("Response=\(response)")
let responseString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print("Response data = \(responseString)")
//Converting response to NSDictionary
var json: NSDictionary!
json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
//getting the JSONArray poems from the response
let returnPoems: NSArray = json["returnPoems"] as! NSArray
print(returnPoems);
//looping through all the json objects in the array teams
for i in 0 ..< returnPoems.count{
let aObject = returnPoems[i] as! [String : AnyObject]
self.name.append(aObject["fullName"] as! String)
self.bio.append(aObject["Bio"] as! String)
//displaying the data
print("Fullname -> ", self.name)
print("Bio ->", self.bio)
print("===================")
print("")
}
}
catch {
print("ERROR: \(error)")
}
}
task.resume()
}
}
extension TimelineViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = contentTableView.dequeueReusableCell(withIdentifier: "PoemCell")
cell?.textLabel?.text = name[indexPath.row]
cell?.detailTextLabel?.text = bio[indexPath.row]
return cell!
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return name.count
}
func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("You selected cell #\(indexPath.row)!")
let indexPath = tableView.indexPathForSelectedRow!
let currentCell = tableView.cellForRow(at: indexPath)! as UITableViewCell
}
}
Upvotes: 0
Views: 621
Reputation: 438122
In your completion block, when you're done building the name
and bio
arrays, refresh the table by calling reloadData
from the main queue:
DispatchQueue.main.async {
self.tableView.reloadData()
}
There were a few other things I'd suggest:
You should remove that NSURLConnection
code. It's performing redundant request and you're not doing anything with the response; plus it is a deprecated API anyway and should be removed;
You should use URL
instead of NSURL
;
You should use URLRequest
instead of NSMutableURLRequest
;
You should use String
rather than NSString
;
You should use Swift Array
and Dictionary
rather than NSArray
and NSDictionary
;
Your signature for didSelectRowAt
is incorrect. Use IndexPath
, not NSIndexPath
.
You are updating the name
and bio
arrays from the URLSession
background queue. That is not thread-safe. You can solve this by updating these from the main queue to avoid needing to do additional synchronization.
I'd even suggest getting rid of those two different arrays and have a single array of a custom object, Poem
. This makes code much simpler. It opens up new possibilities, too (e.g. what if you want to sort the name
array ... how would you update the separate bio
array accordingly; use a single custom object and this problem goes away).
Thus:
struct Poem {
let name: String
let bio: String
init?(dictionary: [String: Any]) {
guard let name = dictionary["fullName"] as? String,
let bio = dictionary["Bio"] as? String else {
print("Did not find fullName/Bio")
return nil
}
self.name = name
self.bio = bio
}
}
class TimelineViewController: UIViewController {
@IBOutlet weak var contentTableView: UITableView!
var poems = [Poem]()
@IBOutlet weak var oPostBtn: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
getPoems()
}
func getPoems() {
let url = URL(string: "http://192.168.1.6/Test/feed1.php")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("Error = \(error?.localizedDescription ?? "Unknown error")")
return
}
if let response = response {
print("Response=\(response)")
}
if let responseString = String(data: data, encoding: .utf8) {
print("Response data = \(responseString)")
}
// Converting response to Dictionary
guard let json = try? JSONSerialization.jsonObject(with: data),
let responseObject = json as? [String: Any],
let returnPoems = responseObject["returnPoems"] as? [[String: Any]] else {
print("did not find returnPoems")
return
}
print(returnPoems)
// dispatch the update of model and UI to the main queue
DispatchQueue.main.async {
self.poems = returnPoems.flatMap { Poem(dictionary: $0) }
self.contentTableView.reloadData()
}
}
task.resume()
}
}
extension TimelineViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = contentTableView.dequeueReusableCell(withIdentifier: "PoemCell", for: indexPath)
let poem = poems[indexPath.row]
cell.textLabel?.text = poem.name
cell.detailTextLabel?.text = poem.bio
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return poems.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You selected cell #\(indexPath.row)!")
// let indexPath = tableView.indexPathForSelectedRow! // why do this? ... the indexPath was passed to this function
// let currentCell = tableView.cellForRow(at: indexPath)! as UITableViewCell // why do this? ... you don't care about the cell ... go back to you model
let poem = poems[indexPath.row]
// do something with poem, e.g.
print(poem)
}
}
Upvotes: 1