I keep getting this error message: Value of optional type 'Int?' must be unwrapped to a value of type 'Int'
with this code:
let data = document.data()
let uid = data["userid"] as? String ?? ""
let location = data["location"] as? String ?? ""
let currentRating = data["currentRating"] as? Int
let usualRating = data["usualRating"] as? Int
var Submission = RatingSubmission(uid: uid, location: location, currentRating: currentRating, usualRating: usualRating)
what do I need to add to currentRating and usualRating in the Submission variable so that it runs properly?
The reason is that RatingSubmission function works with Int values but you if you assign them as optional values there is a chance to these variables may be "nill". To avoid this you can either remove this option like this:
let currentRating = data["currentRating"] as? Int ?? 0
let usualRating = data["usualRating"] as? Int ?? 0
Second option, if you sure these values are not nill, you can downcasting using "as!".(this is not safe most time)
let currentRating = data["currentRating"] as! Int
let usualRating = data["usualRating"] as! Int
Third option, you can check the values with if let;
let currentRating = data["currentRating"] as? Int
let usualRating = data["usualRating"] as? Int
if let currentRating = Int(data["currentRating"]), let usualRating = Int(data["usualRating"]) {
var Submission = RatingSubmission(uid: uid, location: location, currentRating: currentRating, usualRating: usualRating)
}
I would recommend using the get() method on the document which was made specifically for getting properties from documents.
if let usualRating = document.get("usualRating") as? Int {
print(usualRating)
}
Alternatively, you can one-line it by giving it a default value (of 0 in this case). But this would appear like an anti-pattern to me just on the face of it.
let usualRating = document.get("usualRating") as? Int ?? 0
Related
I've got two initializers:
struct UserInfo{
let ref: DatabaseReference?
let key: String
let firstName: String
let lastName: String
let username: String
let pictureURL : String?
let admin : Bool
init(firstName: String, lastName:String,username:String,pictureURL:String?,admin:Bool, key:String = "" ){
self.ref = nil
self.key = key
self.firstName = firstName
self.lastName = lastName
self.username = username
self.pictureURL = pictureURL
self.admin = admin
}
init?(snapshot:DataSnapshot){
guard let value = snapshot.value as? [String:AnyObject],
let firstName = value["firstName"] as? String,
let lastName = value["lastName"] as? String,
let username = value["userName"] as? String,
let profilePic = value["pictureURL"] as? String,
let admin = value["isAdmin"] as? Bool
else {return nil}
self.ref = snapshot.ref
self.key = snapshot.key
self.firstName = firstName
self.lastName = lastName
self.username = username
self.pictureURL = profilePic
self.admin = admin
}
func toAnyObject()-> Any{
return [
"firstName": firstName,
"lastName": lastName,
"username": username,
"pictureURL":pictureURL as Any,
"isAdmin": admin
]
}
}
For reading most recent data I use this method combined with first init and it works:
let completed =
DataObjects.infoRef.child(uid!).observe(.value){ snapshot,error in
var newArray: [UserInfo] = []
if let dictionary = snapshot.value as? [String:Any]{
let username = dictionary["username"] as! String
let firstName = dictionary["firstName"] as! String
let lastName = dictionary["lastName"] as! String
let profilePic = dictionary["pictureURL"] as? String
let admin = dictionary["isAdmin"] as! Bool
let userInformation = UserInfo(firstName: firstName, lastName:
lastName, username: username,pictureURL: profilePic, admin: admin)
newArray.append(userInformation)
print(newArray)
completion(.success(newArray))
print(newArray)
}
Why and when do I need to use second init??
In Firebase tutorial on raywenderlich.com we gat example about: Synchronizing Data to the Table View using second init:
let completed = ref.observe(.value) { snapshot in
// 2
var newItems: [GroceryItem] = []
// 3
for child in snapshot.children {
// 4
if
let snapshot = child as? DataSnapshot,
let groceryItem = GroceryItem(snapshot: snapshot) {
newItems.append(groceryItem)
}
}
// 5
self.items = newItems
self.tableView.reloadData()
But my method works the same with first init.
The question is really asking about two things that functionally work the same.
In one case the snapshot is being "broken down" into its raw data (strings etc) within the firebase closure
DataObjects.infoRef.child(uid!).observe(.value){ snapshot,error in
let username = dictionary["username"] as! String
let firstName = dictionary["firstName"] as! String
let lastName = dictionary["lastName"] as! String
let userInformation = UserInfo(firstName: firstName, lastName: lastName...
and then passing that raw data to the struct. That object is then added to the array
In the second case the snapshot itself is passed to the struct
init?(snapshot:DataSnapshot) {
guard let value = snapshot.value as? [String:AnyObject],
and the snapshot is broken down into it's raw data within the object.
The both function the same.
It's a matter of readability and personal preference. Generally speaking having initializers etc within an object can make the code a bit more readable, the object more reusable and less code - see this pseudo code
DataObjects.infoRef.child(uid!).observe(.value){ snapshot, error in
let user = UserInfo(snapshot)
self.newArray.append(user)
})
That's pretty tight code.
Imagine if there were 10 places you wanted to access those objects within your app. In your first case, that code would have to be replicated 10 times - which could be a lot more troubleshooting. In my example above, the object itself does the heavy lifting so accessing them requires far less code.
Two other things. You may want to consider using .childSnapshot to access the data within a snapshot instead of a dictionary (either way works)
let userName = snapshot.childSnapshot(forPath: "name").value as? String ?? "No Name"
and please avoid force unwrapping optional vars
child(uid!)
as it will cause unstable code and random, unexplained crashes. This would be better
guard let uid = maybeUid else { return } //or handle the error
I am new to Swift, but have some basic experience with Objective-C programming, and Swift seems much simpler.
However, I can't quite understand the struct thing. I followed a tutorial on how to use Firebase Realtime Database, and this tutorial were using a model to store the data.
But when I modified the struct with additional properties, the previously saved entries in the database is not showing up. I think it's because the model doesn't recognize the object in the database because it has different properties, but how can I make a property optional? So that old entries in the database with different structure (missing properties) are still valid and showing up?
Here is the model. The new property added is all the references to the description.
import Foundation
import Firebase
struct InsuranceItem {
let ref: DatabaseReference?
let key: String
let name: String
let timestamp: Int
let itemValue: Int
let description: String?
let userId: String?
init(name: String, timestamp: Int, itemValue: Int = 0, description: String = "", userId: String, key: String = "") {
self.ref = nil
self.key = key
self.name = name
self.timestamp = Int(Date().timeIntervalSince1970)
self.itemValue = itemValue
self.description = description
self.userId = userId
}
init?(snapshot: DataSnapshot) {
guard
let value = snapshot.value as? [String: AnyObject],
let name = value["name"] as? String,
let timestamp = value["timestamp"] as? Int,
let itemValue = value["itemValue"] as? Int,
let description = value["description"] as? String,
let userId = value["userId"] as? String else { return nil }
self.ref = snapshot.ref
self.key = snapshot.key
self.name = name
self.timestamp = timestamp
self.itemValue = itemValue
self.description = description
self.userId = userId
}
func toAnyObject() -> Any {
return [
"name": name,
"timestamp": timestamp,
"itemValue": itemValue,
"description": description!,
"userId": userId!
]
}
}
The problematic bit is your failable init, init?(snapshot: DataSnapshot). You fail the init even if an Optional property is missing, which is incorrect. You should only include the non-Optional properties in your guard statement, all others should simply be assigned with the optional casted value.
init?(snapshot: DataSnapshot) {
guard
let value = snapshot.value as? [String: Any],
let name = value["name"] as? String,
let timestamp = value["timestamp"] as? Int,
let itemValue = value["itemValue"] as? Int else { return nil }
self.ref = snapshot.ref
self.key = snapshot.key
self.name = name
self.timestamp = timestamp
self.itemValue = itemValue
// Optional properties
let description = value["description"] as? String
let userId = value["userId"] as? String
self.description = description
self.userId = userId
}
Unrelated to your question, but your toAnyObject function is unsafe, since you are force-unwrapping Optional values. Simply keep them as Optionals without any unwrapping and add as Any to silence the warning for implicit coersion.
func toAnyObject() -> Any {
return [
"name": name,
"timestamp": timestamp,
"itemValue": itemValue,
"description": description as Any,
"userId": userId as Any
]
}
Here is the output of "print(dict)"...
["2018-10-17 11:19:51": {
firstname = Brooke;
id = 40vI7hApqkfX75SWsqIR6cdt7xV2;
lastname = Alvarez;
message = hshahyzhshbsbvash;
username = poiii;
}]
["2018-10-17 11:20:31": {
firstname = Trevor;
id = 40vI7hApqkfX75SWsqIR6cdt7xV2;
lastname = Bellai;
message = hey;
username = br9n;
}]
["2018-10-17 11:20:44": {
firstname = Amy;
id = 40vI7hApqkfX75SWsqIR6cdt7xV2;
lastname = Ikk;
message = hey;
username = nine9;
}]
My code...
Database.database().reference().child("recent-msgs").child(uid!).observe(.childAdded) { (snapshot: DataSnapshot) in
if let dict = snapshot.value as? [String: Any] {
print(dict)
// Store data in user.swift model
let firstnameData = dict[0]["firstname"] as! String
let idData = dict["id"] as! String
let lastnameData = dict["lastname"] as! String
let messageData = dict["message"] as! String
let usernameData = dict["username"] as! String
let rankData = dict["rank"] as! String
let propicrefData = dict["propicref"] as! String
let convoinfo = RecentConvo(firstnameString: firstnameData, idString: idData, lastnameString: lastnameData, messageString: messageData, usernameString: usernameData, rankString: rankData, propicrefString: propicrefData)
self.recentconvos.append(convoinfo)
print(self.recentconvos)
self.tableView.reloadData()
}
}
I'm trying to retrieve the dictionary within the first dictionary which is the value to the key which is the date associate with it. For example: 2018-10-17 11:19:51. However I cannot use this exact string to call it because I must do this without the knowledge of that string.
I tried this:
let firstnameData = dict[0]["firstname"] as! String
But it returns an error:
Cannot subscript a value of type '[String : Any]' with an index of type 'Int'
The error noted above is showing up because you were trying to access the element at a certain position (0) from the dictionary. Dictionaries are not ordered lists, and hence won't have a fixed order of elements to be accessed.
The logged dictionary doesn't really look like a dictionary. Assuming that it is a dictionary, and its keys are the date strings, you can use the following code snippet to parse the dictionary.
class RecentConversation {
var id: String?
var firstName: String?
var lastName: String?
var message: String?
var username: String?
var rank: String?
var propicref: String?
init?(dictionary: [String: Any]?) {
guard let dict = dictionary else {
// Return nil in case the dictionary passed on is nil
return nil
}
id = dict["id"] as? String
firstName = dict["firstname"] as? String
lastName = dict["lastname"] as? String
message = dict["message"] as? String
username = dict["username"] as? String
rank = dict["rank"] as? String
propicref = dict["propicref"] as? String
}
}
Usage:
let dateStrings = dict.keys.sorted {
// Sort in chronological order (based on the date string; if you need to sort based on the proper date,
// convert the date string to Date object and compare the same).
//
// Swap the line to $0 > $1 to sort the items reverse chronologically.
return $0 < $1
}
var conversations: [RecentConversation] = []
for date in dateStrings {
if let conversation = RecentConversation(dictionary: (dict[date] as? [String: Any])) {
conversations.append(conversation)
}
}
You were all very helpful, so I would like to start off by saying thank you. I went ahead and applied the method that lionserdar explained. (.allKeys)
// Fetch Recent Messages
func fetchRecentMsgs() {
// Direct to database child
Database.database().reference().child("recent-msgs").child(uid!).observe(.childAdded) { (snapshot: DataSnapshot) in
if let dict = snapshot.value as? NSDictionary {
print(dict)
print(dict.allKeys)
let keys = dict.allKeys
for key in keys {
print(key)
if let nestedDict = dict[key] as? [String: Any] {
print(nestedDict)
let firstnameData = nestedDict["firstname"] as! String
let idData = nestedDict["id"] as! String
let lastnameData = nestedDict["lastname"] as! String
let messageData = nestedDict["message"] as! String
let usernameData = nestedDict["username"] as! String
Worked for me so I hope this will help others too!
Assume I receive data in some JSON which is then parsed to dictionary using a native tool. Some of those values are numbers and naturally I will need to parse them, cast them into what I need in the application.
But the transition from Any to an actual number such as Float, Int, Double seems to be a bit messy. For instance if I expect a double but at some point server returns an integer my code will fail using:
let doubleValue = dictionary["key"] as! Double
So this will work when the item is 1.3 but will fail for 1. To use a bit more concrete example we can use the following:
let myDictionary: [String: Any] = [
"myNumber_int" : 1,
"myNumber_float" : Float(1),
"myNumber_cgFloat" : CGFloat(1),
"myNumber_double" : 1.0
]
let numberInt1 = myDictionary["myNumber_int"] as? Int // 1
let numberInt2 = myDictionary["myNumber_int"] as? Float // nil
let numberInt3 = myDictionary["myNumber_int"] as? CGFloat // nil
let numberInt4 = myDictionary["myNumber_int"] as? Double // nil
let numberFloat1 = myDictionary["myNumber_float"] as? Int // nil
let numberFloat2 = myDictionary["myNumber_float"] as? Float // 1
let numberFloat3 = myDictionary["myNumber_float"] as? CGFloat // nil
let numberFloat4 = myDictionary["myNumber_float"] as? Double // nil
let numberCGFloat1 = myDictionary["myNumber_cgFloat"] as? Int // nil
let numberCGFloat2 = myDictionary["myNumber_cgFloat"] as? Float // nil
let numberCGFloat3 = myDictionary["myNumber_cgFloat"] as? CGFloat // 1
let numberCGFloat4 = myDictionary["myNumber_cgFloat"] as? Double // nil
let numberDouble1 = myDictionary["myNumber_double"] as? Int // nil
let numberDouble2 = myDictionary["myNumber_double"] as? Float // nil
let numberDouble3 = myDictionary["myNumber_double"] as? CGFloat // nil
let numberDouble4 = myDictionary["myNumber_double"] as? Double // 1
So for each type only 1 cast will actually work which is... well I would at least expect that CGFloat will be able to cast directly to Double or Float...
So my solution is using NSNumber:
let myNumbers: [CGFloat] = [
CGFloat((myDictionary["myNumber_int"] as? NSNumber)?.floatValue ?? 0.0),
CGFloat((myDictionary["myNumber_float"] as? NSNumber)?.floatValue ?? 0.0),
CGFloat((myDictionary["myNumber_cgFloat"] as? NSNumber)?.floatValue ?? 0.0),
CGFloat((myDictionary["myNumber_double"] as? NSNumber)?.floatValue ?? 0.0),
] // [1,1,1,1]
This naturally works but the code is pretty ugly for something as simple as this. But putting the code "ugliness" aside; can we not do similar without using Next Step? I mean NSNumber for something as seemingly trivial as this? I am missing something very obvious here, right?
Try like this:-
if let data = myDictionary as? [String: AnyObject] {
let myNumber = data["myNumber_int"] as? Int
}
I'm confused on how to properly save data to firebase with Eureka, using init. Resources I have checked out don't seem to clarify my answer. Not sure the best way to do this.
I have a file called User.swift that contains a struct:
`struct UsersInfo {
let email: String
let password: String
let first: String
let last: String
let about: String
let age: Int
let city: String
let firebaseReference: DatabaseReference?
init(email: String, password: String, first: String, last: String, about: String, age: Int, city: String) {
self.email = email
self.password = password
self.first = first
self.last = last
self.about = about
self.age = age
self.city = city
self.firebaseReference = nil
}
init(snapshot: DataSnapshot) {
let snapshotValue = snapshot.value as! [String : AnyObject]
self.email = snapshotValue["email"] as! String
self.password = snapshotValue["password"] as! String
self.first = snapshotValue["first"] as! String
self.last = snapshotValue["last"] as! String
self.age = snapshotValue["age"] as! Int
self.city = snapshotValue["city"] as! String
self.about = snapshotValue["about"] as! String
self.firebaseReference = snapshot.ref
}
} // End Struct.
Then I have a file/view controller that imports Eureka using FormViewController. My Eureka rows that are set to values, are located in this file as followed:
// Get the value of a single row
let emailrow: EmailRow? = form.rowBy(tag: "emailRowTag")
let emailvalue = emailrow?.value
// Get the value of a single row
let passwordrow: PasswordRow? = form.rowBy(tag: "passwordRowTag")
let passwordvalue = passwordrow?.value
// Get the value of a single row
let firstrow: NameRow? = form.rowBy(tag: "firstRowTag")
let firstvalue = firstrow?.value
// Get the value of a single row
let lastrow: TextRow? = form.rowBy(tag: "lastRowTag")
let lastvalue = lastrow?.value
// Get the value of a single row
let aboutrow: TextRow? = form.rowBy(tag: "aboutRowTag")
let aboutvalue = aboutrow?.value
// Get the value of a single row
let agerow: IntRow? = form.rowBy(tag: "ageRowTag")
let agevalue = agerow?.value
// Get the value of a single row
let cityrow: PushRow<String>? = form.rowBy(tag: "cityRowTag")
let cityvalue = cityrow?.value
In this file/view controller, I have a custom button using ButtonRow from Eureka. I implemented a function to do this and called it onCellSelection. The values of rows is what confusing me with Firebase and what I need to put in my snapshotValue dictionary in the User.swift file and to execute in FormViewController. Thanks!
The first thing I did was give the row of type TextRow a tag
so I had something like
row.tag = "username"
Then after that I was able to retrieve that tag with the following code
let usernameRow: TextRow? = form.rowBy(tag: "username")
let username: String? = usernameRow?.cell.textField.text
Firebase only allows strings to push to the database so now that you have that, you can call the setValue function and pass the variable username
firebaseref.setValue("username": username)