Can't unwrap/cast in in Swift 3 - ios

I'm using the ios library Charts (https://github.com/danielgindi/Charts)
There's a delegate function declared chartValueSelected, which gives me back entry: ChartDataEntry.
So I get the entry, with it's data, declared as var data: AnyObject?
print(entry.data)
> Optional(Optional(<MyProject.Stop: 0x6000002c5cc0> (entity: Stop; id: 0xd00000000c980010 <x-coredata://5F54CCEC-11FB-42F1-BDFE-30F7F7E18614/Stop/p806> ; data: { ... })))
print(type(of: entry.data))
> Optional<AnyObject>
That's wierd? I assigned it a non optional? Well, that might be a bug in the library, but I should atleast be able to access it?
guard let e = stopEntry as? Stop else {
continue
}
yVal.append(BarChartDataEntry(value: e.duration!.doubleValue, xIndex: i, data: e))
Well, we're fine with the ?? double optional.. But what? Why cant we unwrap it?
if let hasObject = entry.data {
print("We have object: \(type(of: hasObject))")
> We have object: _SwiftValue
if let stopObject = hasObject as? Stop {
print("We have a stop object!!") // Doesnt come here
}
}
More things that doesnt work are:
if let s = entry.data, let b = s as? Stop {
// Not executed here either
}

Related

Cannot cast/decode data from server into Swift data model?

I need to cast the below response from my server as [UserResult] but I cannot get it to work??
What am I doing wrong?
func userSearch(keyword: String, completion: #escaping (Result<[UserResult], ResponseError>) -> Void ) {
socket.emit("userSearch", keyword)
socket.on("userFound") { ( data, ack) in
print(data) // prints below NSArray
if !data.isEmpty {
if let response = data as? [UserResult] {
print("USERS \(response)") // WILL NOT WORK?
completion(.success(response))
}
} else {
completion(.failure(.badRequest("No users found")))
}
}
}
Data from server
[<__NSArrayM 0x60000040e5b0>(
{
profileUrl = "www.address1.com";
username = chrissmith;
},
{
profileUrl = "www.address2.com";
username = johnsmith;
},
{
profileUrl = "www.address3.com";
username = alicesmith;
}
)
]
UserResult Model
struct UserResult: Decodable {
let username: String
let profileUrl: String
}
Well you are using Socket.IO library and specifically method
socket.on(clientEvent: .connect) {data, ack in
...
}
defined as
#discardableResult
open func on(clientEvent event: SocketClientEvent, callback: #escaping NormalCallback) -> UUID
using typealias:
public typealias NormalCallback = ([Any], SocketAckEmitter) -> ()
So basically at the and you are being returned data of type [Any] according to documentation.
Since you do not know what is inside your data it is better for you to unwrap objects in your array one by one (instead casting it directly to [UserResult]) and try to find out what Type there are by comparing to some set of known types as some of answers from this question suggest.
I would start with verifying the data structure with example code below , and only move on with casting to various type afterwards:
Lets assume example data1 is your data:
let dict1 = ["profileUrl":"www.address1.com","username":"chrissmith"]
let data1: NSArray = [dict1]
//printed data1:
// (
// {
// profileUrl = "www.address1.com";
// username = chrissmith;
// }
// )
if data1[0] as? [String:String] != nil {
print("We found out that first object is dictionary of [String:String]!")
}
else if data1[0] as? Dictionary<NSObject, AnyObject> != nil {
print("We found out that first object is dictionary of mixed values!")
} else {
print("We found out that first object has different data structure")
}
Hopefully this answer was at least a little bit helpfull, even though not providing direct easy solution for your problem.

Unable to infer closure type in the current context

On the 3rd line in the function below I get the following error:
Unable to infer closure type in the current context
How do I fix this?
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()
}
}
}
})
}
The reason why it is not inferring the closure type is because the try statement is not handled. This means that the closure expected to "catch" the error, but in your case, you forgot the do-try-catch rule.
Therefore you can try the following answer which will catch your errors:
do {
let imageAsData = try Data(contentsOf: imageURL)
let image = UIImage(data: imageAsData)
let ImageObject = Image()
ImageObject.image = image
self.arrayOfImgObj.append(ImageObject)
} catch {
print("imageURL was not able to be converted into data") // Assert or add an alert
}
You can then assert an error (for testing), or what I would personally do, is set up an alert.
This way the app wouldn't crash, but instead, notify the user. I find this very helpful when on the go and my device isn't plugged in - so I can see the error messages instead of a blank crash with no idea what happened.
This error can also happen if you have a non related compilation error in your closure body. For example, you may be trying to compare two or more non-boolean types.
extension Array where Element == Resistance {
init(_ points: [Point]) {
let peaks = points.beforeAndAfter { (before, current, after) -> Bool in
before < current && current > after
}
self = []
}
}
will produce Unable to infer closure type in the current context.
The correct code:
extension Array where Element == Resistance {
init(_ points: [Point]) {
let peaks = points.beforeAndAfter { (before, current, after) -> Bool in
before.value < current.value && current.value > after.value
}
self = []
}
}
In addition to ScottyBlades answer, I'd like to add two data points to the "experience". It looks like referencing a non-existent property using self inside the block is not handled nicely by the compiler.
Nice error inside the block:
// Setting a handler for an NWListener instance:
self.nwListener?.newConnectionHandler = { (_ connection: NWConnection) -> Void in
// Results in "Cannot find 'nonExistentProperty' in scope"
// in the line below:
guard let delegate = nonExistentProperty else { return }
}
Weird "Type of expression is ambiguous without more context" error: (note the new self in front of nonExistentProperty)
// Setting a handler for an NWListener instance:
// Results in "Type of expression is ambiguous without more context"
// at the equals sign below:
self.nwListener?.newConnectionHandler = { (_ connection: NWConnection) -> Void in
guard let delegate = self.nonExistentProperty else { return }
}

