I have a class ApiManager() that make request to JSON data in which I learned from this tutorial, which used protocol delegate approach to pass the data to ViewController class
The data is acquired fine but I am not sure how to use it around?! in this case I am trying to use it inside TableView
class ViewController: UITableViewController, ApiManagerDelegate{
var names:[String] = [] // the variable which will hold the JSON data
override func viewDidLoad() {
super.viewDidLoad()
//instantiate the ApiManager Class
//Set the ViewController as its delegate
//perform request to Restaurants info
let manager = ApiManager()
manager.delegate = self
manager.getRestaurantsData()
func didReceiveResponse (info: [String : AnyObject]){
//Read name property from data dictionary (JSON)
if let restaurantsData = info["restaurants"] as? [[String: AnyObject]]{
for restaurant in restaurantsData{
let name = restaurant["name"] as? String
self.names.append(name!)
}
}
print("Data1: \(names)") // prints the data perfectly
}
func didFailToReceiveResponse() {
print("There was an error in recieving API data")
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print (names) //no data here
return names.count //not working
}
I am a bit confused how to work around this, I tried to make return value to didReieveResponse(), but the issue is when I call the function it needs the argument (which is passed to it in the Delegator class "dictionary").. I am completely confused.
Here is the delegator class and protocol for reference:
import UIKit
//Custom Protocol Declaration
#objc protocol ApiManagerDelegate {
optional func didReceiveResponse(info: [ String : AnyObject ])
optional func didFailToReceiveResponse()
}
class ApiManager: NSObject, NSURLSessionDelegate {
//open restaurant web API
private let requestURL = NSURL(string:"http://some-url-here.com")
var delegate: ApiManagerDelegate?
override init() {
super.init()
}
func getRestaurantsData() {
let defaultConfigObject = NSURLSessionConfiguration.defaultSessionConfiguration()
let defaultSession = NSURLSession (configuration: defaultConfigObject, delegate: self, delegateQueue: NSOperationQueue.mainQueue ())
let request = NSMutableURLRequest(URL: requestURL!, cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringCacheData, timeoutInterval: 60 )
request.HTTPMethod = "POST"
request.setValue( "application/x-www-form-urlencoded" , forHTTPHeaderField: "Content-Type" )
let dataTask = defaultSession.dataTaskWithRequest(request, completionHandler: { (data: NSData?, response: NSURLResponse?, error: NSError?) in
if let responseError = error {
self.delegate?.didFailToReceiveResponse?()
print("Reponse Error: \( responseError )" )
} else {
do {
let dictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! [String: AnyObject]
self.delegate?.didReceiveResponse?(dictionary)
//print( "Response: \( dictionary )" )
print("Response: Success")
} catch let jsonError as NSError {
// Handle parsing error
self.delegate?.didFailToReceiveResponse?()
print( "JSONError: \(jsonError.localizedDescription)")
}
}
})
dataTask.resume()
}
}
thanks,
Update:
For future Developers who might suffer like me, the solution is to use TableViewName.reloadData() as mentioned below..
But please notice, it did only worked with me when I placed DidRecieveResponse() function outside ViewDidLoad, not sure why Hopefully one of the experts can explain it later.
Enjoy!
do like
class ViewController: UITableViewController, ApiManagerDelegate{
var names:[String] = []
let manager = ApiManager()
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self
manager.getRestaurantsData()
}
func didReceiveResponse (info: [String : AnyObject]){
//Read name property from data dictionary (JSON)
if let restaurantsData = info["restaurants"] as? [[String: AnyObject]]{
for restaurant in restaurantsData{
let name = restaurant["name"] as? String
self.names.append(name!)
}
print("Data1: \(names)") // prints the data perfectly
if (self.names.count>0)
{
yourtableview.reloadData()
}
}
}
func didFailToReceiveResponse() {
print("There was an error in recieving API data")
}
Related
I have a problem I'm trying to wrap my head around relating to the use of completion handlers. I have 3 layers in my iOS program, the ViewController->Service->Networking. I need to load some data through API call from the view controller.
I have defined functions(completionHandlers) in the ViewController that should execute once the data request is complete and am comfortable when in implementing completion handlers when only two layers exists, but confused when in the following scenario:
DashboardViewController.swift
import UIKit
#IBDesignable class DashboardViewController: UIViewController {
#IBOutlet weak var stepCountController: ExpandedCardView!
var articles:[Article]?
let requestHandler = RequestHandler()
let dashboardService = DashboardService()
override func viewDidLoad() {
super.viewDidLoad()
dashboardService.getDashboardData(completionHandler: getDashboardDataCompletionHandler)
}
func getDashboardDataCompletionHandler(withData: DashboardDataRequest) {
print(withData)
}
}
DashboardService.swift
import Foundation
class DashboardService: GeneralService {
var requestHandler: DashboardRequestHandler
override init() {
requestHandler = DashboardRequestHandler()
super.init()
}
//this function should execute requestHandler.requestDashboardData(), and then execute convertDashboardData() with the result of previous get request
func getDashboardData(completionHandler: #escaping (DashboardDataRequest) -> Void) {
//on network call return
guard let url = URL(string: apiResourceList?.value(forKey: "GetDashboard") as! String) else { return }
requestHandler.requestDashboardData(url: url, completionHandler: convertDashboardData(completionHandler: completionHandler))
}
func convertDashboardData(completionHandler: (DashboardDataRequest) -> Void) {
//convert object to format acceptable by view
}
}
DashboardRequestHandler.swift
import Foundation
class DashboardRequestHandler: RequestHandler {
var dataTask: URLSessionDataTask?
func requestDashboardData(url: URL, completionHandler: #escaping (DashboardDataRequest) -> Void) {
dataTask?.cancel()
defaultSession.dataTask(with: url, completionHandler: {(data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else {
return
}
do {
let decodedJson = try JSONDecoder().decode(DashboardDataRequest.self, from: data)
completionHandler(decodedJson)
} catch let jsonError {
print(jsonError)
}
}).resume()
}
}
If you look at the comment in DashboardService.swift, my problem is quite obvious. I pass a completion handler from ViewController to Service and Service has its own completion handler that it passes to RequestHandler where the view controller completion handler (getDashboardDataCompletionHandler) should be executed after the Service completion handler (convertDashboardData())
Please help me in clarifying how to implement this. Am I making a design mistake by trying to chain completionHandlers like this or am I missing something obvious.
Thank you
--EDIT--
My Request Handler implementation is as follows:
import Foundation
class RequestHandler {
// let defaultSession = URLSession(configuration: .default)
var defaultSession: URLSession!
init() {
guard let path = Bundle.main.path(forResource: "Api", ofType: "plist") else {
print("Api.plist not found")
return
}
let apiResourceList = NSDictionary(contentsOfFile: path)
let config = URLSessionConfiguration.default
if let authToken = apiResourceList?.value(forKey: "AuthToken") {
config.httpAdditionalHeaders = ["Authorization": authToken]
}
defaultSession = URLSession(configuration: config)
}
}
In this case is more clear to use delegation, for example like this:
protocol DashboardServiceDelegate {
func didLoaded(_ viewModel: DashboardViewModel)
}
class DashboardViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
dashboardService.delegate = self
dashboardService.getDashboardData()
}
}
extension DashboardViewController: DashboardServiceDelegate {
func didLoaded(_ viewModel: DashboardViewModel) {
///
}
}
protocol DashboardRequestHandlerDelegate() {
func didLoaded(request: DashboardDataRequest)
}
class DashboardService: GeneralService {
private lazy var requestHandler: DashboardRequestHandler = { reqHandler in
reqHandler.delegate = self
return reqHandler
}(DashboardRequestHandler())
func getDashboardData() {
guard let url = ...
requestHandler.requestDashboardData(url: url)
}
}
extension DashboardService: DashboardRequestHandlerDelegate {
func didLoaded(request: DashboardDataRequest) {
let viewModel = convert(request)
delegate.didLoaded(viewModel)
}
}
I am trying to serialize a GET request then make a movie object, then appending that movie object to a movies array which I will use to show info on the UI.
I am new and have struggled with this problem for some time now :(
If you look at the self.movies?.append(movie) shouldnt that work? I dont see any reasons as to when i try to get the first item i get fatal error index out of bounds which means I the Array is not filled yet.... Dont know what i am doing wrong :(
import UIKit
class ViewController: UIViewController {
var movies:[Movie]? = []
#IBOutlet weak var uiMovieTitle: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
getMovieData()
print(self.movies?.count)
setUI()
}
#IBAction func yesBtn(_ sender: UIButton) {
print(movies?[5].title ?? String())
}
#IBAction func seenBtn(_ sender: UIButton) {
}
#IBAction func noBtn(_ sender: UIButton) {
}
#IBOutlet weak var moviePoster: UIImageView!
let urlString = "https://api.themoviedb.org/3/discover/movie?api_key=935f539acbfed4b9e5534ddeed3fb57e&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres=12"
func getMovieData(){
//Set up URL
let todoEndPoint: String = "https://api.themoviedb.org/3/discover/movie?api_key=935f539acbfed4b9e5534ddeed3fb57e&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres=12"
guard let url = URL(string: todoEndPoint) else {
print("Cant get URL")
return
}
let urlRequest = URLRequest(url: url)
//Setting up session
let config = URLSessionConfiguration.default
let session = URLSession.shared
//Task setup
let task = session.dataTask(with: urlRequest) { (data, URLResponse, error) in
//Checking for errors
guard error == nil else{
print("Error calling GET")
print(error)
return
}
//Checking if we got data
guard let responseData = data else{
print("Error: No data")
return
}
self.movies = [Movie]()
do{//If we got data, if not print error
guard let todo = try JSONSerialization.jsonObject(with: responseData, options:.mutableContainers) as? [String:AnyObject] else{
print("Error trying to convert data to JSON")
return
}//if data is Serializable, do this
if let movieResults = todo["results"] as? [[String: AnyObject]]{
//For each movieobject inside of movieresult try to make a movie object
for moviesFromJson in movieResults{
let movie = Movie()
//If all this works, set variables
if let title = moviesFromJson["title"] as? String, let movieRelease = moviesFromJson["release_date"] as? String, let posterPath = moviesFromJson["poster_path"] as? String, let movieId = moviesFromJson["id"] as? Int{
movie.title = title
movie.movieRelease = movieRelease
movie.posterPath = posterPath
movie.movieId = movieId
}
self.movies?.append(movie)
}
}
}//do end
catch{
print(error)
}
}
////Do Stuff
task.resume()
}
func setUI(){
//uiMovieTitle.text = self.movies![0].title
//print(self.movies?[0].title)
}
}
my Movie class:
import UIKit
class Movie: NSObject {
var title:String?
var movieRelease: String?
var posterPath:String?
var movieId:Int?
var movieGenre:[Int] = []
//public init(title:String, movieRelease:String, posterPath:String,movieId:Int) {
// self.movieId = movieId
//self.title = title
//self.movieRelease = movieRelease
//self.posterPath = posterPath
//self.movieGenre = [movieGenre]
//}
}
getMovieData calls the network asynchronously. Your viewDidLoad invokes this, then calls setUI() - but the networking is still ongoing when setUI is called.
Instead, call setUI when the networking is complete - after the self.movies?.append(movie) line. The UI code will need to happen on the main thread. So...
for moviesFromJson... // your existing code
...
self.movies?.append(movie)
}
// Refresh UI now movies have loaded.
DispatchQueue.main.async {
setUI()
}
import UIKit
class ViewController: UIViewController {
var movies:[Movie]? = []
#IBOutlet weak var uiMovieTitle: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
getMovieDataCall(completionHandler: {data, error in self. getMovieDataCallBack(data: data, error: error)})
}
func getMovieDataCallBack(data: Data?, error: Error?) {
if error == nil {
let dictionary = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! Dictionary<String, AnyObject>
//do your appending here and then call setUI()
print("dictionaryMovie \(dictionary)")
} else {
showAlertView("", error?.localizedDescription)
}
}
func getMovieDataCall(completionHandler: #escaping (Data?, Error?) -> Void)){
//Set up URL
let todoEndPoint: String = "https://api.themoviedb.org/3/discover/movie?api_key=935f539acbfed4b9e5534ddeed3fb57e&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres=12"
guard let url = URL(string: todoEndPoint) else {
print("Cant get URL")
return
}
let urlRequest = URLRequest(url: url)
//Setting up session
let config = URLSessionConfiguration.default
let session = URLSession.shared
//Task setup
let task = session.dataTask(with: urlRequest) { (data, URLResponse, error) in
if error != nil {
NSLog("GET-ERROR", "=\(error)");
completionHandler(nil, error)
} else {
let dataString = String(data: data!, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue))
print(dataString!)
completionHandler(data, nil)
}
task.resume()
}
func setUI(){
}
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.
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.
In my app I have this class to get data from my server:
class Api{
func loadOffers(completion:(([Offers])-> Void), offer_id: String, offerStatus:String){
let myUrl = NSURL(string: "http://www.myServer.php/api/v1.0/offers.php")
let request = NSMutableURLRequest(URL: myUrl!)
request.HTTPMethod = "POST"
let postString = "offer_id=\(offer_id)&offerStatus=\(dealStatus)&action=show"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request)
{ data, response, error in
if error != nil {
println("error\(error)")
}else{
var err:NSError?
let jsonObject : AnyObject! = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil)
if let dict = jsonObject as? [String: AnyObject] {
if let myOffers = dict["offers"] as? [AnyObject] {
var offers = [Offers]()
for offer in myOffers{
let offer = Offers(dictionary: offer as! NSDictionary)
offers.append(offer)
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0 )){
dispatch_async(dispatch_get_main_queue()){
completion(offers)
}
}
}
}
}
}
}
task.resume()
}
}
then in my View Controller I load the model:
class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var offers: [Offers]!
func loadModel() {
let loadingNotification = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
loadingNotification.mode = MBProgressHUDMode.Indeterminate
loadingNotification.labelText = "updating your offers..."
offers = [Offers]()
let api = Api()
api.loadOffers(didLoadOffers , offer_id: dealIdent!, offerStatus: "open")
}
func didLoadOffers(offers:[Offers]){
self.offers = offers
self.tableView.reloadData()
MBProgressHUD.hideAllHUDsForView(self.view, animated: true)
self.refreshControl.endRefreshing()
}
override func viewWillAppear(animated: Bool) {
loadModel()
}
}
Everything works except that when the JSON dictionary is empty, meaning that there no offers the MBProgressHUD keep spinning.
I would like stop the activity indicator adding a subview instead which says that there are no offers. Any Suggestion would be greatly appreciated.
I tried:
if offers.isEmpty{
MBProgressHUD.hideAllHUDsForView(self.view, animated: true)
}
and also
if offers == 0 {
MBProgressHUD.hideAllHUDsForView(self.view, animated: true)
}
but it's not working
Thanks
This is happening because you set the HUD in main queue but are trying to remove from another one. All UI related changes should be done in main_queue()
Try using this code
dispatch_async(dispatch_get_main_queue(), {
// your code to modify HUD here
});
I recommend small redesign of code. I modified your original code a little. Here is API class:
class Api {
func loadOffers(offerID : String, offerStatus : String, completionHandler : ([Offer]?, NSError?) -> Void) {
let myURL = NSURL(string: "http://www.myServer.php/api/v1.0/offers.php")!
var request = NSMutableURLRequest(URL: myURL)
request.HTTPMethod = "POST"
let postString = "offer_id=\(offerID)&offerStatus=\(offerStatus)&action=show"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { (result : NSData!, response : NSURLResponse!, error : NSError!) -> Void in
if let existingError = error {
NSLog("error \(existingError.code) - \(existingError.localizedDescription)")
completionHandler(nil, existingError)
} else {
var parseError : NSError?
if let dictionary = NSJSONSerialization.JSONObjectWithData(result, options: .allZeros, error: nil) as? [String : AnyObject] {
if let myOffers = dictionary["offers"] as? [NSDictionary] {
var parsedOffers = [] as [Offer]
for jsonOffer in myOffers {
parsedOffers.append(Offer(dictionary: jsonOffer))
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
completionHandler(parsedOffers, nil)
}
}
} else {
NSLog("JSON parsing failed")
parseError = NSError(domain: "MyApp", code: 1, userInfo : nil)
completionHandler(nil, parseError)
}
}
}).resume()
}
}
Change is added calling of completion handler even in case error with network communication and in case of failure of JSON parsing.
Corresponding implementation of VC follows:
class ViewController: UITableViewController {
private var _offers : [Offer]?
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
loadModel()
}
private func loadModel() {
var hud = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
hud.mode = MBProgressHUDMode.Indeterminate
hud.labelText = "updating your offers"
Api().loadOffers("1", offerStatus: "1") { (offers : [Offer]?, error : NSError?) -> Void in
self._offers = offers
// TODO: Correct handling of error state ;)
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
hud.hide(true)
}
}
}
}
From my point of view is using closure better, but depends on your implementation. You can use your implementation with method instead of closure and just define hud variable on instance level.
I hope it helps you to solve your problem (I've tested on simulator and iPhone and works well with some testing stub framework).
if myOffers = nil cannot do complete(offers). so HUD could not stop. You can try this:
if let myOffers = dict["offers"] as? [AnyObject] {
var offers = [Offers]()
for offer in myOffers{
let offer = Offers(dictionary: offer as! NSDictionary)
offers.append(offer)
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0 )){
dispatch_async(dispatch_get_main_queue()){
completion(offers)
}
}
}
}
} else {
dispatch_async(dispatch_get_global_queue(priority, 0 )){
dispatch_async(dispatch_get_main_queue()){
completion([Offers]())
}
}
Every path in loadOffers needs to call the completion closure. If you have an if let statement, you need to consider the else case. If the optional is nil, you still need to call completion. You can pass an empty array in that case, or you can add an error parameter to your completion block so the view controller will know more information about why it's not getting anything back.