how to add information in an array with a class Swift - ios

I've got a problem for adding some informations in an array.
My class Flights is define by the following :
class Flight{
let date: String
let type: String
let regi: String
let totalTime: String
let depTime: String
let depPlace: String
let arrTime: String
let arrPlace: String
init(from dat: String, _ typ: String, _ reg: String, _ totaltim: String, _ depTim: String, _ depPlac: String, _ arrTim: String, _ arrPlac: String) {
self.date = dat
self.type = typ
self.regi = reg
self.totalTime = totaltim
self.depTime = depTim
self.depPlace = depPlac
self.arrTime = arrTim
self.arrPlace = arrPlac
}}
In my main code I've got declare my array like this :
var datas: [Flight] = []
And finally I've this code to add some informations coming from firebase :
(I add some comment to show you what print() result)
if let user = Auth.auth().currentUser{
// user is connect
let ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let ev = ref.child("flights").child(userID!)
ev.observe(.childAdded, with: { (snapshot) -> Void in
let flightKey = snapshot.key
ref.child("flights").child(userID!).child(flightKey).observeSingleEvent(of: .value) {(snapshot) in
let value = snapshot.value as? NSDictionary
let date = value?["Date"] as? String ?? "no date"
let type = value?["aircraft-model"] as? String ?? "no type"
let registration = value?["aircraft-registration"] as? String ?? "no callsign"
let totalTime = value?["TOTAL-TIME"] as? String ?? "no total Time"
let deppartTime = value?["departure-time"] as? String ?? "no departure Time"
let deppartPlace = value?["departure-place"] as? String ?? "no departure Place"
let arrivalTime = value?["arrival-time"] as? String ?? "no arrival Time"
let arrivalPlace = value?["arrival-place"] as? String ?? "no arrival Place"
print("Date : \(date) - type : \(type) - registration : \(registration) - Etc ...")// Give me exactly the value I requested
self.datas.append(Flight(from: date, type, registration, totalTime, deppartTime, deppartPlace, arrivalTime, arrivalPlace))
print(self.datas)// Give me "MyProjectName.Flight ...
}
})
}else{
// si non connecté alors DECONNEXION !!!!
fatalError("error ...")
}
So I don't understand why if I print the received value from firebase it work but if I print the array value which is completed by the firebase received value it didn't work ?
Thanks for your help !
Flyer-74

