Unable to change ui label in swift - ios

I am trying to make a weather app. the issue I am having is after I get the data parsed out I try to change the UILabel to equal the data that came back example.
if let weather = item["weather"] as? NSArray{
if let value = weather[0] as? NSDictionary{
if let description = value["description"] as? String{
print(description)
}
instead of printing the description I want to change the content of the UI Label like this
conditionlabel.text = description
but I get this error Reference to property 'Condition' in closure requires explicit 'self.' to make capture semantics explicit.
however, every time I try the self. in front it does not print my data and something else happens. Any help would be much appreciated as I am new to this

Create an optional variable in controller that has you UILabel:
var descriptionText: String?
Now when you parse data, assign value to this variable
if let description = value["description"] as? String{
descriptionText = description
}
After data is parsed assign this value to the label
self.conditionlabel.text = descriptionText

I think what you need to do here is use self.conditionlabel.text = description (that is, the use of self is for the UILabel rather than description - my guess is that you were using self.description which gives you a description of the view controller.)
EDIT:
To overcome the threading issue, wrap the code like this:
DispatchQueue.main.async { [weak self] in
self?.conditionlabel.text = description
}

Related

Check if JSON key is present

I have a function downloading JSON. It does it fine however in some cases a object may not contain a certain key. On that occasion it still trys to add a value to the array. I want to make it so if the key is not present then a value of nil is added to the array. Would appreciate it if someone could help. Thanks alot.
if let link = itemDict.value(forKey: "link") {
if link != nil {
self.linkArray.append(link as! String)
}
}
Just do not use the force unwrapping. Also your if let is wrong for what you want to achieve. Copy paste version:
let link = itemDict.value(forKey: "link") as? String
self.linkArray.append(link)

How to UITest UIProgressView?

Situation:
I want to UI-test my progress view and see if it's progress has increased.
What I've tried:
By using a breakpoint in my UI-test and using the po print(app.debugDescription), I can successfully see and access the UIProgressView element.
It's called:
Other, 0x6080003869a0, traits: 8589935104, {{16.0, 285.5}, {343.0, 32.0}}, identifier: 'progressView', label: 'Progress', value: 5%
I access it with let progressView = secondTableViewCell.otherElements["progressView"], which works, as .exists returns true.
Now, to check progress, the description above hints at the 'value' property, that is currently at 5%. However, when I try progressView.value as! String or Double or whichever, I cannot seem to get the property as it isn't NSCoder-compliant.
Question:
Is it possible to UI-test a UIProgressView and use it's progress value?
My final custom solution:
Using the value property (when casting to String) somehow does work now. As I wanted to check if progress increased over time, I strip the String to get the numeric progress in it to compare. Here is my solution I use now:
// Check if progress view increases over time
let progressView = secondTableViewCell.otherElements["progressView"]
XCTAssert(progressView.exists)
let progressViewValueString = progressView.value as! String
// Extract numeric value from '5%' string
let progressViewValue = progressViewValueString.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
// Let progress increase
sleep(3)
let newProgressViewValueString = progressView.value as! String
let newProgressViewValue = newProgressViewValueString.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
XCTAssert(newProgressViewValue > progressViewValue)
XCUIElement conforms to XCUIElementAttributes which gives you access to a value property of type Any?
The actual type of value at runtime depends on which kind of UI element you are querying. In case of a UIProgressView it is of type __NSCFString, which is a concrete subclass of NSString. Which means you can safely cast it to String:
This works (provided that you set your progress view's accessibility identifier to "progressView"):
func testProgressView() {
let app = XCUIApplication()
app.launch()
let progressView = app.progressIndicators["progressView"]
// alternative (if the above query does not work):
// let progressView = app.otherElements["progressView"]
let currentValue = progressView.value as? String
XCTAssertEqual(currentValue, "5 %")
}
Weirdly in some cases the progressIndicator query does not work. In that case you can try a otherElements query (see comment in above code)

Issues reading data from Firebase Database

Okay I am reading from a database and when I print the individual variables they print out correctly. However it seems like the data refuses to append to the array. Anyone know why? I can't figure it out at all.
let commuteBuilder = Commutes()
Database.database().reference().child("Users").child(user).child("Trips").observe(DataEventType.childAdded, with: { (snapshot) in
//print(snapshot)
if let dict = snapshot.value as? NSDictionary {
commuteBuilder.distance = dict["Distance"] as! Double
commuteBuilder.title = dict["TripName"] as! String
commuteBuilder.transportType = (dict["Transport"] as? String)!
}
commuteArray.append(commuteBuilder)
})
print("helper")
print(commuteArray.count)
return commuteArray
The data is correctly added to the array, just not at the time that you print the array's contents.
If you change the code like this, you can see this:
let commuteBuilder = Commutes()
Database.database().reference().child("Users").child(user).child("Trips").observe(DataEventType.childAdded, with: { (snapshot) in
if let dict = snapshot.value as? NSDictionary {
commuteBuilder.distance = dict["Distance"] as! Double
commuteBuilder.title = dict["TripName"] as! String
commuteBuilder.transportType = (dict["Transport"] as? String)!
}
commuteArray.append(commuteBuilder)
print("added one, now have \(commuteArray.count)")
})
print("returning \(commuteArray.count)")
return commuteArray
You'll see it print something like this:
returning 0
added one, now have 1
added one, now have 2
etc.
This is likely not the output you expected. But it is working as intended. Firebase loads data from its database asynchronously. Instead of blocking your code, it lets the thread continue (so the user can continue using the app) and instead calls back to the code block you passed to observe when new data is available.
This means that by the time this code returns the array it is still empty, but it later adds items as they come in. This means that you cannot return data from a function in the way you are trying.
I find it easiest to change my way of thinking about code. Instead of "First get the data, then print it", I frame it as "Start getting the data. When data comes back, print it".
In the code above, I did this by moving the code that prints the count into the callback block. Instead of doing this, you can also create your own callback, which is called a completion handler or closure in Swift. You can find examples in this article, this article, this question Callback function syntax in Swift or of course in Apple's documentation.

Error when getting image from weather api. iOS

This is from debug.
url String "https://openweathermap.org/img/w/Optional(\50n\).png"
The problem is in this line:
self.imgURL = "https://openweathermap.org/img/w/\(self.dodatek).png"
When I change (self.dodatek) for example to icon 50n it works and show me the icon.
When I start my weather app and write name of the city I want to have url like this, but for 50n it has to be my variable that is taken from json.
https://openweathermap.org/img/w/50n.png
Looks like the quick and dirty fix for your problem is unwrapping the optional dodatek(which is probably String?) like this
self.imgURL = "https://openweathermap.org/img/w/\(self.dodatek!).png"
The cleaner solution is definitely
guard let dodatek = self.dodatek else {
// handle that dodatek is nil
// or just return
return
}
self.imgURL = "https://openweathermap.org/img/w/\(dodatek).png"
Explanation
The problem is that your property dodatek can theoretically be nil when you declare it as String?
If you are sure that it can never be nil, declare it as
var dodatek: String // not String?
instead.
In case it can be nil, the guard let statement above should be used to either define a fallback value of an url that should be used (like a url to a generic weather icon maybe)

Unable to use AnyObject in Swift

I don't understand an inch of how the AnyObject type in Swift is supposed to work. I am loading a Plist file into a NSDictionary instance in my Swift code (not shown here). Later I try to use some of the values from the Plist file in a UIPickerView:
// Trying to extract the first fruit
let fruitKeys = fruits.allKeys
let fruitKey = fruitKeys[0]
// This is another NSDictionary
let fruit = fruits.objectForKey(fruitKey)
// Getting a property of a fruit
let nutritions = fruit.objectForKey("nutritions")
let nutritionKeys = nutritions.allKeys
However I am not able to get the keys of a fruit by calling nutritions.allKeys. I get Could not find member allKeys. However, which part of the documentation have I misunderstood? I read that you can call any objective c method on an instance of AnyObject. Why do I need to cast or to some other magic here?
Should I cast everything to their real types? Or should I use AnyObject??
In short, yes you would generally want to cast to your defined type, here's how I would write it:
let fruitKeys = fruits.allKeys as [String]
let fruitKey = fruitKeys[0]
let fruit = fruits[fruitKey] as NSDictionary
let nutritions = fruit["nutritions"] as NSDictionary
let nutritionKeys = nutritions.allKeys
As I already mentioned in my comment, a possible change to your code would be:
let fruitKeys = fruits.allKeys
let fruitKey = fruitKeys[0]
let fruit = fruits.objectForKey(fruitKey)
let nutritions = fruit.objectForKey("nutritions")
let nutritionKeys : AnyObject? = nutritions.allKeys
I omitted downcasting every value because I saw it as pointless, so you might encounter some warnings, but it will work.

Resources