-1

Can someone please look trough my code and tell me why my value is nil. If you need me to post other files I will. The project is mostly firebase and the app crashes whenever you try to post within the application. I believe it is crashing when the screen has to be updated because the data is hitting the database.

import UIKit
import FirebaseFirestore

class FeedVC: UITableViewController {



    var db = Firestore.firestore()
    var postArray = [Posts]()

    override func viewDidLoad() {
        super.viewDidLoad()

        //db = Firestore.firestore()
//loadData()
      // checkForUpdates()


    }
       override func viewDidAppear(_ animated: Bool){
        loadData()
        checkForUpdates()


    }

    func loadData() {
        db.collection("posts").getDocuments() {
            querySnapshot, error in
            if let error = error {
                print("\(error.localizedDescription)")
            }else{
                self.postArray = querySnapshot!.documents.flatMap({Posts(dictionary: $0.data())})
                DispatchQueue.main.async {
                    self.tableView.reloadData()
                }
            }
        }


    }

    func checkForUpdates() {
        db.collection("posts").whereField("timeStamp", isGreaterThan: Date())
            .addSnapshotListener {
                querySnapshot, error in

                guard let snapshot = querySnapshot else {return}

                snapshot.documentChanges.forEach {
                    diff in

                    if diff.type == .added {
                        self.postArray.append(Posts(dictionary: diff.document.data())!)
                        DispatchQueue.main.async {
                            self.tableView.reloadData()
                        }
                    }
                }

        }
    }

    @IBAction func composePost(_ sender: Any) {






        let composeAlert = UIAlertController(title: "New Post", message: "Enter your name and message", preferredStyle: .alert)

        composeAlert.addTextField { (textField:UITextField) in
            textField.placeholder = "Your name"
        }

        composeAlert.addTextField { (textField:UITextField) in
            textField.placeholder = "Your message"
        }

        composeAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))

        composeAlert.addAction(UIAlertAction(title: "Send", style: .default, handler: { (action:UIAlertAction) in

            if let name = composeAlert.textFields?.first?.text, let content = composeAlert.textFields?.last?.text {

                let newSweet = Posts(name: name, content: content, timeStamp: Date())

                var ref:DocumentReference? = nil
                ref = self.db.collection("posts").addDocument(data: newSweet.dictionary) {
                    error in

                    if let error = error {
                        print("Error adding document: \(error.localizedDescription)")
                    }else{
                        print("Document added with ID: \(ref!.documentID)")
                    }

                }

            }

        }))

        self.present(composeAlert, animated: true, completion: nil)


    }



    // MARK: - Table view data source
    override func numberOfSections(in tableView: UITableView) -> Int {

        return 0
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        return postArray.count
    }


    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)

        let sweet = postArray[indexPath.row]

        cell.textLabel?.text = "\(sweet.name): \(sweet.content)"
        cell.detailTextLabel?.text = "\(sweet.timeStamp)"

        return cell
    }
  }

import Foundation
import FirebaseFirestore

protocol DocumentSerializable  {
    init?(dictionary:[String:Any])
}


struct Posts {
    var name:String
    var content:String
    var timeStamp:Date

    var dictionary:[String:Any] {
        return [
            "name":name,
            "content" : content,
            "timeStamp" : timeStamp
        ]
    }

}

