Json Values Append Array but ViewDidLoad it seem Empty - ios

I am working in iOS and when I take JSON datas , I print it with no error in the function and append it a String array ( I did it correctly )
But if I want to reach to String array later the json Function it seem empty array . For testing I printed the array in a Button Action then it worked for me but I want to use in String Datas in other Function when Json Function ends.
The Codes:
func getAllDataFromURL()
{
var i : Int = 0;
var urlDatas : [String] = allJsonDatas.getAllTours();
for(i = 0 ; i < allJsonDatas.getAllTours().count ; i += 1)
{
Alamofire.request(.GET, urlDatas[i]).responseJSON
{(response) -> Void in
if let arrivedData = response.result.value
{
print("********")
print(arrivedData["generalTourDistrict"] as! String);
print(arrivedData["otherTourDistrict"] as! String);
print(arrivedData["photoURLS"] as! [String]);
print(arrivedData["subTourDistrict"] as! String);
print(arrivedData["tourCalendar"] as! String);
print(arrivedData["tourDistrict"] as! String);
print(arrivedData["tourName"] as! String);
self.tryingSomething.appendContentsOf(arrivedData["photoURLS"] as! [String]);
print("********\n")
}
}
}
}
And viewDidLoad is
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.Request.addAcceptableImageContentTypes(acceptableContentTypes);
self.getAllDataFromURL();
self.printTheJsonDatas();
}
For Example , in getAllDataFromURL() function I can print it correctly.
This is the result for url data from the json:
["https://acenta.dominant.com.tr//dominant/webout/R12/php/product/img.php?path=L2QvMS9jcmkvc295a3VsL3VydW4vMDAvMjIvMTIvaW1hZ2UvL3VfMDAyMjEyLjAwMDAxLmpwZWc=&rx=650&ry=400", "https://acenta.dominant.com.tr//dominant/webout/R12/php/product/img.php?path=L2QvMS9jcmkvc295a3VsL3VydW4vMDAvMjIvMTIvaW1hZ2UvL3VfMDAyMjEyLjAwMDA2LmpwZWc=&rx=650&ry=400"]
But printTheJsonDatas() prints an empty array like -> [ ]
func printTheJsonDatas()
{
print(tryingSomething)
//tryingSomething : [String]
}
Surprisingly , when I put printTheJsonDatas() into buttonClickedAction, then it worked as I said before.
I think the problem is about threads but I can not say anything clearly .

You are calling both your web service request and print functions consecutive like they are both syncronous but they are not. When you make a web servic request with Alamofire it works asyncronous. So you can not expect it to finish in a certain time in future. What you should do is call your print function in .response block of the Alamofire request:
func getAllDataFromURL()
{
var i : Int = 0;
var urlDatas : [String] = allJsonDatas.getAllTours();
for(i = 0 ; i < allJsonDatas.getAllTours().count ; i += 1)
{
Alamofire.request(.GET, urlDatas[i]).responseJSON
{(response) -> Void in
if let arrivedData = response.result.value
{
self.tryingSomething.appendContentsOf(arrivedData["photoURLS"] as! [String]);
self.printTheJsonDatas();
}
}
}
}
An other problem with your code is it is making multiple web service call. Even if you call your print function in .response block it probably wont work as you think. It will print multiple times with different sizes.

