Added property to struct which in Swift - invalidates existing objects - swift

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
]
}

Related

How to know which initializer to use for reading data(Firebase)

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

Swift 4 Unwrapping Dictionary from Firebase

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!

Swift Retrieving Data Firebase

I'm trying to retrieve string key which I have saved it in the object
func retrieveData() {
let refAll = Database.database().reference().child("Playground")
refAll.observe(.value) { (snapshot) in
if let snapshotValue = snapshot.value as? [String:Any] {
var playgroundSnapshot = snapshotValue
let playgroundKeys = Array(playgroundSnapshot.keys)
self.playgroundArray.removeAll()
for key in playgroundKeys {
guard
let value = playgroundSnapshot[key] as? [String : Any]
else {
continue
}
let title = value["title"] as! String
let city = value["city"] as! String
let location = value["location"] as! String
let price = value["price"] as! String
let playground = Playground(title: title, price: price, location: location, city: city, availblePlayground: true)
self.playgroundArray.append(playground)
}
self.tabeView.reloadData()
}
}
}
and in the playgroundArray there is key for each object
keySelected = playgroundArray[indexPath.row].key
but I don't know why keySelected is nil even tho playgroundArray has objects
playgroundArray does not contain "key" . You have to set the Playground Struct. Add var key:String? and also add this to init() func.
let playground = Playground(title: title, price: price, location: location, city: city, availblePlayground: true)
to
let playground = Playground(title: title, price: price, location: location, city: city, availblePlayground: true, key: key)
self.playgroundArray.append(playground)

Cannot convert value of type 'String.Type' to expected argument type 'String'

Swift 4 / Xcode 9.3 / OS X 10.13.4 / iOS 11.3 & 11.2.6
I'm trying to build my app and I'm getting the above error message. I've checked the code over and over and over and I can't figure out why I'm getting this error. I'm not certain which part of the code you need to see, but here is the page I'm getting the error on. The error code is flagging the very last line of code.
import UIKit
import os.log
class Bonus: NSObject, NSCoding {
//MARK: Archiving Paths
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("bonuses")
//MARK: Properties
var bonusCode: String
var category: String
var name: String
var value: Int
var city: String
var state: String
var photo: UIImage?
//MARK: Initialization
init?(bonusCode: String, category: String, name: String, value: Int, city: String, state: String, photo: UIImage?) {
// The name must not be empty.
guard !name.isEmpty else {
return nil
}
// The value must not be negative.
guard (value >= 0) else {
return nil
}
// Initialize stored properties.
self.bonusCode = bonusCode
self.category = category
self.name = name
self.value = value
self.city = city
self.state = state
self.photo = photo
}
//MARK: Types
struct PropertyKey {
static let bonusCode = "bonusCode"
static let category = "category"
static let name = "name"
static let value = "value"
static let city = "city"
static let state = "state"
static let photo = "photo"
}
//MARK: NSCoding
func encode(with aCoder: NSCoder) {
aCoder.encode(bonusCode, forKey: PropertyKey.bonusCode)
aCoder.encode(category, forKey: PropertyKey.category)
aCoder.encode(name, forKey: PropertyKey.name)
aCoder.encode(value, forKey: PropertyKey.value)
aCoder.encode(city, forKey: PropertyKey.city)
aCoder.encode(state, forKey: PropertyKey.state)
aCoder.encode(photo, forKey: PropertyKey.photo)
}
required convenience init?(coder aDecoder: NSCoder) {
// The name is required. If we cannot decode a name string, the initializer should fail.
guard let bonusCode = aDecoder.decodeObject(forKey: PropertyKey.bonusCode) as? String else {
os_log("Unable to decode the Code for a Bonus object.", log: OSLog.default, type: .debug)
return nil
}
// Because photo is an optional property of Meal, just use conditional cast
let photo = aDecoder.decodeObject(forKey: PropertyKey.photo) as? UIImage
let category = aDecoder.decodeObject(forKey: PropertyKey.category)
let value = aDecoder.decodeInteger(forKey: PropertyKey.value)
let city = aDecoder.decodeObject(forKey: PropertyKey.city)
let state = aDecoder.decodeObject(forKey: PropertyKey.state)
// Must call designated initializer.
self.init(bonusCode: String, category: String, name: String, value: Int, city: String, state: String, photo: UIImage?)
}
}
The error is flagging on the bonusCode: String, specifically on the S in String.
I'm pretty new to programming, but I only found one other search result for this specific question, and the other similar ones seemed to be very specific to the code being used.
You have to pass the decoded values rather than the types in the last line and the line to decode the name is missing and you have to cast the other string objects. The force unwrapping is safe because all non-optional values are encoded properly.
let name = aDecoder.decodeObject(forKey: PropertyKey.name) as! String
let category = aDecoder.decodeObject(forKey: PropertyKey.category) as! String
let value = aDecoder.decodeInteger(forKey: PropertyKey.value)
let city = aDecoder.decodeObject(forKey: PropertyKey.city) as! String
let state = aDecoder.decodeObject(forKey: PropertyKey.state) as! String
...
self.init(bonusCode: bonusCode, category: category, name: name, value: value, city: city, state: state, photo: photo)
self.init(bonusCode: String,
category: String,
name: String,
value: Int,
city: String,
state: String,
photo: UIImage?)
This is a function call, not a function declaration.
You are passing types instead of values into a function call.
You should be doing this instead:
self.init(bonusCode: bonusCode,
category: category,
name: name,
value: value,
city: city,
state: state,
photo: photo)
So finally, your init should look like (with a little improvement):
required convenience init?(coder aDecoder: NSCoder) {
//NOTE: `decodeObject(forKey:)` returns optional `Any` and hence all those `as? String`
//name was missing
let name = aDecoder.decodeObject(forKey: PropertyKey.name) as? String
let bonusCode = aDecoder.decodeObject(forKey: PropertyKey.bonusCode) as? String
let category = aDecoder.decodeObject(forKey: PropertyKey.category) as? String
let value = aDecoder.decodeInteger(forKey: PropertyKey.value)
let city = aDecoder.decodeObject(forKey: PropertyKey.city) as? String
let state = aDecoder.decodeObject(forKey: PropertyKey.state) as? String
let photo = aDecoder.decodeObject(forKey: PropertyKey.photo) as? UIImage
/*
Only photo is optional in order to init but the rest are required
Hence the optional binding for the rest below
*/
if let name = name,
let bonusCode = bonusCode,
let category = category,
let city = city,
let state = state {
// Must call designated initializer.
self.init(bonusCode: bonusCode,
category: category,
name: name,
value: value,
city: city,
state: state,
photo: photo)
}
else {
/*
Some required object/s were missing so we can't call the
designated initializer unless we want to give default values.
Hence return nil
*/
return nil
}
}