Welcome :)
I think all is as expected and you're just seeing this because Swift doesn't know how to describe your objects.
To fix this, you should implement the CustomStringConvertible protocol in your Flight class (https://developer.apple.com/documentation/swift/customstringconvertible)
So something like
extension Flight: CustomStringConvertible {
var description: String {
var description = ""
description.append("date: \(date)\n")
description.append("type: \(type)\n")
description.append("regi: \(regi)\n")
//and so on
return description
}
}
Should give you what you are looking for.
Hope that helps you

You can try to adopt CustomStringConvertible protocol
class Flight : CustomStringConvertible {
var description: String {
return "\(date) \(type)" // add here any variable you want it to be printed
}
let date: String
let type: String
let regi: String
let totalTime: String
let depTime: String
let depPlace: String
let arrTime: String
let arrPlace: String
init(from dat: String, _ typ: String, _ reg: String, _ totaltim: String, _ depTim: String, _ depPlac: String, _ arrTim: String, _ arrPlac: String) {
self.date = dat
self.type = typ
self.regi = reg
self.totalTime = totaltim
self.depTime = depTim
self.depPlace = depPlac
self.arrTime = arrTim
self.arrPlace = arrPlac
}
}

You could add a custom debug description for your object by adding an extension to Flight, and make it conform to the CustomDebugStringConvertible protocol. Conformance to this protocol requires that you provide a property: var debugDescription: String { get }. Inside this string is where you have full control over the debug values for your custom Object.
extension Flight: CustomDebugStringConvertible {
var debugDescription: String {
return "Date: \(date), Type: \(type), Registartion: \(regi)"
}
}

Related

Creating a struct that conforms to the encodable protocol gives me an error due to a timestamp being a variable. Is there a way to fix this?

import Firebase
import UIKit
//I followed the information you gave me. I am unsure if I have done that correctly or as you were expecting it. But, it gives the same error for codable. "Type post doesn't conform to protocol decodable".
import Firebase
import UIKit
struct Post: Codable {
var caption: String
var likes: Int
var imageUrl: String
var ownerUid: String
var postId: String
var ownerImageUrl: String
var ownerUsername: String
var didLike = false
var hashtags: [String]
var activity: [String]
var video: String
var videoURL: URL
var videoFileExtension: String?
var music: String
private var timestampDate: Date
var timestamp: Timestamp { Timestamp(date: timestampDate) }?
enum CodingKeys: String, CodingKey {
case caption
case likes
case imageUrl
case ownerUid
case timestamp
case postId
case ownerImageUrl
case ownerUsername
case didLike
case hashtags
case activity
case video
case videoURL
case videoFileExtension
case music
}
init(postId: String, dictionary: [String: Any]) {
self.postId = dictionary["postId"] as? String ?? ""
self.caption = dictionary["caption"] as? String ?? ""
self.likes = dictionary["likes"] as? Int ?? 0
self.imageUrl = dictionary["imageUrl"] as? String ?? ""
self.ownerUid = dictionary["ownerUid"] as? String ?? ""
self.ownerImageUrl = dictionary["ownerImageUrl"] as? String ?? ""
self.ownerUsername = dictionary["ownerUsername"] as? String ?? ""
self.hashtags = dictionary["hashtags"] as? [String] ?? [String]()
self.activity = dictionary["activity"] as? [String] ?? [String]()
self.video = dictionary["video"] as? String ?? ""
self.videoURL = dictionary["videoURL"] as? URL ?? URL(fileURLWithPath: "")
self.music = dictionary["music"] as? String ?? ""
if let asDouble = dictionary["timestamp"] as? Double { self.timestampDate = Date(timeIntervalSince1970: asDouble) } else { self.timestampDate = Date() }
}
//Here I am using JSONEncoder to be called in other parts of the code and to //help process the data to firebase
var dictionary: [String: Any] {
let data = (try? JSONEncoder().encode(self)) ?? Data()
return (try? JSONSerialization.jsonObject(with: data, options: [.mutableContainers, .allowFragments]) as? [String: Any]) ?? [:]
}
}
The compiler will not synthesize Codable for you since you have a coding key for a computed property. This is not supported, auto synthesis only works with stored properties. If you remove timestamp from your CodingKeys enum it should work fine, but your encoded JSON won’t contain the timestamp. If you need that in your output or parse it from input you will have to implement Codable yourself.
Upon the initial question:
A way to do that would be to keep the Date as private, and use Timestamp as a computed value:
private var timestampDate: Date
var timestamp: Timestamp { Timestamp(date: timestampDate) }
This need a little changes in the CodingKeys, because timestamp doesn't exists for it, but timestampDate does now:
enum CodingKeys: String, CodingKey {
...
case timestampDate = "timestamp"
}
Now, there are still a few issues.
self.videoURL = dictionary["videoURL"] as? URL ?? URL(fileURLWithPath: "")
This shouldn't work, since you are getting JSON, and URL isn't really a JSON value.
Instead:
let videoURLString = dictionary["videoURL"] as? String ?? ""
self.videoURL = URL(fileURLWithPath: videoURLString)
Now, you might have an issue with the Date value, you need to tell the encoder what's the logic:
var dictionary: [String: Any] {
do {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .secondsSince1970
let data = try encoder.encode(self)
let dict = try JSONSerialization.jsonObject(with: data)
return dict as? [String: Any] ?? [:]
} catch {
print("Error: \(error)")
return [:]
}
}
I used as reference date 1970, depending on your settings, you might change it when encoding or decoding.
Also, I did proper do/try/catch, please don't write try?. If there is an error, you won't see it, you are just ignoring them.
Now, it's unrelated, but in the init(postId:dictionary:) you don't read postId value. Did you meant self.postId = dictionary["postId"] as? String ?? postId ?
Instead of using dictionary["someValue"], why not use dictionary[CodingKeys.someValue.rawValue], avoiding you any typo error?

How can I create a UITableView for each property in a model?

I have a struct that looks something like this:
internal class RemoteProfileModel: Decodable {
let userId: String
let company: String
let email: String
let firstName: String
let lastName: String
let department: String
let jobTitle: String
let pictureUri: URL?
let headerUri: URL?
let bio: String
let updatedDate: Date
}
I need to list out these properties in a UITableView. I also need to use different cell types for some of the properties.
I'm thinking perhaps I should convert this struct to a dictionary of key/value pairs, and use the key to determine the cell type.
Is this possible? Is there another way to achieve this? I am unsure if it possible to convert a struct to a dictionary so am not sure this is the best way?
to convert a class to a dictionary,
class RemoteProfileModel: Decodable {
let userId: String
let company: String
let email: String
let firstName: String
let lastName: String
let department: String
let jobTitle: String
let pictureUri: URL?
let headerUri: URL?
let bio: String
let updatedDate: Date
init() {
userId = "666"
company = "AAPL"
email = "hehe#163.com"
firstName = "user"
lastName = "test"
department = "guess"
jobTitle = "poor iOS"
pictureUri = URL(string: "wrong")
headerUri = URL(string: "none")
bio = "China"
updatedDate = Date()
}
func listPropertiesWithValues(reflect: Mirror? = nil) -> [String: Any]{
let mirror = reflect ?? Mirror(reflecting: self)
if mirror.superclassMirror != nil {
self.listPropertiesWithValues(reflect: mirror.superclassMirror)
}
var yourDict = [String: Any]()
for (index, attr) in mirror.children.enumerated() {
if let property_name = attr.label {
//You can represent the results however you want here!!!
print("\(index): \(property_name) = \(attr.value)")
yourDict[property_name] = attr.value
}
}
return yourDict
}
}
Call like this:
let profile = RemoteProfileModel()
profile.listPropertiesWithValues()
In Swift Debugging and Reflection,
A mirror describes the parts that make up a particular instance

Firebase print snapshot works fine and fetches the dictionary but when tried to access individual field data, returns nil for certain fields

I have a problem accessing values from certain fields in my firebase database. Right now this is how my structure looks in firebase:
messages:
messageId:
fromId:
text:
timestamp:
toId:
I am able to successfully upload the data to firebase when a user inputs a message to another user. And I am also able to successfully print the snapshot. But when I set the dictionary values and access it, only "fromId" and "toId" works but "timestamp" and "text" returns a nil value.
Pretty sure there is some sort of a wrong implementation in terms of taking the snapshot values and setting it. For your reference, I have included 3 files, one where the data model is defined, one where I upload data to firebase and one where I am trying to print it but I get nil.
The file where I am trying to print data but I get nil. Note: I am only getting nil when I am trying to print "text" and "timestamp" field values. "fromId" and "toId" works.
import UIKit
import Firebase
class MessagesController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
observeMessages()
}
var messages = [Message]()
func observeMessages(){
let ref = Database.database().reference().child("messages")
ref.observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as?
Dictionary<String, AnyObject>{
let message = Message(dictionary: dictionary)
print(message.text)
print(message.fromId)
print(message.toId)
print(timestamp)
}
})
}
}
This is how I am uploading the data to firebase using a handle send function once the user has entered some text in the text box
#objc func handleSend(){
let ref = Database.database().reference().child("messages")
let childRef = ref.childByAutoId()
let toId = user!.uid!
let fromId = Auth.auth().currentUser!.uid
let timestamp: Int = Int(NSDate().timeIntervalSince1970)
let values = ["fromId": fromId, "text":
inputTextField.text!, "timestamp": timestamp, "toId": toId] as
[String : Any]
childRef.updateChildValues(values)
}
Finally this is how I have declared my messages class:
class Message{
var fromId: String!
var text: String!
var timestamp: Int!
var toId: String!
init(dictionary: Dictionary<String, AnyObject>) {
if let text = dictionary["messageText"] as? String {
self.text = text
}
if let fromId = dictionary["fromId"] as? String {
self.fromId = fromId
}
if let toId = dictionary["toId"] as? String {
self.toId = toId
}
if let timestamp = dictionary["creationDate"] as? Int {
self.timestamp = timestamp
}
}
}
When I print message.fromID, I get the data results in the console but when I print message.text or message.timestamp I get
nil
nil
nil
So in summary snapshot works, fromId, toID fields also work but for some reason the data from the text and timestamp fields are returned as nil
your are accessing values from dictionary with invalid key use text instead of messageText and use timeSamp instead of creationDate. like below
class Message{
var fromId: String!
var text: String!
var timestamp: Int!
var toId: String!
init(dictionary: Dictionary<String, AnyObject>) {
if let text = dictionary["text"] as? String {
self.text = text
}
if let fromId = dictionary["fromId"] as? String {
self.fromId = fromId
}
if let toId = dictionary["toId"] as? String {
self.toId = toId
}
if let timestamp = dictionary["timestamp"] as? Int {
self.timestamp = timestamp
}
}
}

