Comparing non optional value leads to error - swift

I am on point where I gotta compare non optional value with nil. But I can't do it because Xcode says:
Comparing non-optional value of type 'Int' to nil always returns false
So I created Struct and then made variable: var products: [Product] = []
How I am able to compare it with nil?:
if products[indexPath.row].snusPortions == nil
{
cell.snusPortionsAmountLabel.text = "N/A"
}else
{
cell.snusPortionsAmountLabel.text = String(products[indexPath.row].snusPortions)
}
I've assigned values to them like this:
let ref = FIRDatabase.database().reference().child("Snuses").queryOrdered(byChild: "Brand").queryEqual(toValue: brandName)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists(){
let enumerator = snapshot.children
while let thisProduct = enumerator.nextObject() as? FIRDataSnapshot
{
print(thisProduct.value) // So I may see what the data is like and know how to extract it
// Chances are you'd have to create a dictionary
let thisProductDict = thisProduct.value as! [String:AnyObject]
let productName = thisProductDict["Products"] as! String
let snusPortions = thisProductDict["PortionsCan"] as? Int
let productObject = Product(snusProductTitle: productName, snusNicotine: snusNicotine, snusPortions: snusPortions!, snusFlavor: snusFlavor, snusWeight: snusWeight!, snusShippingWeight: snusShippingWeight, snusProductImageURL: productURL)
self.products.append(productObject)
print(self.products)
}
self.tableView.reloadData()
}
})
This is Product struct:
struct Product {
var snusProductTitle: String
init()
{
snusProductTitle = ""
}
init(snusProductTitle: String){
self.snusProductTitle = snusProductTitle
}
}
While testing it says snusPortions is nil but I said to make it "N/A" if it is nil, why?

It sounds like you are confusing yourself between the local variable snusPortions and the Product property snusPortions.
In your Product definition, the property snusPortions is an Int. It can never be nil. Hence, in this code:
if products[indexPath.row].snusPortions == nil
... this Product's snusPortions will never be nil, and we will never set the text to "N/A".
Now let's look at your other code:
let snusPortions = thisProductDict["PortionsCan"] as? Int
This is a completely different snusPortions. It can be nil, namely, if thisProductDict lacks a "PortionsCan" key or if its value is not castable to Int.

Related

Retrieving Firebase Database value Swift Xcode

I have a lite problem, I cannot recover the carte value in my database. when i get there for username. But the path is good.
My code :
override func viewDidLoad() {
super.viewDidLoad()
loadCarte()
}
func loadCarte(){
var ref: DatabaseReference!
ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
ref.child("users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let Carte = value?["carte"] as? String ?? ""
self.carteButton.setTitle(Carte + " cartes", for: .normal)
// ...
}) { (error) in
print(error.localizedDescription)
}
}
I manage to recover other values but not that one, can that come from the fact that its being an Integer?
Thank's in Advance
EDIT : my Json structure of a User Type under users > userID
{
"carte" : 10,
"email" : "apperce.v#gmail.com",
"username" : "vicro87000"
}
You're storing a numeric value in the carte property, so you're casting a numeric value to a string. In Swift you can't cast a number to a string like that.
I've been told by a Swift expert that this is the best way to accomplish the same:
let carte = value?["carte"].flatMap { $0 as? Int } .map { String($0) }
The flatMap operation here unwraps the int value from inside the optional that it's in. Then the map converts the int value to a string. Calling the String initializer in the map closure is required because Swift Ints are not castable to String types.
Assuming you're structure is:
uid_0
"carte" : 10,
"email" : "apperce.v#gmail.com",
"username" : "vicro87000"
And you want to read in the child values, here's some code to read that node
let thisUser = ref.child("users").child(uid)
thisUser.observeSingleEvent(of: .value, with: { snapshot in
let email = snapshot.childSnapshot(forPath: "email").value as? String ?? "No email"
let userName = snapshot.childSnapshot(forPath: "username").value as? String ?? "No username"
let carte = snapshot.childSnapshot(forPath: "carte").value as? Int ?? 0
print(email, username, carte)
})
So the carte var will be an Int and can be used in a label
myTextField.text = "\(carte)"
or cast to a String
let myIntAsString = String(carte)
this
as? Int ?? 0
is called a nil coalescing operator and protects your code in case the value is set to nil, it actually is set to 0 so you're code doesn't crash.
As a side note, this is dangerous
userID!
as force-unwrapping an optional can lead to a crash
Better to do
guard let user = Auth.auth().currentUser else {return}
let uid = user.uid