Using UpdateChildValue to Edit from Firebase

I am wanting to update the child values after editing inside the textfields.
At the moment I have this action:
#IBAction func updateAction(_ sender: Any) {
guard let itemNameText = itemName.text, let itemDateText = itemDate.text else { return }
guard itemNameText.characters.count > 0, itemDateText.characters.count > 0 else {
print("Complete all fields")
return
}
let uid = FIRAuth.auth()?.currentUser?.uid
let key = item.ref!.key
let itemList = Item(itemName: itemNameText, itemDate: itemDateText, uid: uid!)
let editItemRef = databaseRef.child("/usersList/\(key)")
editItemRef.updateChildValues(itemList.toAnyObject())
print("edited")
}
I was following this tutorial but he seems to use the username, and as I only have the email or uid (userID) as authentication I thought I'd use the uid.
This is my toAnyObject function inside my class:
func toAnyObject() -> [String: AnyObject] {
return ["itemName": itemName as AnyObject, "itemDate": itemDate as AnyObject, "userID": userID as AnyObject]
}
When I run the breakpoint it does show the edited value of the item however the update doesn't appear to be performing.
Just to be extra safe, try dropping the leading slash from your path:
databaseRef.child("usersList/\(key)")
…and try printing the Error returned by Firebase, if any:
editItemRef.updateChildValues(itemList.toAnyObject()) {
(error, _) in
if let error = error {
print("ERROR: \(error)")
} else {
print("SUCCESS")
}
Edit. We found out he was using the wrong database path. The right one is:
databaseRef.child("users").child(uid!).child("usersList/‌​\(key)")

Swift 3: What's the safest way to unwrap optional values coming from an array?

First, I initialize the variables to hold the stock data
var applePrice: String?
var googlePrice: String?
var twitterPrice: String?
var teslaPrice: String?
var samsungPrice: String?
var stockPrices = [String]()
I fetch current stock prices from YQL, and put those values into an array
func stockFetcher() {
Alamofire.request(stockUrl).responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let json = JSON(responseData.result.value!)
if let applePrice = json["query"]["results"]["quote"][0]["Ask"].string {
print(applePrice)
self.applePrice = applePrice
self.tableView.reloadData()
}
if let googlePrice = json["query"]["results"]["quote"][1]["Ask"].string {
print(googlePrice)
self.googlePrice = googlePrice
self.tableView.reloadData()
}
if let twitterPrice = json["query"]["results"]["quote"][2]["Ask"].string {
print(twitterPrice)
self.twitterPrice = twitterPrice
self.tableView.reloadData()
}
if let teslaPrice = json["query"]["results"]["quote"][3]["Ask"].string {
print(teslaPrice)
self.teslaPrice = teslaPrice
self.tableView.reloadData()
}
if let samsungPrice = json["query"]["results"]["quote"][4]["Ask"].string {
print(samsungPrice)
self.samsungPrice = samsungPrice
self.tableView.reloadData()
}
let stockPrices = ["\(self.applePrice)", "\(self.googlePrice)", "\(self.twitterPrice)", "\(self.teslaPrice)", "\(self.samsungPrice)"]
self.stockPrices = stockPrices
print(json)
}
}
}
in cellForRowAt indexPath function I print to the label
if self.stockPrices.count > indexPath.row + 1 {
cell.detailTextLabel?.text = "Current Stock Price: \(self.stockPrices[indexPath.row])" ?? "Fetching stock prices..."
} else {
cell.detailTextLabel?.text = "No data found"
}
I'm running into the issue of printing Current Stock Price: Optional("stock price"), with the word optional. I gather that this is because I'm giving it an array of optional values, but I sort of have to since I actually don't know if there will be data coming from YQL, one of the 5 stocks might be nil while the others have data. From reading other similar questions I can see that the solution would be to unwrap the value with !, but I'm not so sure how to implement that solution when it's an array with data that might be nil, and not just an Int or something.
How can I safely unwrap here and get rid of the word Optional?
First off:
Any time you repeat the same block of code multiple times and only increase a value from 0 to some max, it is a code smell. You should think about a different way to handle it.
You should use an array to do this processing.
How about a set of enums for indexes:
enum companyIndexes: Int {
case apple
case google
case twitter
case tesla
//etc...
}
Now you can run through your array with a loop and install your values more cleanly:
var stockPrices = [String?]()
Alamofire.request(stockUrl).responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let json = JSON(responseData.result.value!)
let pricesArray = json["query"]["results"]["quote"]
for aPriceEntry in pricesArray {
let priceString = aPriceEntry["ask"].string
stockPrices.append(priceString)
}
}
}
And to fetch a price from the array:
let applePrice = stockPrices[companyIndexes.apple.rawValue]
That will result in an optional.
You could use the nil coalescing operator (??) to replace a nil value with a string like "No price available.":
let applePrice = stockPrices[companyIndexes.apple.rawValue] ?? "No price available"
or as shown in the other answer:
if let applePrice = stockPrices[companyIndexes.apple.rawValue] {
//we got a valid price
} else
//We don't have a price for that entry
}
I'm writing this outside of Xcode (so there might be typos), but this kind of logic should work.
if self.stockPrices.count > indexPath.row + 1 {
var txt = "Fetching stock prices..."
if let price = self.stockPrices[indexPath.row] {
txt = price
}
cell.detailTextLabel?.text = txt
} else {
cell.detailTextLabel?.text = "No data found"
}
For safe unwrap use that code:
if let currentStockPrice = self.stockPrices[indexPath.row]
{
// currentStockPrice available there
}
// currentStockPrice unavailable
If you need to unwrap multiple variables in one if after another it may lead to unreadable code. In such case use this pattern
guard let currentStockPrice = self.stockPrices[indexPath.row]
else
{
// currentStockPrice is unavailable there
// must escape via return, continue, break etc.
}
// currentStockPrice is available