update values from struct to Firestore in Swift

I want to update values from struct,This is my struct code.
struct Usersdata {
let uid:String?
let facebook:String?
let google : String?
let name : String?
let age : Int?
let birthday : String?
let smokeage : Int?
let smokeaddiction : Int?
let smokebrand : String?
let gold : Int?
let score : Int?
let fish : Int?
let shit : Int?
let userimage : String?
init(aDoc: DocumentSnapshot) {
self.uid = aDoc.get("uid") as? String ?? ""
self.facebook = aDoc.get("facebook") as? String ?? ""
self.google = aDoc.get("google") as? String ?? ""
self.name = aDoc.get("name") as? String ?? ""
self.age = aDoc.get("age") as? Int ?? 0
self.birthday = aDoc.get("birthday") as? String ?? ""
self.smokeage = aDoc.get("smokeage") as? Int ?? 0
self.smokeaddiction = aDoc.get("smokeaddiction") as? Int ?? 0
self.smokebrand = aDoc.get("smokebrand") as? String ?? ""
self.gold = aDoc.get("gold") as? Int ?? 0
self.score = aDoc.get("score") as? Int ?? 0
self.fish = aDoc.get("fish") as? Int ?? 0
self.shit = aDoc.get("shit") as? Int ?? 0
self.userimage = aDoc.get("userimage") as? String ?? ""
}
}
I got values from my query func like this
func queryAUser() {
let docRef = self.db.collection("Users").document(userID).collection("userdata").document("userdata")
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let aUser = Usersdata(aDoc: document)
self.local_userdata = aUser
} else {
print("Document does not exist")
}
}
}
I can use local_userdata to get my values , but I want to update new values to Firestore now .
Are there any solutions to update values from Usersdata.struct?
Let me give this a shot.
Assuming you have read in a user using the code in your question and the user is stored in self.local_userdata
Here's an example function to update a users name given a users uid and a new name
func updateUserName(withUid: String, toNewName: String) {
self.db.collection("users").document(withUid).setData( ["name": toNewName], merge: true)
}
To use this, read self.local_userdata.uid so we know which user we want to modify and pass in that uid and what the new name should be.
You could enhance this further to update any field for a certain user with this
func updateUserField(withUid: String, andField: String, toNewValue: String) {
self.db.collection("users").document(withUid).setData( [andField: toNewValue], merge: true)
}
of course if you are changing the fields you should also update the structure accordingly
self.local_userdata.name = "some name"