getAllDataFromURL() runs asynchronously with a completion block (known as a closure in swift, or callback on other languages). The order of events is:
getAllDataFromURL()
printTheJsonDatas()
Alamofire.request()
(wait for AF response)
self.tryingSomething.appendContentsOf(...)
If you want the print function to work correctly, you'll need to call it inside the completion block of the AF request, e.g.:
func getAllDataFromURL()
{
var i : Int = 0;
var urlDatas : [String] = allJsonDatas.getAllTours();
for(i = 0 ; i < allJsonDatas.getAllTours().count ; i += 1)
{
Alamofire.request(.GET, urlDatas[i]).responseJSON
{(response) -> Void in
if let arrivedData = response.result.value
{
self.tryingSomething.appendContentsOf(arrivedData["photoURLS"] as! [String]);
//
self.printTheJsonDatas();
// ^
}
}
}
}
You could also rewrite getAllDataFromURL() with a closure to be asynchronous (the more appropriate way to write this, I'd say):
func getAllDataFromURL(completion: (result: Array) -> Void) {
...
var returnArray = []
// ^ here this is a local variable, doesn't need to be global anymore
returnArray.appendContentsOf(arrivedData["photoURLS"] as! [String]);
completion(returnArray)
}

Related

Swift Function returning a value from asynchronous firebase call

I am writing a function that takes a groupchatID (String) and returns a list of Recipients ([String]) for that group chat. I am struggling with the asynchronous part of the function however. When I run the function, it correctly prints to the console the array of usernames I was looking for. Although, when I call the function and try to print the returned value, it is always an empty array because the function returns the array before the firebase call has finished. I am trying to use a callback, but I do not quite understand the syntax of it all. Please take a look and let me know what needs to be changed.
The Function:
func GetRecipientsFor(GroupChatID :String , completion: #escaping ([String]) -> ()) {
var returnArray: [String] = [""]
rootRef.child("chatMembers").child(GroupChatID).observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children.allObjects {
var append = child as! FIRDataSnapshot
returnArray.append((append.key as String))
print("Return Array Currently Contains: \(returnArray)")
//The above printout works properly and when the for loop finishes, the array is exactly as I want it
}
completion(returnArray)
//BUT, this portion returns an empty array
})
}
How I call the function:
GetRecipientsFor(GroupChatID: gchatID) { (result) -> () in
print(result)
}
NEW Function Call
var recipients : [String] = [""]
DispatchQueue.main.async {
GetRecipientsFor(GroupChatID: gchatID) { result in
print(result) //PRINTS CORRECTLY!!!
recipients = result
}
}
print(recipients) //PRINTS A BLANK ARRAY
The problem with
var recipients : [String] = [""]
DispatchQueue.main.async {
GetRecipientsFor(GroupChatID: gchatID) { result in
print(result)
recipients = result
}
}
print(recipients) // Completes before recipients = result
is that the last line is happening before the async call.
To explain futher print(recipients) happens before recipients = result. All logic using recipients needs to happen within that completion block. All you need to do is
func getRecipients(completion: #escaping ([String]) -> ()) {
var recipients : [String] = [""]
DispatchQueue.main.async {
GetRecipientsFor(GroupChatID: gchatID) { result in
print(result)
completion(result)
}
}
}
if you want to have further logic included you can call a function within the completion i.e. handleResults(result). I think it would be very beneficial to read more about closures/completion blocks/and async calls.
You also can simplify that and use the firebase observer async task adding other param to your function like this:
//controller is where you need to get the result
func GetRecipientsFor(GroupChatID :String , controller: UIViewController){
rootRef.observeSingleEvent(of: .value) { (snapshot) in
//here you haver your snapshot. do the stuff and
controller.setDataForRecipe(dataFromYourSnapshot)
}
}
And in your controller:
public func setDataForRecipe (arrayIngredients: [String]){
//whatever you want. example:
self.data = arrayIngredients
self.tableView.reloadData()
}

Executing a ViewModel function before setting variables in ViewController