Firebase don't send me my value into my variable

I've got a code which normally should return to me a value from Firebase.
My Firebase struct is :
Experience{
UserId{
LDG_DAY: "4"
LDG_NIGHT: "0"
APCH_IFR: "0"
}
}
My code is :
func getUserExp(){
let ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let Date = self.flightDate.text
ref.child("Experience")/*.child(userID!)*/.observeSingleEvent(of: .value) {(snapshot) in
if snapshot.hasChild(userID!){
let value = snapshot.value as? NSDictionary
let ldg_day = value?["LDG_DAY"] as? String ?? "123"
let ldg_night = value?["LDG_NIGHT"] as? String ?? "0"
let apch_ifr = value?["APCH_IFR"] as? String ?? "0"
self.intLdgDay = Int(ldg_day)!
self.intLdgNight = Int(ldg_night)!
self.intApchIfr = Int(apch_ifr)!
print("string = \(ldg_day) int = \(self.intLdgDay)")
}
}
}
Now the code didn't work as I would like... In fact my code return the basic as? String ?? "123" value but the snapshot.value get the good value from firebase ...
What's wrong ? I use this code for many other part of my app and no problems about it ?
Thanks for your help
I believe you want to ensure the node exists before trying to read the child data.
NOTE:
I see the path to read has the uid commented out so it's unclear if you intended to read a single user (leaving in the uid) or if you actually wanted to load every user at one time (thousands). This answer assumes you are intending to read that specific user node only. See #Callam answer if you intended to read ALL of the users nodes at one time.
The code you have now is using snapshot.hasChild which looks within the node to see if the child, the users uid exists, and it doesn't so the code will always fail.
if snapshot.hasChild(userID!)
I think what you want to do is use snapshot.exists to ensure it's a valid node before reading. Here's the code:
let experienceRef = self.ref.child("Experience")
let usersExpRef = experienceRef.child(uid)
usersExpRef.observeSingleEvent(of: .value) { snapshot in
if snapshot.exists() {
let value = snapshot.value as! [String: Any]
let ldg_day = value["LDG_DAY"] as? String ?? "123"
print("string = \(ldg_day)")
} else {
print("the \(uid) node does not exist")
}
}
I would also suggest safely unwrapping options before attempting to work with them as they could be nil, and that would crash your code.
guard let thisUser = Auth.auth().currentUser else { return }
let uid = thisUser.uid
Note I also replaced the old objc NSDictionary with it's Swifty counterpart [String: Any]
Assuming your struct is from the root, and Experience contains more than one user ID, your code is currently observing the value for all user IDs since the /*.child(userID!)*/ is commented out.
Therefore you are requesting every user's experience and checking on the client if the current user exists as a child – this will succeed if the current user's ID is present at Experience/$uid.
ref.child("Experience")/*.child(userID!)*/.observeSingleEvent(of: .value) { (snapshot) in
if snapshot.hasChild(userID!) {
let value = snapshot.value as? NSDictionary
Now we have a snapshot with all Experiences and we've confirmed that it has a child for the current user's ID – we would need to get that child and cast the value of that to a dictionary.
let value = snapshot.childSnapshot(forPath: userID).value as? NSDictionary
This fixes the issue but obviously, we don't want to download every experience on a single user's device, and they maybe shouldn't even have the permission to request that reference location either.
So if you uncomment .child(userID!), the snapshot will be of just one Experience, so snapshot.hasChild(userID!) will fail. Instead, you can use snapshot.exists() and/or a conditional cast to determine if the snapshot for the userID is existent and/or thereby castable.
func getUserExp() {
let ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let Date = self.flightDate.text
ref.child("Experience").child(userID!).observeSingleEvent(of: .value) { snapshot in
if snapshot.exists() {
let value = snapshot.value as? [String:String]
let ldg_day = value?["LDG_DAY"] ?? "123"
let ldg_night = value?["LDG_NIGHT"] ?? "0"
let apch_ifr = value?["APCH_IFR"] ?? "0"
self?.intLdgDay = Int(ldg_day)!
self?.intLdgNight = Int(ldg_night)!
self?.intApchIfr = Int(apch_ifr)!
print("string = \(ldg_day) int = \(self.intLdgDay)")
} else {
print("experience for \(snapshot.key) doesn't exist")
}
}
}
You can clean this up a bit with a struct and extension.
// Experience.swift
struct Experience {
var ldg_day: String
var ldg_night: String
var apch_ifr: String
}
extension Experience {
static var currentUserRef: DatabaseReference? {
return Auth.auth().currentUser.flatMap {
return Database.database().reference(withPath: "Experience/\($0.uid)")
}
}
init?(snapshot: DataSnapshot) {
guard snapshot.exists() else { return nil }
let value = snapshot.value as? [String:String]
self.ldg_day = value?["LDG_DAY"] ?? "123"
self.ldg_night = value?["LDG_NIGHT"] ?? "0"
self.apch_ifr = value?["APCH_IFR"] ?? "0"
}
}
Et voilà,
func getUserExp() {
Experience.currentUserRef?.observeSingleEvent(of: .value, with: { [weak self] in
if let experience = Experience(snapshot: $0) {
self?.intLdgDay = Int(experience.ldg_day)!
self?.intLdgNight = Int(experience.ldg_night)!
self?.intApchIfr = Int(experience.apch_ifr)!
print("string = \(experience.ldg_day) int = \(self.intLdgDay)")
} else {
print("experience for \($0.key) doesn't exist")
}
})
}

Swift cast if possible

I have this code
let jsonData = try JSONSerialization.jsonObject(with: data, options: []) as! [Any?]
if var first = jsonData[0] as! String?{
if(first=="Error"){
DispatchQueue.main.async(execute: {
self.postNotFoundLabel.isHidden = false
});
}else if(first=="Empty"){
print("Empty")
}
}
What i want to do is to cast jsonData[0] to String if it's possible and if it's not then move on.But instead when it's not possible application stops and gives me an error
Could not cast value of type '__NSDictionaryI' (0x1092054d8) to 'NSString' (0x108644508).
How can i cast only when it's possible?
You are trying to force-cast to an optional String. That's not what you want.
Change:
if var first = jsonData[0] as! String? {
to:
if var first = jsonData[0] as? String {
This tries to cast to String. If jsonData[0] isn't actually a String, you get nil and the if var fails.
And you probably want if let, not if var since you don't seem to be making any change to first.
First of all JSON objects will never return optional values so [Any?] is nonsense.
Second of all the error message says the type cast to string is inappropriate because the type of the result is actually a dictionary.
Solution: Check the type for both String and Dictionary
if let jsonData = try JSONSerialization.jsonObject(with: data) as? [Any],
let first = jsonData.first {
if let firstIsDictionary = first as? [String:Any] {
// handle case dictionary
} else if let firstIsString = first as? String {
// handle case string
}
}
PS: A type cast forced unwrap optional to optional (as! String?) is nonsense, too.
Here's the Swifty way to do what you're doing :)
guard let jsonData = try JSONSerialization.jsonObject(with: data, options: []) as? [Any?], let first = jsonData[0] as? String else {
DispatchQueue.main.async(execute: {
self.postNotFoundLabel.isHidden = false
});
return
}
if(first == "Empty") {
print(first)
}
Don't use as! if you are not sure that casting will succeed. The exclamation mark after the as keyword forces the casting, which throws an error if the casting does not succeed.
Use as? instead, which returns an optional variable of the type you were trying to casting to. If the casting fails, instead of throwing an error, it just returns nil.
let jsonData = try JSONSerialization.jsonObject(with: data) as? [Any]
if var first = jsonData.first as? String{
if(first=="Error"){
DispatchQueue.main.async(execute: {
self.postNotFoundLabel.isHidden = false
});
}else if(first=="Empty"){
print("Empty")
}
}

Swift Cannot assign through subscript: subscript is get only

I am fairly new to the Swift syntax and am receiving this error with my code "Cannot assign through subscript: subscript is get only"
This is from the line: friendDictionary[(friendUID as? String)!] = ["name": friendsData!["name"]]
Any advice on the correct way of doing it would be very helpful.
func getFriendsUIDs() {
if FBSDKAccessToken.currentAccessToken() == nil {
print("failed to start graph request")
return
}else{
}
if FBSDKAccessToken.currentAccessToken() != nil {
}
let parameters = ["fields": "name, id, picture"]
FBSDKGraphRequest(graphPath: "/me/friends", parameters: parameters).startWithCompletionHandler {
(NSURLConnection, result, requestError) in
let friendIds = result["id"] as? NSDictionary
let friendsData = friendIds!["data"] as? [NSDictionary]
var ref: FIRDatabaseReference!
ref = FIRDatabase.database().reference()
ref.child("users").child((FIRAuth.auth()?.currentUser?.uid)!).child("friendUIDs").observeEventType(.Value, withBlock: { (snapshot) in
self.FriendUIDs = NSArray()
self.FriendUIDs = (snapshot.value as? NSArray)!
print(self.FriendUIDs)
var friendDictionary = NSDictionary()
for friendUID in self.FriendUIDs {
friendDictionary[(friendUID as? String)!] = ["name": friendsData!["name"]]
}
self.fetchFriendFeed(friendDictionary)
}) { (error) in
print(error.localizedDescription)
}
}
}
func fetchFriendFeed(friendDictionary: NSDictionary) {
var ref: FIRDatabaseReference!
ref = FIRDatabase.database().reference()
for friendUID in FriendUIDs {
ref.child("users").child(friendUID as! String).child("Agenda").observeEventType(.ChildAdded, withBlock: { (snapshot) in
print(snapshot)
if let dictionary = snapshot.value as? [String: AnyObject] {
let friendPost = FriendPost()
friendPost.picture = friendDictionary[friendUID as! String]? ["picture"] as? String
friendPost.activity = dictionary["activity"] as? String
friendPost.date = dictionary["date"] as? String
friendPost.time = dictionary["time"] as? String
friendPost.friendname = friendDictionary[friendUID as! String]? ["name"] as? String
self.friendPosts.append(friendPost)
dispatch_async(dispatch_get_main_queue(), {
self.collectionView?.reloadData()
Nothing to do with Swift. You've elected to use Objective-C, in effect, by making friendDictionary an NSDictionary. NSDictionary is immutable; you can't assign into it or alter it in any way. That is simply a fact about Objective-C. The Swift var declaration makes no difference to this fact.
A better choice, since you are writing in Swift, would be to use a Swift dictionary, which is [AnyHashable:Any]() (in Swift 3). This will interchange with NSDictionary when you are talking to Objective-C, but it will give you a mutable dictionary because you (rightly) declared it with var.
Have you tried using NSMutableDictionary? That solved the issue for me.
For those who get stuck here, another reason for this happens when you try to assign something that does not conform the actual dictionary, in my example i was doing something like this:
var dict = [Date : UUID]()
let randomUUID = UUID()
dict[randomUUID] = Date.now
whereas I meant to write UUID : Date but I was sleepy so i made a mistake, and Swift gave me a misleading error saying subscript is get-only. So this error also appears with type mismatch for Swift 5.7.

Unexpectedly found nil while unwrapping an Optional value

Probably I don't understand clearly Optional values:
class func albumsWithJSON(allResults: NSArray) -> [Album] {
var albums = [Album]()
if allResults.count>0 {
for result in allResults {
var name = result["trackName"] as? String
if name == nil {
name = result["collectionName"] as? String
}
var price = result["formattedPrice"] as? String
if price == nil {
price = result["collectionPrice"] as? String
if price == nil {
var priceFloat: Float? = result["collectionPrice"] as? Float
var nf: NSNumberFormatter = NSNumberFormatter()
nf.maximumFractionDigits = 2;
if priceFloat != nil {
price = "$"+nf.stringFromNumber(priceFloat)
}
}
}
let thumbnailURL = result["artworkUrl60"] as String
let imageURL = result["artworkUrl100"] as String
let artistURL = result["artistViewUrl"] as? String
var itemURL = result["collectionViewUrl"] as? String
if itemURL == nil {
itemURL = result["trackViewUrl"] as? String
}
var newAlbum = Album(name: name!, price: price!, thumbnailImageURL: thumbnailURL, largeImageURL: imageURL, itemURL: itemURL!, artistURL: artistURL!)
albums.append(newAlbum)
}
}
}
At this line I get "unexpectedly found nil while unwrapping an Optional value" error:
var newAlbum = Album(name: name!, price: price!, thumbnailImageURL: thumbnailURL, largeImageURL: imageURL, itemURL: itemURL!, artistURL: artistURL!)
Obviously some informations are missing from JSON, but how can I handle a missing value?
All that error means is that one of the values that you're passing into that function is nil. When you put the ! at the end of those values, it unwraps it. If the value is nil while unwrapping, it throws an exception.
The way to fix this is dependent on how important the values being nil is. If you cannot live with any one of those being nil, then you'll have to check for nil and do something. If you can have them be nil, then you'll want to make sure that the function accepts nil values.
To better understand optionals, you should read the docs: https://developer.apple.com/library/prerelease/mac/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-XID_483