extension Posts : DocumentSerializable {
    init?(dictionary: [String : Any]) {
        guard let name = dictionary["name"] as? String,
            let content = dictionary["content"] as? String,
            let timeStamp = dictionary ["timeStamp"] as? Date else {return nil}

        self.init(name: name, content: content, timeStamp: timeStamp)
    }
}





  • It's crashing probably because you are updating table view data under viewDidLoad. – El Tomato Dec 31 '19 at 01:14
  • Where should I move it too? – chadmartin609 Dec 31 '19 at 01:20
  • The crash is at - self.postArray.append(Posts(dictionary: diff.document.data())!) – chadmartin609 Dec 31 '19 at 01:21
  • Try it with viewWillAppear or viewDidAppear. – El Tomato Dec 31 '19 at 01:24
  • I tried it in the viewwillAppear and the viewDidAppear and I am still getting the error. – chadmartin609 Dec 31 '19 at 01:40
  • I don't know what is 'it' unless you show IT. – El Tomato Dec 31 '19 at 01:49
  • Sorry, I added the loadData() and checkForUpdates() functions into the viewDidAppear function. The error is still present. – chadmartin609 Dec 31 '19 at 01:53
  • Set the number to 0 in table view's `numberOfRowsInSection` to see if the source of the crash is really about loading data. – El Tomato Dec 31 '19 at 01:57
  • I set the functions numberOfSections() return value to 0 and when I went to post I still got the error. Also data is still not lading on screen from DB. – chadmartin609 Dec 31 '19 at 02:01
  • It seems that `Posts(dictionary:)` is a failable initialiser and it is failing. Work out why. Also, don't force unwrap if you don't want to crash. – Paulw11 Dec 31 '19 at 02:05
  • Could there be something wrong with my Posts Class? I am a newbie and looking for help. I know this time that I can't find the answer. Could you work with me to slice this? – chadmartin609 Dec 31 '19 at 02:08
  • Change the 6th line to lazy var db = Firestore.firestore() and remove the corresponding one in the viewDidLoad guy. – El Tomato Dec 31 '19 at 02:09
  • If that is the line that it is crashing on then `Posts(dictionary:)` returned `nil`, so probably the data in the dictionary isn't what that initialiser wants. You need to look at that code and that dictionary – Paulw11 Dec 31 '19 at 02:11
  • I have updated the above code and added the Posts class. I Please take a look. – chadmartin609 Dec 31 '19 at 02:14
  • It is probably simplest if you set a breakpoint in that initialiser and step through to see which entry in your dictionary isn't what you want/expect. – Paulw11 Dec 31 '19 at 02:32
  • Your issue is probably at `dictionary["timestamp"] as? Date`. I am pretty sure what comes back from firebase usually is basic types, so string or int or double etc, Date is not one of them. You are trying to cast the string to date in the guard statement or it returns nil. Your `Posts(dictionary:)` is returning nil and you are force unwrapping a nil hence it crashes – Alexander Dec 31 '19 at 02:33
  • Okay, so how would I fix it. I am pretty new. – chadmartin609 Dec 31 '19 at 02:43

2 Answers2

1

use TimeStamp instead of Date

 import FirebaseFirestore 
    guard let stamp = data["timeStamp"] as? Timestamp else { return nil }

Jawad Ali
  • 13,556
  • 3
  • 32
  • 49
0

This is the line that's crashing according to the comments...

self.postArray.append(Posts(dictionary: diff.document.data())!)

and it's crashing because you are force-unwrapping a variable that's nil

Posts(dictionary: diff.document.data())! <- ! means 'I guarantee you will never be nil'

And it's nil because of this Posts extension...

extension Posts : DocumentSerializable {
    init?(dictionary: [String : Any]) {
        guard let name = dictionary["name"] as? String,
            let content = dictionary["content"] as? String,
            let timeStamp = dictionary ["timeStamp"] as? Date else {return nil}

and the offending code is here

guard 
   let name = dictionary["name"] as? String,
   let content = dictionary["content"] as? String,
   let timeStamp = dictionary ["timeStamp"] as? Date 

else {return nil} //OFFENDING CODE!

If any one of those vars is not present in the dictionary, it fails, and returns nil.

So... one of your documents does not contain one of those fields.

You may also want to refer to the Firestore Guide on how to write dates. Check out the example code in Add Data

Also, Firestore doesn't have an internal Date type so use Timestamp.dateValue instead. See it here Timestamp

Jay
  • 34,438
  • 18
  • 52
  • 81