I tried a lot of methods from stackoverflow and from googling
but I got no luck of reaching what I need.
simply I want to store the response from api request to some variable
so that I can use it freely.
here is the code
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var responseLabel: UILabel!
let apiUrlStr = "http://jsonplaceholder.typicode.com/posts"
var resData = NSArray()
override func viewDidLoad() {
super.viewDidLoad()
request(.GET, apiUrlStr, encoding: .JSON).responseJSON { (_, _, JSON, _) in
let arr = JSON as NSArray
let user = arr[0] as NSDictionary
self.responseLabel.text = user.objectForKey("title") as? String
self.resData = JSON as NSArray //i cant get this from outside
}
println(resData) //prints nothing but empty parentheses while inside the request it prints the data right
}
}
because I have multiple classes and every class need to handle the response in a different way I want to create an api class with a response method that will return the response.
something like this maybe:
class api{
func req(url: String, params: NSDictionary){
//method goes here
}
func res() -> NSDictionary{
//method goes here
var response = NSDictionary()
return response
}
}
then use it like this in viewdidload
let params = ["aaa","bbb"]
let api = api()
api.req(apiUrlStr, params)
let res = api.res()
***by the way the request method I'm using right now is from Alamofire.swift and I dont mind using another method
here is some of the posts and sites I tried to follow without a luck
proper-way-to-pull-json-api-resoponse
link2
all lead to the same result response only from inside the method.
The problem is that the call to the network (looks like you're using alamofire) is asynchronous. So the network request is started and before it completes, your
println(resData)
line executes. At this point the response from the network hasn't returned so resData is still an empty array.
Try doing this instead:
Add a new function
func handleResponse(resData: NSArray) {
self.resData = resData
println(resData)
}
Then in the response code replace
self.resData = JSON as NSArray
with
let resData = JSON as NSArray
self.handleResponse(resData)
Assuming you need more than just a println statement to change when the data is loaded, you will also need to inform things like table views or other UI components to reload their data source in this method.
Related
I am making an API call for muscles relating to an exercise, the call looks like this:
func loadPrimaryMuscleGroups(primaryMuscleIDs: [Int]) {
print(primaryMuscleIDs)
let url = "https://wger.de/api/v2/muscle"
Alamofire.request(url).responseJSON { response in
let jsonData = JSON(response.result.value!)
if let resData = jsonData["results"].arrayObject {
let resData1 = resData as! [[String:AnyObject]]
if resData1.count == 0 {
print("no primary muscle groups")
self.musclesLabel.isHidden = true
} else {
print("primary muscles used for this exercise are")
print(resData)
self.getMuscleData(muscleUrl: resData1[0]["name"] as! String)
}
}
}
}
This returns me a whole list of all the muscles available, I need it to just return the muscles the exercise requires. This is presented in exercises as an array of muscle id's, which I feed in via the viewDidLoad below
self.loadPrimaryMuscleGroups(primaryMuscleIDs: (exercise?.muscles)!)
So I am feeding the exercises muscle array into the func as an [Int] but at this point im stumped on how to filter the request so that the resulting muscle data are only the ones needed for the exercise.
I was thinking it would be something like using primaryMuscleIDs to filter the id property of a muscle in the jsonData response, but im not sure how to go about that?
Thanks for any clarify here, hopefully I have explained it clearly enough to come across well
You'd want to do something like this in your else block:
var filteredArray = resData1.filter { item in
//I'm not exactly sure of the structure of your json object,
//but you'll need to get the id of the item as an Int
if let itemId = item["id"] as? Int {
return primaryMuscleIDs.contains(itemId)
}
return false
}
//Here with the filtered array
And since the Alamofire request is asynchronous, you won't be able to return a result synchronously. Your method will need to take a callback that gets executed in the Alamofire response callback with the filtered response.
Something like (but hopefully with something more descriptive than an array of Any:
func loadPrimaryMuscleGroups(primaryMuscleIDs: [Int], callback: ([Any]) -> ()) {
//...
Alamofire.request(url).responseJSON { response in
//...
//once you get the filtered response:
callback(filteredArray)
}
}
Finally, check out How to parse JSON response from Alamofire API in Swift? for the proper way to handle a JSON response from Alamofire.
I have a post class that I use to fill a collection view with post data from Firebase. I was having trouble getting some user data so I tried putting the observer in the post class. This seems to work fine, however there is a slight delay in getting the data from Firebase so it seems to finish the init() function before the firebase call is complete. This is the post class :
class Post {
var _comment1Text: String?
var _comment1User: String?
var _comment1Name: String?
init(comment1Text: String, comment1User: String, comment1Name: String) {
self._comment1Text = comment1Text
self._comment1User = comment1User
self._comment1Name = comment1Name
if self._comment1User != "" {
DataService.ds.REF_USERS.child(self._comment1User!).observeSingleEventOfType(.Value, withBlock: { userDictionary in
let userDict = userDictionary.value as! NSDictionary
self._comment1Name = userDict.objectForKey("username") as? String
})
}
print(self._comment1Text)
print(self._comment1Name)
}
}
If I print within the firebase call, it works. However, if I print after it, for some reason, comment1name is not yet filled. Is there a way to get self._comment1Name to contain the data from Firebase in time to fill the collectionView?
Thanks in advance.
DataService.ds.REF_USERS.child(self._comment1User!).observeSingleEventOfType(.Value
is an Asynchronous call, So access your print functions inside the completionBlock and you have to update your collectionView inside the completionBlock.
DataService.ds.REF_USERS.child(self._comment1User!).observeSingleEventOfType(.Value, withBlock: { userDictionary in
let userDict = userDictionary.value as! NSDictionary
self._comment1Name = userDict.objectForKey("username") as? String
print(self._comment1Text)
print(self._comment1Name)
// Update your collectionView
})
Asynchronous call's are loaded in a different network thread, so it takes some time to retrieve the DB from the server.
If you are looking for communicating between a custom class and you viewController look at my this answer :- https://stackoverflow.com/a/40160637/6297658
This question already has an answer here:
JSON parsing swift, array has no value outside NSURLSession
(1 answer)
Closed 6 years ago.
I collected JSON data from an API and am able to initialize my array (class instance variable), but after the block, data is destroyed from array. How can I return data from the task block that can be used to initialize my array?
override func viewDidLoad() {
// var movies=[Movie]()
let requestURL: NSURL = NSURL(string: "https://yts.ag/api/v2/list_movies.json")!
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(URL: requestURL)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(urlRequest) {
(data, response, error) -> Void in
let httpResponse = response as! NSHTTPURLResponse
let statusCode = httpResponse.statusCode
if (statusCode == 200) {
print("File downloaded successfully.")
do {
let jsonYts = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments)
let jsonDataTag = jsonYts["data"]
let jsonMovie = jsonDataTag!!["movies"]!
let movies = [Movie].fromJSONArray(jsonMovie as! [JSON])
// self.moviesList = movies as! [Movie] // this is my array. I want to add data to it
for data in self.moviesList {
// print(data.title)
}
} catch {
}
}
}
task.resume()
}
Asynchronous methods don't return to the invoking function because they are not called directly in the first place. They are placed on a task queue to be called later (on a different thread!). It's the hardest thing for those new to asynchronous programming to master. Your async method must mutate a class variable. So you'll have:
class MyContainer: UIViewController {
var myInstanceData: [SomeObject] // In your case moviesList
func startAsyncRequest() { // In your case viewDidLoad()
// In your case NSMutableURLRequest
doSomethingAsynchronous(callback: { [weak self] (result) in
// in your case JSON parsing
self?.myInstanceData = parseResult(result) // share it back to the containing class
// This method returns nothing; it is not called until well
// after startAsyncRequest() has returned
})
}
}
Once the callback finishes, myInstanceData is available to all the other methods in the class. Does that make sense as a general pattern?
After that you have to learn this material:
Swift performSegueWithIdentifier not working
To get the result back into the UI from the background thread on which your callback runs.
Addendum
Taking a second look after editing your question for clarity, it seems you may already know this, but you've commented out the correct line, and if you put it back in, things just work, that is, the movies won't be destroyed but will be recorded in the class property moviesList.
// self.moviesList = movies as! [Movie] // this is my array. I want to add data to it
Why not just re-enable it? Perhaps the real problem is: Do you mean to append instead of overwrite, perhaps? Then the answer may be as simple as:
self.moviesList += movies as! [Movie]
You can just use "SwiftyJson" to handle the response from the API if it returns JSON data; if it uses XML then use "SWXMLHash". They are both pods (libraries) designed to handle the response from their respective formats.
Installation help for SwiftyJson. If you still have problems comment below.
At this point you can just import SwiftyJson and use it.
Let response be any object, and suppose your JSON data is in this format:
{"text": {
"data": "Click Here",
"size": 36,
"style": "bold",
"name": "text1",
"hOffset": 250,
"vOffset": 100,
"alignment": "center"
}
}
Then you can just write code like this
func response(data){
let dt = JSON(data : data) // the second data is the response from api
response = dt.object // for accessing the values as object from json format
let style = response["text"]["data"]!! as? String // style = "bold"
let n = response["text"]["name"]!! as? String // n = "text1"
let size = response["text"]["hOffset"]!! as? Int // size = 250
}
// Done !! Easy.. if you guys need help or snapshots for the same do comment..
I'm a newly iOS developer and start to learn using alamofire.
here I can use alamofire to get data in my viewcontroller.swift.
but when I create a new class to write the same alamofire code , it won't get because of async. it'll return 0 first and the array prints up then.
how can I get array in ViewController after download finished?
in Viewcontroller:
self.jsonArray = Networking.alamofireGET("my_API")
print(self.jsonArray.count)
in Networking class:
class Networking {
class func alamofireGET(url:String) -> NSMutableArray
{
var orignalArray = NSMutableArray()
Alamofire.request(.GET, url).responseJSON { response in
if let JSON = response.result.value
{
//print("JSON: \(JSON)")
orignalArray = JSON["data"] as! NSMutableArray
print("抓到囉\(orignalArray)")
}
}
return orignalArray
}
}
its unclear what you are trying to ask. If you want the array to load in view controller, you can simply add the code in the viewDidLoad() method of the VC. You can also use the dispatch_async(dispatch_get_main_queue()) method.
dispatch_async(dispatch_get_main_queue(), {//write code here what ever you want to do})
In my Swift iOS project, I am trying to populate an array of custom class objects using JSON data retrieved with Alamofire and parsed with SwiftyJSON. My problem, though, is combining the results of two different network request and then populating a UITableView with the resulting array.
My custom class is implemented:
class teamItem: Printable {
var name: String?
var number: String?
init(sqljson: JSON, nallenjson: JSON, numinjson: Int) {
if let n = sqljson[numinjson, "team_num"].string! as String! {
self.number = n
}
if let name = nallenjson["result",0,"team_name"].string! as String! {
self.name = name
}
}
var description: String {
return "Number: \(number) Name: \(name)"
}
}
Here is my viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
refresh() {
() -> Void in
self.tableView(self.tableView, numberOfRowsInSection: self.teamsArr.count)
self.tableView.reloadData()
for item in self.teamsArr {
println(item)
}
return
}
self.tableView.reloadData()
}
which goes to the refresh() method:
func refresh(completionHandler: (() -> Void)) {
populateArray(completionHandler)
}
and finally, populateArray():
func populateArray(completionHandler: (() -> Void)) {
SqlHelper.getData("http://cnidarian1.net16.net/select_team.php", params: ["team_num":"ALL"]) {
(result: NSData) in
let jsonObject : AnyObject! = NSJSONSerialization.JSONObjectWithData(result, options: NSJSONReadingOptions.MutableContainers, error: nil)
let json = JSON(jsonObject)
self.json1 = json
println(json.count)
for var i = 0; i < json.count; ++i {
var teamnum = json[i,"team_num"].string!
NSLog(teamnum)
Alamofire.request(.GET, "http://api.vex.us.nallen.me/get_teams", parameters: ["team": teamnum])
.responseJSON { (req, res, json, err) in
let json = JSON(json!)
self.json2 = json
self.teamsArr.append(teamItem(sqljson: self.json1, nallenjson: self.json2, numinjson: i))
}
}
completionHandler()
}
}
the first problem I had was that i in the for loop reached 3 and caused errors when I thought it really shouldn't because that JSON array only contains 3 entries. My other main problem was that the table view would be empty until I manually triggered reloadData() with a reload button in my UI, and even then there were problems with the data in the tables.
really appreciate any assistance, as I am very new to iOS and Swift and dealing with Alamofire's asynchronous calls really confused me. The code I have been writing has grown so large and generated so many little errors, I thought there would probably be a better way of achieving my goal. Sorry for the long-winded question, and thanks in advance for any responses!
The Alamofire request returns immediately and in parallel executes the closure, which will take some time to complete. Your completion handler is called right after the Alamofire returns, but the data aren't yet available. You need to call it from within the Alamofire closure - this ensures that it is called after the data became available.