This question already has answers here:
How to return value from Alamofire
(5 answers)
Closed 5 years ago.
I am new with iOS programming. I am trying to make a piece of code in my function be synchronized, but it doesn't seem to work:
func fetchLocationsList(searchText:String)->Array<String> {
print ("searched text:\(searchText)")
let url = URL(string:"http://api.openweathermap.org/data/2.5/find?q=\(searchText)&type=like&sort=name&cnt=9&APPID=a33aa72")
//Using Alamofire to handle http requests
Alamofire.request(url!).responseJSON {response in
guard let jsonResponse = response.result.value as? [String:Any]
else { print ("error in json response")
return}
guard let list = jsonResponse["list"] as? NSArray else {return}
let lockQueue = DispatchQueue(label:"Et.My-Weather-App.queue1")
_ = lockQueue.sync{
for index in 0..<list.count {
print ("index is: \(index)")
guard let listElement = list[index] as? [String:Any] else {return}
let id = listElement["id"] as! Int
print ("id is: \(id)")
let cityName = listElement["name"] as! String
print ("cityName is: \(cityName)")
let sys = listElement["sys"] as! [String:Any]
let country = sys["country"] as! String
print ("country is: \(country)")
let element = "\(cityName), \(country), \(id)"
print ("\(element)")
self.resultsArray.append(element)
}
}
}
if self.resultsArray.count==0 {
print ("results array is also zero!")
}
return self.resultsArray
}
When I run it, I see that the line "results array is also zero!" is printed before the "for" loop fills the resultArray with elements, so the returned resultArray is always empty!
What am I doing wrong?
I suggest you do this as async tasks are a pain and this works quite well.
func fetchLocationsList(searchText:String, completion: #escaping (_ results:Array<String>)->()){
print ("searched text:\(searchText)")
let url = URL(string:"http://api.openweathermap.org/data/2.5/find?q=\(searchText)&type=like&sort=name&cnt=9&APPID=a33aa72")
//Using Alamofire to handle http requests
Alamofire.request(url!).responseJSON {response in
guard let jsonResponse = response.result.value as? [String:Any] else { print ("error in json response"); return}
guard let list = jsonResponse["list"] as? Array<Dictionary<String,Any>> else { return }
var array = Array<String>() // create an array to store results.
for item in list {
let id = item["id"] as! Int
let cityName = item["name"] as! String
let sys = item["sys"] as! Dictionary<String,Any>
let country = sys["country"] as! String
let element = "\(cityName), \(country), \(id)"
array.append(element) // add to that array.
}
completion(array) //send the array via the completions handler.
}
}
So in your viewDidLoad or whatever.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
fetchLocationsList(searchText: "Whatever this string is") { (results) in
self.resultsArray.append(contentsOf: results)
// Then do anything else you need to do after this function has completed within this closure.
}
}
Related
I do not know how to access the 'duration' value within my nested Optional NSSingleObjectArrayI that is constructed from a JSON response. How do I access the nested values within this data structure?
When I call print(firstRow["elements"]), I get the following output:
Optional(<__NSSingleObjectArrayI 0x60000120f920>(
{
distance = {
text = "1.8 km";
value = 1754;
};
duration = {
text = "5 mins";
value = 271;
};
"duration_in_traffic" = {
text = "4 mins";
value = 254;
};
status = OK;
}
))
I have tried string indexing (firstRow['elements']['duration']) but am getting errors.
fetchData { (dict, error) in
if let rows = dict?["rows"] as? [[String:Any]]{
if let firstRow = rows[0] as? [String:Any]{
print("firstRow is")
print(firstRow["elements"])
// Trying to access duration within firstRow['elements'] here
}
}
}
For reference, this is the fetchData function:
func fetchData(completion: #escaping ([String:Any]?, Error?) -> Void) {
let url = getRequestURL(origin: "test", destination: "test")!;
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let array = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String:Any]{
completion(array, nil)
}
} catch {
print(error)
completion(nil, error)
}
}
task.resume()
}
A sample HTTP JSON request is here:
https://maps.googleapis.com/maps/api/distancematrix/json?destinations=77%20Massachusetts%20Ave,%20Cambridge,%20MA&departure_time=now&key=AIzaSyB65D4XHv6PkqvWJ7C-cFvT1QHi9OkqGCE&origins=428%20Memorial%20Dr,%20Cambridge,%20MA
Seeing your output, your firstRow["elements"] is Optional, so you need to unwrap it. And it actually is an NSArray with a single element, where the only element is a Dictionary, with 4 entries -- "distance", "duration", "duration_in_traffic" and "status". You may need to cast the element to a Dictionary to access each entry.
You may use Optional binding with as?-casting for this purpose:
fetchData { (dict, error) in
if let rows = dict?["rows"] as? [[String: Any]] {
if let firstRow = rows.first {
print("firstRow is")
print(firstRow["elements"])
//Unwrap and cast `firstRow["elements"]`.
if let elements = firstRow["elements"] as? [[String: Any]] {
//The value for "duration" is a Dictionary, you need to cast it again.
if let duration = elements.first?["duration"] as? [String: Any] {
print(duration["text"] as? String)
print(duration["value"] as? Int)
}
}
}
}
}
Or too deeply nested ifs are hard to read, so someone would like it as:
fetchData { (dict, error) in
if
let rows = dict?["rows"] as? [[String: Any]],
let firstRow = rows.first,
let elements = firstRow["elements"] as? [[String: Any]],
let duration = elements.first?["duration"] as? [String: Any]
{
print(duration["text"] as? String)
print(duration["value"] as? Int)
}
}
Or using guard may be a better solution.
Or else, if you can show us the whole JSON text in a readable format, someone would show you how to use Codable, which is a modern way to work with JSON in Swift.
I am trying to convert items in an optional dictionary into individual strings so I can loop through them and convert them into URLs. But have been unable to do so.
Here is function which I use to fetch images from firebase which returns this optional dictionary which is also included below:
func fetchAllUsersImages() {
print("inside func")
self.ref.child("Posts").child(self.userID).child(self.postNum).observe(.childAdded, with: { snapshot in
print("inside closure")
// print(URL(string: snapshot.value as! String))
// let postSnap = snapshot.childSnapshot(forPath: self.postNum)
// let imageUrlSnap = postSnap.childSnapshot(forPath: "ImageUrl")
print(snapshot.value, "value")
// guard let allImages = imageUrlSnap.children.allObjects as? [DataSnapshot] else { return print("the code failed here")}
guard let allImages = snapshot.value as? [DataSnapshot] else { return print("the code failed here")}
// let snapshotVal = snapshot.value
// let snapshotValValue = snapshotVal as! String
// print(snapshotValValue, "snapshot as string value")
for image in allImages {
print(image, "image")
}
print(snapshot.key, "key")
print(snapshot.value, "value")
print(snapshot.children, "cjildren")
print(allImages)
print()
})
}
Output of snapshot.value:
Optional({
image1 = "https://firebasestorage.googleapis.com/v0/b/base.appspot.com/o/ijzAnEdyKNbhPsQVH6a8mOa1QpN2%2Fpost1%2Fimage1?alt=media&token=c2f396fd-717d-4192-909a-db390dd23143";
image2 = "https://firebasestorage.googleapis.com/v0/b/atabase.appspot.com/o/ijzAnEdyKNbhPsQVH6a8mOa1QpN2%2Fpost1%2Fimage2?alt=media&token=359b8527-f598-4f9a-934e-079cee21fd15";
})
Based on the answer provided I did the followoing:
func fetchAllUsersImages() {
print("inside func")
self.ref.child("Posts").child(self.userID).child(self.postNum).observe(.childAdded, with: { snapshot in //error here
var images: [URL] = []
if let snapShotValue = snapshot.value as? [String: String] {
for (_, value) in snapShotValue {
if let imageURL = URL(string: value) {
print(imageURL, "image url here")
let imageAsData = try Data(contentsOf: imageURL)
let image = UIImage(data: imageAsData)
let ImageObject = Image()
ImageObject.image = image
self.arrayOfImgObj.append(ImageObject)
self.tableView.reloadData()
}
}
}
})
}
However on the 3rd line I get
Unable to infer closure type in the current context
Edit:
To fix this error put the code, at the deepest part of the code, in a do block amd include a catch block also. This will fix the error.
Well first you need to check if the optional Dictionary exists then loop the dictionary for each key-value pair. Here is a way to do it:
var imageURLs: [URL] = []
if let snapShotValue = snapshot.value as? [String: String] { // Cast optional dictionary to a Dictionary of String keys and String values
// Cast would fail if snapshot.value is nil or has a different Dictionary setup.
for (key, value) in snapShotValue { // you can change key to _ since we are not using it
if let imageURL = URL(string: value) { // Get URL value from string
imageURLs.append(imageURL) // Add new URL to parsed URLs
}
}
}
So once the process is finished you'll have the images in imageURLs variable.
I want to parse JSON in UICollectionviewCell. I have a collectionViewController with two UICollectionviewCell. In collectionViewController first cell made to background scrolling and in the second I want to parse JSON. There is no error in the code, this is my JSON code.
var oCategoryFilter: CategoryFilter? {
didSet {
if let name = oCategoryFilter?.totalItem {
totalItemLabel.text = name
}
appsCollectionView.reloadData()
}
}
var arrProduct: [Product]?
func getPropductListByCategory(){
let category_id:String;
category_id = "21"
let url = URL(string: UtilityController.BASE_URL+"/products/"+category_id)
URLSession.shared.dataTask(with:url!) { (urlContent, response, error) in
if error != nil {
print(error)
}
else {
do {
let json = try JSONSerialization.jsonObject(with: urlContent!) as! [String:Any]
print(json)
let items = json["categories"] as? [[String: Any]] ?? []
items.forEach { item in
let oProduct = Product()
//oProduct.id = item["id"] as? String
oProduct.image = item["image"] as? String
oProduct.name = item["name"] as? String
oProduct.ar_name = item["ar_name"] as? String
//oProduct.description = item["description"] as? String
oProduct.ar_description = item["ar_description"] as? String
oProduct.price = item["price"] as? String
oProduct.quantity = item["quantity"] as? String
oProduct.is_featured = item["is_featured"] as? String
oProduct.seller_id = item["seller_id"] as? String
oProduct.payment_required = item["payment_required"] as? String
oProduct.is_editors_choice = item["is_editors_choice"] as? String
oProduct.created_at = item["created_at"] as? String
oProduct.updated_at = item["updated_at"] as? String
self.arrProduct?.append(oProduct)
}
print(url)
} catch let error as NSError {
print(error)
}
}
DispatchQueue.main.async(execute: {
self.appsCollectionView.reloadData()
})
}.resume()
}
When are you calling your functions ? You should call the method in the CollectionView, when it is loading every cell, but doing that is really bad, because each time you will scroll or reload your CollectionView it will parse again.
You should parse in a special class, call by the collection view and this last send the parse object to the cell.
Hi I'm trying to get data from a certain JSON API. I can gat a snapshot of all values from the API, which is shown below. But I can't manage to put a specifiek row in a variable. This is the JSON form which I get. I want to print the "Description" value.Can someone help me with this?
And Hier is my code:
func apiRequest() {
let config = URLSessionConfiguration.default
let username = "F44C3FC2-91AF-5FB2-8B3F-70397C0D447D"
let password = "G23#rE9t1#"
let loginString = String(format: "%#:%#", username, password)
let userPasswordData = loginString.data(using: String.Encoding.utf8)
let base64EncodedCredential = userPasswordData?.base64EncodedString()
let authString = "Basic " + (base64EncodedCredential)!
print(authString)
config.httpAdditionalHeaders = ["Authorization" : authString]
let session = URLSession(configuration: config)
var running = false
let url = NSURL(string: "https://start.jamespro.nl/v4/api/json/projects/?limit=10")
let task = session.dataTask(with: url! as URL) {
( data, response, error) in
if let taskHeader = response as? HTTPURLResponse {
print(taskHeader.statusCode)
}
if error != nil {
print("There is an error!!!")
print(error)
} else {
if let content = data {
do {
let array = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
print(array)
if let items = array["items"] {
if let description = items["Description"] as? [[String:Any]]{
print(description as Any)
}
}
}
catch {
print("Error: Could not get any data")
}
}
}
running = false
}
running = true
task.resume()
while running {
print("waiting...")
sleep(1)
}
}
First of all the array is not an array and not AnyObject, it's a dictionary which is [String:Any] in Swift 3.
let dictionary = try JSONSerialization.jsonObject(with: content) as! [String:Any]
print(dictionary)
I don't know why all tutorials suggest .mutableContainers as option. That might be useful in Objective-C but is completely meaningless in Swift. Omit the parameter.
The object for key itemsis an array of dictionaries (again, the unspecified JSON type in Swift 3 is Any). Use a repeat loop to get all description values and you have to downcast all values of a dictionary from Any to the expected type.
if let items = dictionary["items"] as? [[String:Any]] {
for item in items {
if let description = item["Description"] as? String {
print(description)
}
}
}
Looks like items is an array that needs to be looped through. Here is some sample code, but I want to warn you that this code is not tested for your data.
if let items = array["items"] as? [[String: AnyObject]] {
for item in items {
if let description = item["Description"] as? String{
print("Description: \(description)")
}
}
}
This code above, or some variation of it, should get you on the right track.
use the SwiftyJSON and it would be as easy as json["items"][i].arrayValue as return and array with items Values or json["items"][i]["description"].stringValue to get a string from a row
I'm looking to try and reference all "titles" within this json (link here) in xcode 8. The issue is there's an object and array that need to be referenced (i believe) before I can pull the title data, and I'm not sure how to do that.
So far this is what i've got:
func fetchFeed(){
let urlRequest = URLRequest(url: URL(string: "http://itunes.apple.com/us/rss/topalbums/limit=10/json")!)
let task = URLSession.shared.dataTask(with: urlRequest) { (data,response,error) in
if error != nil {
print(error)
return
}
self.artists = [Artist]()
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String : AnyObject]
if let feedFromJson = json["feed"]?["entry"] as? [[String : AnyObject]] {
for feedFromJson in feedsFromJson {
let feed = Feed()
if let entry = feedFromJson["entry"] as? String, let author = feedFromJson["domain"] as? String {
feed.entry = entry
article.headline = title
}
self.articles?.append(article)
}
}
DispatchQueue.main.async {
self.tableview.reloadData()
And thank you for your help in advance!
I'm working hard to try to understand what you need. If you want to get an Article array where the headline is the title label for the entry, here is how I cheated it out.
func articles(from json: Any?) -> [Article] {
guard let json = json as? NSDictionary, let entries = json.value(forKeyPath: "feed.entry") as? [NSDictionary] else {
return []
}
return entries.flatMap { entry in
guard let title = entry.value(forKeyPath: "title.label") as? String else {
return nil
}
var article = Article()
article.headline = title
return article
}
}
you call it as such
self.articles = articles(from: json)
NSDictionary has the method value(forKeyPath:) that is near magic. Calling json.value(forKeyPath: "feed.entry") returns an array of dictionaries. Each dictionary is an "entry" object in the json. Next, I map each entry to call entry.value(forKeyPath: "title.label") which returns a string.
If this is something more than a quick solution, then I would consider adding SwiftyJSON to your project.
func articles(from json: Any?) -> [Article] {
return JSON(json)["feed"]["entry"].arrayValue.flatMap { entry in
guard let title = entry["title"]["label"].string else {
return nil
}
var article = Article()
article.headline = title
return article
}
}
There is two kinds of titles.
the "feed" and the "entry".
if let entry = feedFromJson["entry"] as? String, let author = feedFromJson["domain"] as? String {
The practice of iOS is not this.
feedFromJson["entry"] is nil ,not a string . I guess you try to get the "feed" title.
if let entry = (json["feed"] as Dictionary)?["title"]
To get the "entry" title. Just traverse the array, and get the title.
let titleDict = feedFromJson["title"] as? Dictionary
let title = titleDict["title"] as? String
article.headline = title
Better to know the structure of the JSON data.
It's too quick.
if let feedFromJson = json["feed"]?["entry"] as? [[String :
AnyObject]] {
You should step by step.
if let feedFromJson = (json["feed"] as Dictionary)?["entry"] as? [[String : AnyObject]] {