For the last three days I have been trying to make some progress with this, but no matter what I try I can't seem to wrap my head around how to solve this problem. This answer is what got me (I think) almost all the way there (but not quite): Capturing data from Alamofire
What I am trying to do is get the number of reviews for an app. Because Alamofire does its network calls asynchronously, I am trying to create a callback that will then allow me to actually work with the JSON that it returns. In the code below I am trying to get the reviews JSON outside of my Alamofire function and assign it to the reviewJSON variable so that I can do things with it. What am I doing wrong?
import UIKit
import SwiftyJSON
import Alamofire
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let appStoreReviewsURL: String = "https://itunes.apple.com/de/rss/customerreviews/id=529479190/json"
func getDataFromInterwebs(theURL: String, complete:(reviews: JSON) -> ()) {
Alamofire.request(.GET, theURL).responseJSON { response in
guard response.result.error == nil else {
print("error calling GET ")
print(response.result.error!)
return
}
if let value = response.result.value {
let appReviewsFromAppStore = JSON(value)
complete(reviews: appReviewsFromAppStore)
}
else {
print("error parsing")
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
var reviewJson = getDataFromInterwebs(appStoreReviewsURL){ completion in
return completion}
print(reviewJson)
}
Please try following way. See if it works for you
// Change your getDataFromInterwebs like this
func getDataFromInterwebs(theURL: String, complete:(reviews: JSON) -> Void) {
Alamofire.request(.GET, theURL).responseJSON { response in
guard response.result.error == nil else {
print("error calling GET ")
print(response.result.error!)
return
}
if let value = response.result.value {
let appReviewsFromAppStore = JSON(value)
complete(reviews: appReviewsFromAppStore)
}
else {
complete(reviews: "Error occured while trying to parse data")
print("error parsing")
}
}
}
}
then in viewdidload is like this
override func viewDidLoad() {
super.viewDidLoad()
getDataFromInterwebs(appStoreReviewsURL) { (reviews) in
var reviewJson = reviews
dispatch_async(dispatch_get_main_queue(), {
self.didGetRattingJson(reviewJson)
})
}
}
and finally
func didGetRattingJson(reviewJson: JSON) {
//do whatever you want to do here
}
Related
This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 1 year ago.
I'm trying to return json data from an api call. I'm able to access the json data successfully but am struggling to find a way / the best way to return it for access in my app. Thanks for any ideas!
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// make the api call and obtain data
let data = self.loadData()
print("inside viewDidLoad", data) // prints 'inside viewDidLoad emptyString'
}
func loadData() -> String {
var circData = "emptyString"
let session = URLSession.shared
let url = URL(string: "https://us-east-1.aws.webhooks.mongodb-realm.com/api/client/v2.0/app/cirdata-khyvx/service/cirData/incoming_webhook/cirlAPI")!
let task = session.dataTask(with: url, completionHandler: { data, response, error in
if let json = try? JSONSerialization.jsonObject(with: data!, options: []) {
// print("json: ", json) // prints the whole json file, verifying the connection works. Some 300kb of data.
// print("json file type: ", type(of: json)) // prints '__NSArrayI'
let jsonString = "\(json)"
circData = jsonString
// print("circData", circData) // prints the whole json file, verifying that the json string has been assigned to 'circData'
}
})
task.resume()
// print("after: ", circData) // prints 'after: emptyString'. It's as if the reassignment didn't take place.
return circData
}
}
You can't return a value synchronously becuase the api call that is fetching json data is asynchronous. You need to use a completion handler instead.
You can put breakpoints in different places inside the code to understand how the flow executes.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.loadData(completion: { [weak self] (result, error) in
if let error = error {
print(error.localizedDescription)
}
if let result = result {
print(result)
}
})
}
func loadData(completion: #escaping (_ data: Any?, _ error: Error?) -> Void) {
let url = URL(string: "https://us-east-1.aws.webhooks.mongodb-realm.com/api/client/v2.0/app/cirdata-khyvx/service/cirData/incoming_webhook/cirlAPI")!
let task = URLSession.shared.dataTask(with: url, completionHandler: { data, response, error in
if let error = error {
completion(nil, error)
return
}
do {
if let data = data {
let json = try JSONSerialization.jsonObject(with: data, options: [.allowFragments])
completion(json, nil)
} else {
completion(nil, nil)
}
} catch {
completion(nil, error)
}
})
task.resume()
}
}
I am trying to save data from a JSON call into a variable (calling this in ViewDidLoad if it makes a difference). I've been stuck on this for quite a while and am getting pretty frustrated.
I know there are topics on this already, but I can't seem to figure it out and I'm hoping theres a simple explanation for what I'm doing wrong.
Edit: The execution goes to "print("breakpoint")" line before doing the JSON call, I'd like to force execution to wait so I can actually fill up wantedCARDSET but am struggling with how to do that. Sorry if that was unclear!
Here's the call in viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
setPickerView.dataSource = self
setPickerView.delegate = self
testJSONgrab.getCards(url: "https://mtgjson.com/json/WAR.json") {json, error in
DispatchQueue.main.async {
for item in json {
self.wantedCARDSET.append(item)
}
}
}
print("breakpoint")
getCards function
class mtgJSONDATA {
func getCards(url: String, completionHandler: #escaping ([CARDS], Error?)-> Void) {
var cardSet = [CARDS]()
guard let url = URL(string:url) else {return}
let session = URLSession.shared
let task = session.dataTask(with: url, completionHandler: {
(data, response, error) in
if error == nil {
guard let data = data else {return}
do {
let decoded = try JSONDecoder().decode(mtgJSON.self, from: data)
DispatchQueue.main.async {
for item in decoded.cards! {
cardSet.append(item)
}
completionHandler(cardSet, nil)
}
} catch let jsonError {
print("Error serializing JSON: ", jsonError)
}
}
})
task.resume()
}
You miss a reload
DispatchQueue.main.async {
self.wantedCARDSET = item
self.setPickerView.reloadAllComponents()
}
As the call to get your data is asynchronous
I am trying to parse JSON data using SwiftyJSON into an array to use in my TableView. However even though I can successfully request the data and parse it into an array, I cannot return it from the getObjects function as it is done asynchronously. I have tried to use a completion handler, and after following several tutorials it seems I am missing something.
Does anybody know how I can return the array to use in my TableViewController ?
Table View Controller
let objects = [Objects]()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let urlString = "URLSTRING"
objects = dataManager.getObjects(urlString)
print("objects in view controller products array \(objects.count)")
self.tableView.reloadData
}
Request Functions
class DataManager {
func requestObjects(_ stringUrl: String, success:#escaping (JSON) -> Void, failure:#escaping (Error) -> Void) {
print("Request Data")
Alamofire.request(stringUrl, method: .get).validate().responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
print("responce success")
success(json)
case .failure(let error):
print(error)
} //End of switch statement
} //End of alamofire request
} //End of request function
func getObjects(_ urlString:String) -> [Object] {
var objects = [Object]()
requestObjects(urlString, success: { (JSONResponse) -> Void in
let json = JSONResponse
for item in json["items"] {
let title = item.1["title"].string
objects.append(Object(title: title!))
}
print("Number of objects = \(objects.count)")
}) {
(error) -> Void in
print(error)
}
print(objects) // Prints empty array
return objects // Array is empty
}
}
You need to use completionHandler to return data to TableViewController.
func getObjects(completionHandler : #escaping ([Object]) -> (),_ urlString:String) -> [Sneaker] {
var objects = [Object]()
requestObjects(urlString, success: { (JSONResponse) -> Void in
let json = JSONResponse
for item in json["items"] {
let title = item.1["title"].string
objects.append(Object(title: title!))
}
completionHandler(objects)
print("Number of objects = \(objects.count)")
}) {
(error) -> Void in
print(error)
}
print(objects) // Prints empty array
return objects // Array is empty
}
}
In your TableViewController
dataManager.getObject(completionHandler: { list in
self.objects = list
}, urlString)
There could be some syntax error i didnt test it
This is my JSON data
{
"Number":"ID001",
"Password":"1111",
"Email":"email#gmail.com"
}
Currently i'm using SwiftyJSON to print specific JSON output in X Code. So far i managed to print email value specifically. But i'm not sure how to display email value in UILabel emailLbl.
The code as below.
import UIKit
import SwiftyJSON
class ViewController: UIViewController {
#IBOutlet var emailLbl: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "http://localhost/get.php")
let task = URLSession.shared.dataTask(with: url!) {
(data, response, error) in
if error != nil {
print("Error")
}
else {
guard let data = data else {
print("data was nill ?")
return
}
let json = JSON(data: data)
print(json["email"].string!)
}
}
task.resume()
}
}
Does Anyone have any idea how ?
Thanks.
Try this:
let json = JSON(data: data)
print(json["email"].string!)
self.emailLbl.text = json["email"].string!
I want to show the JSON data grabbed from a server on a Table View. The problem is, I can't get it to show up on it. I have tried several different methods and searched a lot to find a solution, but I can't.
My code (all of it) is shown below and I hope somebody can help me out. Thanks in advance.
import UIKit
import Alamofire
import SwiftyJSON
class MasterViewController: UITableViewController {
var tableTitle = [String]()
var tableBody = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
getJSON()
}
func getJSON(){
Alamofire.request(.GET, "http://announcement.vassy.net/api/AnnouncementAPI/Get/").responseJSON { (Response) -> Void in
// checking if result has value
if let value = Response.result.value {
let json = JSON(value)
for anItem in json.array! {
let title: String? = anItem["Title"].stringValue
let body: String? = anItem["Body"].stringValue
self.tableTitle.append(title!)
self.tableBody.append(body!)
}
}
}
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// Table View Stuff
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tableTitle.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! TableViewCell
// cell config
cell.title!.text = tableTitle[indexPath.row]
cell.body!.text = tableBody[indexPath.row]
return cell
}
}
The Alamofire network request is asynchronous, meaning you can't know when the result will come back.
The problem here is that you reload the tableView outside the scope of the Alamofire request, so it is executed before the data comes back.
The reload should happen in the same scope, and on the main thread, for example:
func getJSON(){
Alamofire.request(.GET, "http://announcement.vassy.net/api/AnnouncementAPI/Get/").responseJSON { (Response) -> Void in
// checking if result has value
if let value = Response.result.value {
let json = JSON(value)
for anItem in json.array! {
let title: String? = anItem["Title"].stringValue
let body: String? = anItem["Body"].stringValue
self.tableTitle.append(title!)
self.tableBody.append(body!)
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
}
}
I think #Eric said almost everything in his answer, nevertheless, not it's a good decision in design keep the code for make the network request in your same UITableViewController this keep a couple between two things that are independents and change for differents reasons.
My advice is separate the two parts of the code decoupling the dependency between your two layers. In this way when you need to change anything related with your networking request handler you don't need to change it in any place where you make the same request, it's an advice!!!.
In case you want to do it, you can use closures to hanlde the async behaviour of Alamofire passgin the completionHandlerinside the wrapper you make to handle the networking requests, for example, let's define a simple wrapper using the singleton pattern (it's just for the purpose of explain the sample, you can handle it as you want).
import AlamofireImage
import SwiftyJSON
class NetworkHandler {
/// The shared instance to define the singleton.
static let sharedInstance = RequestManager()
/**
Private initializer to create the singleton instance.
*/
private init() { }
func getJSON(completionHandler: (json: JSON?, error: NSError?) -> Void) {
Alamofire.request(.GET, http://announcement.vassy.net/api/AnnouncementAPI/Get/).responseJSON { response in
switch(response.result) {
case .Success(let value):
let json = JSON(value)
completionHandler(json: json, error: nil)
case .Failure(let error):
completionHandler(json: nil, error: error)
}
}
}
}
Then in your UITableViewController you can call the new wrapper to Alamofire in this way:
class MasterViewController: UITableViewController {
var tableTitle = [String]()
var tableBody = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
NetworkHandler.sharedInstance.getJSON { [weak self] (json, error) -> Void in
// request was made successful
if error == nil {
for anItem in json.array! {
let title: String? = anItem["Title"].stringValue
let body: String? = anItem["Body"].stringValue
self.tableTitle.append(title!)
self.tableBody.append(body!)
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
}
}
// rest of your code
}
In the above way you keep the code decoupled, it's a good design.
I hope this help you