possible to cast this Alamofire result to an array of dictionaries

I am not an iOS dev and have to make a few changes to a Swift / AlamoFire project (not mine) and am a bit lost.
I have the following JSON:
{"metro_locations":
[
{
"name":"Ruby Red"
},
{
"name":"Blue Ocean"
}
]
}
class (I know that there are issues here):
class Location{
var name=""
init(obj:tmp){
self.name=tmp["name"]
}
}
and need to make an AlamoFire call
Alamofire.request(.GET, "https://www.domain.com/arc/v1/api/metro_areas/1", parameters: nil)
.responseJSON { response in
if let dataFromNetworking = response.result.value {
let metroLocations = dataFromNetworking["metro_locations"]
var locations: [Location]=[]
for tmp in metroLocations as! [Dictionary] { // <- not working, Generic Paramter 'Key' could not be inferred
let location=Location.init(obj: tmp)
locations.append(location)
}
}
}
I have included the error msg, the "not working" but feel that there are issues in other parts too (like expecting a dictionary in the initialization). What does the 'Key' could not be inferred mean and are there other changes I need to make?
edit #1
I have updated my Location to this to reflect your suggestion:
init?(dictionary: [String: AnyObject]) {
guard let id = dictionary["id"] else { return nil }
guard let name = dictionary["name"] else { return nil }
guard let latitude = dictionary["latitude"] else { return nil }
guard let longitude = dictionary["longitude"] else { return nil }
self.name = name as! String
self.id = id as! Int
self.latitude = latitude as! Double
self.longitude = longitude as! Double
}
but I get the error:
Could not cast value of type 'NSNull' (0x10f387600) to 'NSNumber' (0x10f77f2a0).
like this:
I would think that the guard statement would prevent this. What am I missing?
You can cast metroLocations as an array of dictionaries, namely:
Array<Dictionary<String, String>>
Or, more concisely:
[[String: String]]
Thus:
if let dataFromNetworking = response.result.value {
guard let metroLocations = dataFromNetworking["metro_locations"] as? [[String: String]] else {
print("this was not an array of dictionaries where the values were all strings")
return
}
var locations = [Location]()
for dictionary in metroLocations {
if let location = Location(dictionary: dictionary) {
locations.append(location)
}
}
}
Where
class Location {
let name: String
init?(dictionary: [String: String]) {
guard let name = dictionary["name"] else { return nil }
self.name = name
}
}
Clearly, I used [[String: String]] to represent an array of dictionaries where the values were all strings, as in your example. If the values included objects other than strings (numbers, booleans, etc.), then you might use [[String: AnyObject]].
In your revision, you show us a more complete Location implementation. You should avoid as! forced casting, and instead us as? in the guard statements:
class Location {
let id: Int
let name: String
let latitude: Double
let longitude: Double
init?(dictionary: [String: AnyObject]) {
guard let id = dictionary["id"] as? Int,
let name = dictionary["name"] as? String,
let latitude = dictionary["latitude"] as? Double,
let longitude = dictionary["longitude"] as? Double else {
return nil
}
self.name = name
self.id = id
self.latitude = latitude
self.longitude = longitude
}
}