Guard statements for optionals are nil in swift - json

I'm parsing json data using SwiftyJson from a weather API. And i have a list of optional data that i added it to a single guard statement to make the code look simpler and more effective. But unfortunately in the list of optionals i sometimes have nil values from the parsed API data, so the statement goes to else and returns nothing. Do i have to put guard else statements to every optional or there is a way to continue and return the found non nil values in the else statement?
Here is my code:
dispatch_async(dispatch_get_main_queue(), {
let jsonContent = JSON(data: data!)
guard let cTemp = jsonContent["currently"]["temperature"].double,
let cFeelsLike = jsonContent["currently"]["apparentTemperature"].double,
let cHumidity = jsonContent["currently"]["humidity"].double,
let cDewPoint = jsonContent["currently"]["dewPoint"].double,
let cPressure = jsonContent["currently"]["pressure"].double,
let cVisibility = jsonContent["currently"]["visibility"].double,
let cWindSpeed = jsonContent["currently"]["windSpeed"].double,
let cWindDirection = jsonContent["currently"]["windBearing"].double,
let cRainChance = jsonContent["currently"]["precipProbability"].double,
let cIconString = jsonContent["currently"]["icon"].string,
let cSummary = jsonContent["currently"]["summary"].string,
let cDailySummary = jsonContent["daily"]["summary"].string
else{
self.messageFrame.removeFromSuperview()
return
}
Here is the code after parsing the data that changes the labels on the storyboard.
if self.segmentedControl.selectedSegmentIndex == 0 {
UIApplication.sharedApplication().applicationIconBadgeNumber = Int(round(cTemp))
self.tempLabel.text = String(Int(round(cTemp))) + "˚"
self.humidityLabel.text = String(Int(round(cHumidity*100))) + "%"
self.pressureLabel.text = String(Int(round(cPressure))) + NSLocalizedString(" mBar", comment: "milli Bar")
self.windSpeedLabel.text = String(Int(round(cWindSpeed))) + NSLocalizedString(" Km/h", comment: "Kilo fe El sa3a")
self.realFeelLabel.text = String(Int(round(cFeelsLike))) + "˚"
self.windDirectionLabel.text = self.windDirectionNotation(cWindDirection)
self.rainChanceLabel.text = String(Int(round(cRainChance * 100))) + "%"
// self.visibilityLabel.text = String(Int(round(cVisibility))) + NSLocalizedString(" Km", comment: "Km")
self.descriptionLabel.text = cSummary
self.descriptionMoreLabel.text = cDailySummary
self.bgImage.image = self.bgPicker(cIconString) //Change BG according to currently weather conditions.
} else {
self.tempLabel.text = String(Int(round(cTemp))) + "˚"
self.humidityLabel.text = String(Int(round(cHumidity*100))) + "%"
self.pressureLabel.text = String(Int(round(cPressure))) + NSLocalizedString(" mBar", comment: "milli Bar")
self.windSpeedLabel.text = String(Int(round(cWindSpeed))) + NSLocalizedString(" mph", comment: "meel fee el sa3a")
self.realFeelLabel.text = String(Int(round(cFeelsLike))) + "˚"
self.windDirectionLabel.text = self.windDirectionNotation(cWindDirection)
self.rainChanceLabel.text = String(Int(round(cRainChance * 100))) + "%"
// self.visibilityLabel.text = String(Int(round(cVisibility))) + NSLocalizedString(" mi", comment: "meel")
self.descriptionLabel.text = cSummary
self.descriptionMoreLabel.text = cDailySummary
self.bgImage.image = self.bgPicker(cIconString) //Change BG according to currently weather conditions.
}

It is legal to set a label's text to nil or an Optional string. So for each item, use Optional chaining to unwrap it and set the corresponding label's text.
Unfortunately I don't know SwiftyJSON, but here's how you would do it if this were simply a Dictionary:
// here is some test data
let content = ["currently":["temperature":"21"]]
let lab = UILabel()
// this is what you would do
lab.text = (content["currently"] as? NSDictionary)?["temperature"] as? String
The point is that last line. If we get nil, we set the label's text to nil and no harm done. If we get our string, we set the label's text to an Optional wrapping that string and no harm done.

Related

Unable to use SwiftyJSON to access JSON data

I'm trying to access some JSON data but I can't access that using swiftyJSON. I'm getting JSON response back so I'm acquiring it using alamofire. Here's the JSON:
{"groupID":"6","groupName":"Test","teacher":"teacher1
","teacherID":"13","Locations":
[{"locationID":"5","locationName":"field"},
{"locationID":"6","locationName":"34th"}],"Error":""}
I'm using print statements to debug what's wrong. Here's the code that I'm trying to use:
let json = JSON(response.result.value ?? "error")
//let jsonError = json["Error"]
print("=================<JSON RESPONSE>=================");
print(json)
print("=================</JSON RESPONSE/>=================");
self.groupID = json["groupID"].stringValue
self.groupName = json["groupName"].stringValue
self.teacherID = json["teacherID"].stringValue
let locjson = json["Locations"]
print("Entering LocJSON Loop")
print("=================<LOCJSON >=================");
print("GNAME:" + self.groupID)
print("TID: " + json["teacherID"].stringValue)
print("Locjson.stringalue: " + locjson.stringValue)
//print("LocationJSON" + json["Locations"]);
print("=================</LOCJSON/>=================");
for (key, object) in locjson {
print("In LocJSON Loop")
let locationIDVar: Int? = Int(key)
self.locations[locationIDVar!].locationID = locationIDVar!
self.locations[locationIDVar!].locationName = object.stringValue
print(self.locations[locationIDVar!].locationName)
print(object);
}
Here's the output from console that corresponds to the print statements.
=================<JSON RESPONSE>=================
{"groupID":"6","groupName":"Test","teacher":"Teacher1"
,"teacherID":"13","Locations":
[{"locationID":"5","locationName":"field"},
{"locationID":"6","locationName":"34th"}],"Error":""}
=================</JSON RESPONSE/>=================
Entering LocJSON Loop
=================<LOCJSON >=================
GNAME:
TID:
Locjson.stringalue:
=================</LOCJSON/>=================
Also, how do I get to the multiple locations that are inside 'Locations'?
The value for key Locations is an array containing dictionaries.
let locjson = json["Locations"].arrayValue
for location in locjson {
let locID = location["locationID"].stringValue
let locName = location["locationName"].stringValue
print(locID, locName)
}
In Swift 4 I'd prefer Decodable over SwiftyJSON because you can directly decode into structs.

Clear data in an array while requesting new json response (fatal error: Index out of range swift 3)

I have json request which show a list of cars. I used timer to refresh the request as well as the list every 10 seconds. The problem is that the data keep adding up to the array and make my application crashes. How can I clear the data before appending new data? What should I do?
let list = listdevices[indexPath.row] // error
if list.statusxe == "run" {
cell?.devnameLabel?.text = list.devname
cell?.addressLabel?.text = list.address
cell?.statusxeLabel?.textColor = UIColor(red: 1/255, green: 117/255, blue: 0/255, alpha: 1)
cell?.statusxeLabel?.text = "Đang chạy"
cell?.speedLabel?.text = "\(list.speed) km/h"
}
else if list.statusxe == "stop"{
cell?.devnameLabel?.text = list.devname
cell?.addressLabel?.text = list.address
cell?.statusxeLabel?.textColor = UIColor(red: 230/255, green: 6/255, blue: 6/255, alpha: 1)
cell?.statusxeLabel?.text = "Đang dừng"
cell?.speedLabel?.text = ""
}
else if list.statusxe == "expired"{
cell?.devnameLabel?.text = list.devname
cell?.addressLabel?.text = list.address
cell?.statusxeLabel?.textColor = UIColor.black
cell?.speedLabel?.textColor = UIColor.black
cell?.statusxeLabel.text = " "
cell?.speedLabel?.text = "hết hạn dịch vụ"
}
else if list.statusxe == "lost_gprs"{
cell?.devnameLabel?.text = list.devname
cell?.addressLabel?.text = list.address
cell?.statusxeLabel?.textColor = UIColor.red
cell?.statusxeLabel?.text = "Mất GPRS"
cell?.speedLabel?.text = ""
}
cell?.accessoryType = UITableViewCellAccessoryType.disclosureIndicator
return cell!
}
I have json request which show a list of cars. I used timer to refresh the request as well as the list every 10 seconds. The problem is that the data keep adding up to the array and make my application crashes. How can I clear the data before appending new data? What should I do?
let url = "http://api.vnetgps.com:8000/tracking"
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "GET"
request.setValue(token , forHTTPHeaderField: "token")
request.setValue(username, forHTTPHeaderField: "username")
request.setValue("-1", forHTTPHeaderField: "devid")
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration, delegate: nil, delegateQueue: OperationQueue.main)
let task = session.dataTask(with: request, completionHandler: {(data, response, error) in
if (error != nil ) {
print("Error")
}
else {
self.listdevices.removeAll()
if let json = try? JSONSerialization.jsonObject(with: data!, options: []) as? [String: Any],
let items = json?["units"] as? [[String: Any]] {
for item in items {
var lat = item["latitude"] as? String
UserDefaults.standard.set(lat, forKey: "latitude")
var long = item["longitude"] as? String
UserDefaults.standard.set(long, forKey: "longitude")
// print("long", long)
var devid = item["devid"] as? String
UserDefaults.standard.set(devid, forKey: "devid")
var devname = item["devname"] as? String
UserDefaults.standard.set(devname, forKey: "devname")
var speed = item["speed"] as? String
UserDefaults.standard.set(speed, forKey: "speed")
var statustt = item["status"] as? String
UserDefaults.standard.set(statustt, forKey: "statusxe")
var drivername = item["drivername"] as? String
UserDefaults.standard.set(drivername, forKey: "drivername")
var address = item["address"] as? String
UserDefaults.standard.set(address, forKey: "address")
var direction = item["direction"] as? String
self.listdevices.append(Listdevices(statusxe: statustt! , speed: speed!, devid: devid!, devname: devname!, address: address!, latitude: lat!, longitude: long!, drivername: drivername!, direction: direction!))
// print("list",self.listdevices)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
})
task.resume()
}
The problem is probably that you empty the array while it is being used by the tableview.
Instead use map to replace the old array contents with the new contents. That way you don't need to empty the array first.
Something like:
self.listDevices = items.map { Listdevices($0) }
And then implement an initialiser for ListDevices like this:
init(device: [String: Any]) { ... }
A couple of unsolicited code review comments:
All the writes to UserDefaults are pointless since each iteration just overwrites the previous one, so you should just remove the whole loop.
Make statusxe into an enum StatusXE
Replace the long conditional with a switch based on the new StatusXE enum
Be careful with naming. The variable list is not a list, so don't call it that, instead maybe call it device. The variable listdevices should just be called devices or if you insist on using the word list, it should be deviceList. Also remember proper camelCasing.
Avoid force unwrapping. Possibly use the nil coalescing operator to provide default values for device properties that are null.
Avoid unnecessarily duplicating code. In the long conditional, the first two lines of code are repeated in every case. Just move those two lines out of the conditional.
I think it could be due to the async nature of your code.
try avoid self.listdevices.append and use something like :
// declare listdevices with var instead of let
let temp = [Listdevices]()
for item in items {
...
temp.append(Listdevices(statusxe: statustt! , speed: speed!, devid: devid!, devname: devname!, address: address!, latitude: lat!, longitude: long!, drivername: drivername!, direction: direction!))
}
self.listdevices = temp

Create new Realm List and add items

I make a API request with Alamofire , I then get a response in JSON format, I then parse the JSON into a NSDictionary to get to the data I want.
The data I get is four Arrays of different items.
I want to the create a new List in Realm to save these items in.
Here are my Realm Object Classes :
class ListOfDefinitions: Object {
let listOfItems = List<Item>()
}
and
class Item: Object {
dynamic var AverageCost = Int()
dynamic var Barcode = ""
dynamic var Description = ""
dynamic var InternalUnique = Int()
dynamic var LastCost = Int()
dynamic var LimitToMainRegionUnique = Int()
dynamic var Notes = ""
dynamic var StockCategoryUnique = Int()
dynamic var StockCode = ""
dynamic var StockGroupUnique = Int()
dynamic var UnitDescriptor = ""
}
Here is my code on how I handle the JSON response and where I want to save the data in my code.
var newItemInStockList : ListOfDefinitions! // declared in the class
let newItemInStock = Item()
.responseJSON { response in
switch response.result {
case .Success(let JSON):
// print("Success with JSON: \(JSON)")
let response = JSON as! NSDictionary
let responseParams = response.objectForKey("ResponseParameters") as! NSDictionary
//print(responseParams)
//let stockItemGroupList = responseParams.objectForKey("StockItemGroupList")
let stockItemList = responseParams.objectForKey("StockItemList") as! NSDictionary
//print(stockItemList)
let listofDefinitions = stockItemList.objectForKey("ListofDefinitions") as! NSArray
print(listofDefinitions.count)
for defJson in listofDefinitions {
print(defJson["Description"])
someString = defJson["Description"] as! String
print(someString)
// Because there are 4 arrays of items this for loop will be red 4 times, each time it is red I want o create a new list and add the items to the list
// This comment area is where I tried to create a new list and then .append the items in it, but it doesn't work.
// let newOne = ListOfDefinitions()
//
//
// try! realm.write{
//
// realm.add(newOne)
// }
// self.newItemInStock.AverageCost = defJson["AverageCost"] as! Int
// self.newItemInStock.Barcode = defJson["Barcode"] as! String
// self.newItemInStock.Description = defJson["Description"] as! String
// self.newItemInStock.InternalUnique = defJson["InternalUnique"] as! Int
// self.newItemInStock.LastCost = defJson["LastCost"] as! Int
// self.newItemInStock.LimitToMainRegionUnique = defJson["LimitToMainRegionUnique"] as! Int
// self.newItemInStock.Notes = defJson["Notes"] as! String
// self.newItemInStock.StockCategoryUnique = defJson["StockCategoryUnique"] as! Int
// self.newItemInStock.StockCode = defJson["StockCode"] as! String
// self.newItemInStock.StockGroupUnique = defJson["StockGroupUnique"] as! Int
// self.newItemInStock.UnitDescriptor = defJson["UnitDescriptor"] as! String
//
// try! realm.write{
//
// self.newItemInStockList.listOfItems.append(self.newItemInStock)
// }
}
case .Failure(let error):
print("Request failed with error: \(error)")
}
And here is what I get when I print the 4 Arrays
Looking at your sample code, I think the main issue happening here is that you're re-using the same self.newItemInStock instance for each object you're adding to the list.
It would be best to create a new Item object in the loop as you're going along and append that to the List object.
I recommend using a combination of AlamofireObjectMapper to handle all your JSON mapping (both ways) https://github.com/tristanhimmelman/AlamofireObjectMapper
and the ListTransform found in ObjectMapper+Realm https://github.com/Jakenberg/ObjectMapper-Realm
They're both available to be installed through cocoapods. Your code will look much cleaner and easier to maintain

Why is retrieving data from JSON taking a lot of time?

It takes at least 15-35 second for my iPhone to go through these loops. I learned about JSON mostly on StackOverflow and this is the way people do this. These arrays have 3 elements inside with some text and one small image per element
if let parseJSON = json{
let succes = parseJSON["data"]
let item = self.success["catalogue_products"] as! [[String: AnyObject]]
if item.isEmpty == false{
for i in item {
var categoryName = i["category_name"] as! String
if self.category == nil{
self.category = categoryName
self.categories.append(self.category)
self.categoryCount = 1
}
if self.category != categoryName{
self.categoryCount += 1
self.category = categoryName
self.categories.append(self.category)
}
var deep = i["products"] as! [[String: AnyObject]]
for i in deep{
var product = ProductCatalogue()
product.categoryName = categoryName
product.id = i["id"]
println(product.id)
product.name = i["name"]
product.imageUrl = i["image"]
product.value = i["value"]
var volumes = (i["volumes"] as? [AnyObject])!
var check = true
for i in volumes{
if check == true {
product.volumeMin = i
check = false
} else {
product.volumeMax = i
check = true
}
}
product.colors = i["colors"] as! [[String: AnyObject]]
for i in product.colors{
let temp: AnyObject? = i["code"]
product.colorCode.append(temp!)
let url2: AnyObject? = i["image"]
product.colorImageUrl.append(url2!)
}
let url = NSURL(string: String(stringInterpolationSegment: product.imageUrl))
let data = NSData(contentsOfURL: url!)
product.image = UIImage(data: data!)
if product.colorImageUrl.isEmpty == false {
for i in 0...(product.colorImageUrl.count - 1) {
let url1 = NSURL(string: String(stringInterpolationSegment: product.colorImageUrl[i]))
let data1 = NSData(contentsOfURL: url1!)
switch i {
case 0: product.color1 = UIImage(data: data1!)
case 1: product.color2 = UIImage(data: data1!)
case 2: product.color3 = UIImage(data: data1!)
case 3: product.color4 = UIImage(data: data1!)
case 4: product.color5 = UIImage(data: data1!)
case 5: product.color6 = UIImage(data: data1!)
case 6: product.color7 = UIImage(data: data1!)
default: println("")
}
}
}
self.array.append(product)
}
}
self.sortinOut()
self.loadScreen()
Its because you are making MANY HTTP requests. First in one for loop which is not a good idea at all and second time in the nested loop, which in my idea is a bad mistake. HTTP requests are not efficient and fast when dealing them like this.
If you are willing to get a bunch of data, make the JSON in your REST api and get it once.
If you don't have a REST api and you're simply getting images from the web, make it as efficient as possible by making the fewest requests possible. Also, try getting your images Asynchronously, by calling the GCD Async function.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// download image
});
Hope it helps

How can I use an if let statement to check if JSON data is present in multiple constants?

The foursquare api endpoint I'm hitting requires me to combine a photo prefix, size, and suffix together to create a usable image URL. I am attempting to do this in the "photoURL" constant which currently works.
How can I check to see if the data for all of the pieces of this photoURL is there (using if let) while combining the variables together to set venueImageView from URL using Haneke?
Here is my code:
func bindData() {
let ratingSignals = self.dog?["venue"]["ratingSignals"].stringValue
let photoURLPrefix = self.dog?["venue"]["featuredPhotos"]["items"][0]["prefix"].stringValue
let photoURLSuffix = self.dog?["venue"]["featuredPhotos"]["items"][0]["suffix"].stringValue
let photoURL = photoURLPrefix! + "original" + photoURLSuffix!
let venuePhotoURL = NSURL(string: photoURL)
println("photo url prefix is \(photoURLPrefix)")
println("photo url suffix is \(photoURLSuffix)")
println(photoURL)
self.openUntilLabel.text = self.dog?["venue"]["hours"]["status"].stringValue
self.addressLabel.text = self.dog?["venue"]["location"]["address"].stringValue
self.titleLabel.text = self.dog?["venue"]["name"].stringValue
self.ratingsLabel.text = "Based on \(ratingSignals) ratings"
self.ratingImageView.image = UIImage(named:"Score8-5")!
if let photoURL = photoURLPrefix! + "original" + photoURLSuffix!{
let url = NSURL(string: photoURL)
venueImageView.hnk_setImageFromURL(url!)
}
I commented out self.venueImageView.hnk_setImageFromURL(venuePhotoURL!) which currently works, but Im worried that if a request doesnt return an image it will crash the app. So I am trying to use an if let to check that the data exists, and then set the imageView inside of this statement.
The error I am getting:
"Bound value in a conditional binding must be of optional type"
Here is an image of the error:
I did not run your code but I think your code will crash even if you remove that if condition and if block completely in case of no data is returned.
You should validate your response like this;
let photoURLPrefix? = self.dog?["venue"]["featuredPhotos"]["items"][0]["prefix"] as String?
let photoURLSuffix? = self.dog?["venue"]["featuredPhotos"]["items"][0]["suffix"] as String?
if (photoURLPrefix == nil || photoURLSuffix == nil)
{
//Do not proceed
return;
}
I don't know if the way you are retrieving data from dog object, is working but the important thing is that optional sign to use.
Given that a GET request will always return a suffix if a prefix is present, I just tested against the prefix and didn't need to "combine" them.
Here is my solution: dog(above) is now "pin"
if let photoURLPrefix = self.pin?["venue"]["featuredPhotos"]["items"][0]["prefix"].stringValue {
let photoURLSuffix = self.pin?["venue"]["featuredPhotos"]["items"][0]["suffix"].stringValue
let photoURL = photoURLPrefix + "original" + photoURLSuffix!
let venuePhotoURL = NSURL(string: photoURL)
self.venueImageView.hnk_setImageFromURL(venuePhotoURL!)
println("photo url prefix is \(photoURLPrefix)")
println("photo url suffix is \(photoURLSuffix)")
println(photoURL)
}