I have an issue with code order in a ItemsViewController.swift
When I run my code it starts the for items loop before my api returns the values for the items. This is done in the line: self.viewModel/getItemsTwo... Therefore it thinks that items is nil by the time the loop starts, so it errors with:
fatal error: unexpectedly found nil while unwrapping an Optional value
How can I start the loop only after items has been filled by the api call/function call?
class ItemsViewController: UIViewController {
private let viewModel : ItemsViewModel = ItemsViewModel()
override func viewDidLoad() {
super.viewDidLoad()
self.viewModel.getItemsTwo(self.viewModel.getCurrentUser())
var items = self.viewModel.items
for item in items! {
print(item)
}
}
...
The getItemsTwo function in the viewModel sets the viewModel.items variable when it is called
EDIT 1
ItemsViewModel.swift
...
var items : JSON?
...
func getItemsTwo(user: MYUser) {
let user_id = user.getUserId()
let url = String(format:"users/%#/items", user_id)
self.get(url).responseJSON { (response) -> Void in
let dataExample = response.data
var newdata = JSON(data: dataExample!)
self.items = newdata
}
}
...
EDIT 2
I am trying to do this:
just change it in the ViewController to:
var items = self.viewModel.getItemsTwo(self.viewModel.getCurrentUser())
and the ViewModel to:
func getItemsTwo(user: MYUser) -> JSON {
let user_id = user.getUserId()
let url = String(format:"users/%#/items", user_id)
self.get(url).responseJSON { (response) -> Void in
let dataExample = response.data
var newdata = JSON(data: dataExample!)
self.items = newdata
}
return self.items
}
But the return statement still errors as if self.items in nil.
Maybe you could expand your getItemsTwo method to take a callback closure, something like:
func getItemsTwo(user: MYUser, callback: (items: [JSON])-> Void)
Meaning that you have a parameter called callback which is a closure function that returns Void and takes an array of JSON items as an input parameter.
Once you have added newdata to self.items you could call your callback closure like so:
func getItemsTwo(user: MYUser, callback: (items: [JSON])-> Void) {
let user_id = user.getUserId()
let url = String(format:"users/%#/items", user_id)
self.get(url).responseJSON { (response) -> Void in
let dataExample = response.data
var newdata = JSON(data: dataExample!)
self.items = new data
//Items are now populated, call callback
callback(items: self.items)
}
}
And then, in your ItemsViewController you could say:
override func viewDidLoad() {
super.viewDidLoad()
self.viewModel.getItemsTwo(self.viewModel.getCurrentUser()) { items in
for item in items {
print(item)
}
}
}
Notice that if you add a closure as the last parameter you can use a so called "Trailing Closure" and place it "outside" or "after" your function as described in this chapter of "The Swift Programming Language".
Hope that helps you (I haven't checked in a compiler so you might get some errors, but then well look at them OK :)).

Cannot change the value of a global variable

I just wrote some Swift code for accessing Riot API, with Alamofire and SwiftyJSON.
I wrote a function func getIDbyName(SummName: String) -> String to get the summoner id.
As you can see from the code below, I am assigning the id to self.SummID.
After executing the function, I am able to println the correct id, for example "1234567". However, the return self.SummID returns "0", the same as assigned in the beginning.
I tried to mess with the code, but I simply cannot get the correct value of self.SummID outside of the Alamofire.request closure. It always remain "0" anywhere outside.
I think it has something to do with the scope of the variable. Does anyone know what is going on here?
import Foundation
import Alamofire
import SwiftyJSON
class SummInfo {
var SummName = "ThreeSmokingGuns"
var SummID = "0"
var SummChamp = "akali"
var SummS1 = "flash"
var SummS2 = "ignite"
var SummRank = "Unranked"
var SummWR = "-" //summoner's winrate
let api_key = "key"
let URLinsert = "?api_key="
init(SummName: String, SummChamp: String, SummS1: String, SummS2: String, SummRank: String, SummWR: String) {
self.SummName = SummName
self.SummChamp = SummChamp
self.SummS1 = SummS1
self.SummS2 = SummS2
self.SummRank = SummRank
self.SummWR = SummWR
}
init(SummName: String) {
self.SummName = SummName
}
func getIDbyName(SummName: String) -> String
{
let SummURL = "https://na.api.pvp.net/api/lol/na/v1.4/summoner/by-name/"
var fullURL = "\(SummURL)\(SummName)\(URLinsert)\(api_key)"
Alamofire.request(.GET, fullURL)
.responseJSON { (request, response, data, error) in
if let anError = error
{
// got an error in getting the data, need to handle it
println("error calling GET on /posts/1")
println(error)
}
else if let data: AnyObject = data // hate this but responseJSON gives us AnyObject? while JSON() expects AnyObject
// JSON(data!) will crash if we get back empty data, so we keep the one ugly unwrapping line
{
// handle the results as JSON, without a bunch of nested if loops
let post = JSON(data)
self.tempJ = post
var key = post.dictionaryValue.keys.array //not necessary
var key2 = post[SummName.lowercaseString].dictionaryValue.keys.array
self.SummID = post[key[0],key2[2]].stringValue //[profileIconId, revisionDate, id, summonerLevel, name]
//test console output
println("The post is: \(post.description)")
println(SummName.lowercaseString)
println(key)
println(key2)
println(self.SummID)
}
}
return self.SummID
}
}
The reason is that
Alamofire.request(.GET, fullURL)
.responseJSON
is an asynchronous call. This means that the call to getIDbyName will immediately return without waiting the responseJSON to finish. This is the exact reason why you get a the '0' value for ID that you have set initially.
Having said that, the solution is to have a call back closure in the getIDbyName method:
func getIDbyName(SummName: String, callback: (id:String?) ->() ) -> ()
{
let SummURL = "https://na.api.pvp.net/api/lol/na/v1.4/summoner/by-name/"
var fullURL = "\(SummURL)\(SummName)\(URLinsert)\(api_key)"
Alamofire.request(.GET, fullURL)
.responseJSON { (request, response, data, error) in
if let anError = error
{
// got an error in getting the data, need to handle it
println("error calling GET on /posts/1")
println(error)
//Call back closure with nil value
callback(nil) //Can additionally think of passing actual error also here
}
else if let data: AnyObject = data // hate this but responseJSON gives us AnyObject? while JSON() expects AnyObject
// JSON(data!) will crash if we get back empty data, so we keep the one ugly unwrapping line
{
// handle the results as JSON, without a bunch of nested if loops
let post = JSON(data)
self.tempJ = post
var key = post.dictionaryValue.keys.array //not necessary
var key2 = post[SummName.lowercaseString].dictionaryValue.keys.array
self.SummID = post[key[0],key2[2]].stringValue //[profileIconId, revisionDate, id, summonerLevel, name]
//test console output
println("The post is: \(post.description)")
println(SummName.lowercaseString)
println(key)
println(key2)
println(self.SummID)
//Pass the actual ID got.
callback(self.SummID)
}
}
return self.SummID
}
And clients should always use this API to fetch the latest ID, and can refer the attribute directly to get whatever is cached so far in SummID member.
Here is how to call this method-
object.getIDbyName(sumName){ (idString :String) in
//Do whatever with the idString
}

Populate array of custom classes from two different network requests

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.

Empty return value from swift function containing closure

I created a function that should return a dictionary filled with data that are retrieved (using json, based on Ray Wenderlich tut) online. That code is in a closure. The problem is that an empty dictionary is returned first, and only afterwards it gets filled. Don't know if this is related to some delay in getting the remote data, but obviously I need the dictionary to be filled first before returning it. Here is the code.
func getStatusFromRemoteSource() -> [StatusModel] {
var statusUpdates = [StatusModel]()
println("statusUpdates after initialization: \(statusUpdates)") // 1
DataManager.getStatusDataWithSuccess { (statusData) -> Void in
let json = JSON(data: statusData)
if let jsonArray = json.array {
for jsonItem in jsonArray {
var statusVersion: String? = jsonItem["version"].string
var statusDescription: String? = jsonItem["message"].string
var statusCode: Int? = jsonItem["code"].string!.toInt()
var update = StatusModel(version: statusVersion, message: statusDescription, code: statusCode)
statusUpdates.append(update)
println("statusUpdates after appending update: \(statusUpdates)") // 3 (after other function call)
}
let item = 0
println("Version \(statusUpdates[item].version) has status \(statusUpdates[item].message)")
// println("Status code: \(statusUpdates[item].code)")
}
}
println("Status updates before return: \(statusUpdates)") // 2
return statusUpdates
}
So //1 prints first, then //2 (still empty) and then the other function (that calls this one) is called. Only then //3 is printed (correctly) with the content that should be retrieved.
How can I fill the statusUpdates dictionary before returning it?
You should use Closures in method to return statusUpdates as its Async method.
The empty statusUpdates will be returned immediately in your code but when using closures, you can wait till DataManager.getStatusDataWithSuccess is finished:
typealias RemoteStatusHandler = (status:[StatusModel]) -> Void
func getStatusFromRemoteSource(handler:RemoteStatusHandler){
var statusUpdates = [StatusModel]()
println("statusUpdates after initialization: \(statusUpdates)") // 1
DataManager.getStatusDataWithSuccess { (statusData) -> Void in
let json = JSON(data: statusData)
if let jsonArray = json.array {
for jsonItem in jsonArray {
var statusVersion: String? = jsonItem["version"].string
var statusDescription: String? = jsonItem["message"].string
var statusCode: Int? = jsonItem["code"].string!.toInt()
var update = StatusModel(version: statusVersion, message: statusDescription, code: statusCode)
statusUpdates.append(update)
println("statusUpdates after appending update: \(statusUpdates)") // 3 (after other function call)
}
let item = 0
println("Version \(statusUpdates[item].version) has status \(statusUpdates[item].message)")
// println("Status code: \(statusUpdates[item].code)")
}
handler(status: statusUpdates)
}
}
Then your function can be called like this:
getStatusFromRemoteSource { (status) -> Void in
//use status here, this function is void.
}

Resources