How to work with Firebase without allowing optional values

I'm new to iOS development and I understand that allowing optional values when an object is initialized is not a 'good citizen' technique. That being said, I've read that it is good practice to always have values set, like this:
class Item{
var name: String
var color: String
init(name: String, color: String) {
self.name = name
self.color = color
}
}
This looks nice and tidy but how can I do something like that working with Firebase? Look what I've got so far:
private func loadPosts(){
databaseHandle = ref.child("users/\(self.user.uid)/posts").observe(.value, with:{(snapshot) in
var newPosts = [Post]()
for itemSnapShot in snapshot.children {
let post = Post(snapshot: itemSnapShot as! FIRDataSnapshot)
newPosts.append(post!)
}
self.posts = newPosts
self.tableView.reloadData()
})
}
This guy is placed in my PostsViewController where I have my table view. This is my model:
class Post {
var ref: FIRDatabaseReference?
var title: String?
var answer: String?
var contentUrl: String?
var photoUrl: String?
var createdAt: String?
var feeling: String?
var kind: String?
var text: String?
var uid: String?
var measurements: Dictionary<String, String>?
//MARK: Initialization
init?(snapshot: FIRDataSnapshot){
ref = snapshot.ref
let data = snapshot.value as! Dictionary<String, Any>
title = data["title"]! as? String
answer = data["answer"] as? String
contentUrl = data["content_url"] as? String
photoUrl = data["photo_url"] as? String
createdAt = data["created_at"] as? String
feeling = data["feeling"] as? String
kind = data["kind"] as? String
text = data["text"] as? String
uid = data["uid"] as? String
measurements = data["measurements"] as? Dictionary<String, String>
}
}
I don't know exactly why but those question marks doesn't feel quite right and now and then I get some nil pointer error, which I think I should be able to avoid by using the 'good citizen' technique.
So, does anybody know how can I use Firebase following Swift best practices?
Either you wish to allow the properties of your Post class to be nil or you don't.
If you do, that's fine. The code you posted allows any of them to be nil. You just need to safely access each property every time you need it.
If you don't, then don't make them optional. Then in your init you need to ensure none of the properties are set to nil by giving each a default if there is no value in the snapshot.
class Post {
var ref: FIRDatabaseReference
var title: String
var answer: String
var contentUrl: String
var photoUrl: String
var createdAt: String
var feeling: String
var kind: String
var text: String
var uid: String
var measurements: [String : String]
//MARK: Initialization
init?(snapshot: FIRDataSnapshot) {
if let data = snapshot.value as? [String : Any] {
self.ref = snapshot.ref
title = data["title"] as? String ?? ""
answer = data["answer"] as? String ?? ""
contentUrl = data["content_url"] as? String ?? ""
photoUrl = data["photo_url"] as? String ?? ""
createdAt = data["created_at"] as? String ?? ""
feeling = data["feeling"] as? String ?? ""
kind = data["kind"] as? String ?? ""
text = data["text"] as? String ?? ""
uid = data["uid"] as? String ?? ""
measurements = data["measurements"] as? [String : String] ?? [:]
} else {
return nil
}
}
}
Note how this ensures there is a proper snapshot. Note how a default value is set to each property if there is no value in the snapshot. Obviously you can assign any default you wish. I use the empty string as an example.
Even if you want to allow the properties to be nil, you should at least update your code to check for a valid snapshot like in the code above.
Of course you can have a combination where some properties can't be nil and some can. That's up to your needs.
First it is fine for you to have optionals in your data model, as long as you assign value to it later on in the future.
I would recommend to use ObserveSingleEvent() and you should make use of completion handler to make it easy. If you don't know completion handler: Link
I recommend:
• not to put database ref in your class model, and instead of using Dictionary<String, String>? just use [String: AnyObject]?
• make your post array public so that it can be accessed into the tableview.
Here's example:
class func getPosts(uid: String, _ completion: #escaping (_ posts: [Post]?, _ error: Error?) -> Void) {
//update inside users node
var posts = [Post]()
Firebase.databaseRef.child("users").child(uid).child("posts").observeSingleEvent(of: FIRDataEventType.value, with: { (dataSnapshot) in
guard let postsDictionary = dataSnapshot.value as? [String: AnyObject] else {
completion(nil, nil)
return
}
let n = postsDictionary.count
for postDictionary in postsDictionary {
let post = Post()
post.userID = uid
if let content = postDictionary.value["content"] as? String {
post.content = content
}
if let imageURL = postDictionary.value["imageURL"] as? String {
post.imageURL = imageURL
}
if let timeStamp = postDictionary.key as String! {
if let date = timeStamp.convertToDate() {
post.timeStamp = date
}
post.postIdentifier = timeStamp
}
posts.append(post)
if posts.count == n {
// Sort the array by the newest post
let sortedPosts = posts.sorted(by: { $0.timeStamp.compare($1.timeStamp) == .orderedDescending })
completion(sortedPosts, nil)
}
}
}) { (error) in
completion(nil, error)
}
}
Assigning to tableview be like:
getPosts(uid: Current.user.userID!) { (posts, error) in
guard error == nil else {
print(error.debugDescription)
return
}
cell.label.text = posts[indexPath.item].content

Resources