Intermittent fatal error (unexpectedly found nil while unwrapping an Optional value) in Swift

This error is intermittent. Sometimes the app works fine and sometimes it does not.
I have a view controller getNamesViewController which has an alamofire request, it also has a scroll view that loads extra nibs to allow the user to scroll left or right through the content
The results from alamofire are passed to each of the views like so:
self.vc0.a = self.a
where vc0 has been declared as the xib:
let vc0 = displayRegionsViewController(nibName: "displayRegionsViewController", bundle: nil)
The alamofire that gets the results and passes them to the variables is:
Alamofire.request(.GET, url, parameters: ["api_key": api])
.responseJSON { response in
switch response.result {
case .Success:
if let value = response.result.value {
let json = JSON(value)
dispatch_async(dispatch_get_main_queue()) {
self.a = json["totalCountName"].stringValue
self.b = json["namesRegion"].stringValue
self.vc0.a = self.a
self.vc0.b = self.b
}
}
case .Failure(let error):
print(error)
}
}
This data gets values from the JSON (The JSON returns 0 if there are no results)
On the nib file, The variables are initialized like so:
var a:Int? = 0
var b:Int? = 0
var c:Float? = 00.0
var d:Float? = 00.0
Then in the view did load, I have this code:
func percentageSum (lhs: Int, rhs: Int) -> String {
let result = round((Double(lhs) / Double(rhs)) * 100)
return String(result) + "%"
}
func percentageSumFloat (lhs: Float, rhs: Float) -> Float {
let floatreturn = Float((lhs / rhs) * 100)
print(floatreturn)
return floatreturn
}
//Convert passed value to int
a = Int(a)
b = Int(b)
if (a != 0) && (b != 0) {
self.displayViewsTotal.text = percentageSum(self.a!,rhs: self.b!)
campaignOpensTotalChart.updateProgressCircle(percentageSumFloat(c!,rhs: d!))
campaignOpensTotalChart.changeLineWidth(CGFloat(20))
SwiftSpinner.hide()
}else {
SwiftSpinner.show("Unexpected Error, Try again").addTapHandler({
SwiftSpinner.hide({
self.initRun()
})
}, subtitle: "Tap to try again..")
}
Now, as I said sometimes (More than not) This works, but every now and then (more on actual device) I get the error:
fatal error: unexpectedly found nil while unwrapping an Optional value
At Xcode highlights this line:
self.displayViewsTotal.text = percentageSum(self.a!,rhs: self.b!)
Confession:
I am a noob at this, trying to get straight into development by building an app.
This is what I want:
Use alamofire to connect to the JSON API and get the results (Which are String Form)
Once alamofire is complete, pass those new values to variables declared in the nib files which are loaded in the scroll view.
or, if the way I am doing it so far is acceptable, I would simply like a way to make this code:
self.displayViewsTotal.text = percentageSum(self.a!,rhs: self.b!)
campaignOpensTotalChart.updateProgressCircle(percentageSumFloat(c!,rhs: d!))
campaignOpensTotalChart.changeLineWidth(CGFloat(20))
not throw the error above.
Please help me guys, I am bashing my head against the wall here.
Edit: Thoughts
I think it might be wither something to do with the view getting loaded before alamofire has loaded the data, however I used:
dispatch_async(dispatch_get_main_queue()) { }
which I believe fixes that issue?
If you're not sure your optionals are not nil, don't force wrap them like self.displayViewsTotal.text = percentageSum(self.a!,rhs: self.b!)
Just rewrite your code and check your optionals are not nil before using.
func percentageSum (lhs: Int?, rhs: Int?) throws -> String {
guard let lhs = lhs else{
return "lhs is nil"
}
guard let rhs = rhs else{
return "rhs is nil"
}
let result = round((Double(lhs) / Double(rhs)) * 100)
return String(result) + "%"
}
self.displayViewsTotal.text = percentageSum(self.a,rhs: self.b)

Resources