0

I'm trying to make a fetch from my database to populate a collection view, in order of newest at the top, down to oldest. I tried using snap.children.allObjects.reversed(), but my app crashes upon loading. Here's the full fetch function:

func fetchPosts() {

    let ref = FIRDatabase.database().reference()
    ref.child("users").queryOrderedByKey().observe(.value, with: { snapshot in

        let users = snapshot.value as! [String : AnyObject]

        for (_, value) in users {

            if let uid = value["uid"] as? String {

                if uid == FIRAuth.auth()?.currentUser?.uid {

                    if let followingUsers = value["following"] as? [String : String] {

                        for (_, user) in followingUsers {
                            self.following.append(user)
                        }
                    }
                    self.following.append(FIRAuth.auth()!.currentUser!.uid)

                    ref.child("posts").queryOrderedByKey().observeSingleEvent(of: .value, with: { (snap) in

                        for postSnapshot in snap.children.allObjects.reversed() as! [FIRDataSnapshot] {
                            let value = postSnapshot.value as! [String : AnyObject]

                            if let userID = value["userID"] as? String {
                                for each in self.following {
                                    if each == userID {

                                        let posst = Post()
                                        if let poster = value["poster"] as? String, let likes = value["likes"] as? Int, let pathToImage = value["pathToImage"] as? String, let postID = value["postID"] as? String {

                                            posst.poster = poster
                                            posst.likes = likes
                                            posst.pathToImage = pathToImage
                                            posst.postID = postID
                                            posst.userID = userID
                                            if let people = value["peopleWhoLike"] as? [String : AnyObject] {
                                                for (_, person) in people {
                                                    posst.peopleWhoLike.append(person as! String)
                                                }
                                            }
                                            posts.append(posst)
                                        }
                                    }
                                }
                                self.collectionView.reloadData()
                            }
                        }
                    })
                    ref.removeAllObservers()
                }
            }
        }
    })
}

The error is EXC_BAD_INSTRUCTION (code=EXC_1386_INVOP, subcode=0x0), with the warning Cast from 'ReversedRandomAccessCollection<[Any]>' (aka 'ReversedRandomAccessCollection>') to unrelated type '[FIRDataSnapshot]' always fails.

Is .reversed not the way to go about this? As it is, my code without .reversed loads the posts in order from oldest at the top, down to the newest at the bottom. How can I switch it around?

EDIT: Firebase snippet of posts:

"posts" : {
"-KfWzWv8rP38bUreDupj" : {
  "likes" : 1,
  "pathToImage" : "https://firebasestorage.googleapis.com/v0/b/cloudcamerattt.appspot.com/o/posts%2F1JSgke8QqFds4CxF2Z4MhuzbRoW2%2F-KfWzWv8rP38bUreDupj.jpg?alt=media&token=fef86bea-1ae2-4e1e-82fa-6209bc281a5e",
  "peopleWhoLike" : {
    "-KfX29jTcwaQDpkdIVX8" : "yI6NokUl2mTa7Uah4SgtAiulTJH2",
    "-KfXQJBRemZUCI2ieT94" : "MpnGvQj7ZOdz12zKD0bTeX1kp0B3"
  },
  "postID" : "-KfWzWv8rP38bUreDupj",
  "poster" : "Harry Potter",
  "userID" : "1JSgke8QqFds4CxF2Z4MhuzbRoW2"
},

EDIT 2: Adding a timestamp

Added var timestamp: Int! to my Post object, then add it into my upload function:

func uploadToFirebase() {
    AppDelegate.instance().showActivityIndicator()

    let uid = FIRAuth.auth()!.currentUser!.uid
    let ref = FIRDatabase.database().reference()
    let storage = FIRStorage.storage().reference(forURL: "gs://cloudcamerattt.appspot.com")
    let key = ref.child("posts").childByAutoId().key
    let imageRef = storage.child("posts").child(uid).child("\(key).jpg")
    let data = UIImageJPEGRepresentation(self.previewImage.image!, 0.6)
    var Timestamp: TimeInterval {
        return NSDate().timeIntervalSince1970 * 1000
    }

    let uploadTask = imageRef.put(data!, metadata: nil) { (metadata, error) in
        if error != nil {
            print(error!.localizedDescription)
            AppDelegate.instance().dismissActivityIndicator()
            return
        }

        imageRef.downloadURL(completion: { (url, error) in

            if let url = url {
                let feed = ["userID" : uid,
                            "pathToImage" : url.absoluteString,
                            "likes" : 0,
                            "poster" : FIRAuth.auth()!.currentUser!.displayName!,
                            "postID" : key,
                            "timestamp" : (0-Timestamp)] as [String : Any]

                let postFeed = ["\(key)" : feed]
                ref.child("posts").updateChildValues(postFeed)
                AppDelegate.instance().dismissActivityIndicator()

                let feedController = self.storyboard?.instantiateViewController(withIdentifier: "feedVC") as! FeedViewController
                feedController.navigationItem.setHidesBackButton(true, animated: false)

                self.tabBarController?.selectedIndex = 0
            }
        })
    }
    uploadTask.resume()
}

Then add it into my fetch:

                                        let posst = Post()
                                        if let poster = value["poster"] as? String, let likes = value["likes"] as? Int, let pathToImage = value["pathToImage"] as? String, let postID = value["postID"] as? String, let timestamp = value["timestamp"] as? Int {

                                            posst.poster = poster
                                            posst.likes = likes
                                            posst.pathToImage = pathToImage
                                            posst.postID = postID
                                            posst.userID = userID
                                            posst.timestamp = timestamp

Updated fetch function (results in crash Could not cast value of type 'FIRDataSnapshot' (0x10584eee8) to 'NSArray' (0x107b43dd8).):

func fetchPosts() {

    let ref = FIRDatabase.database().reference()
    ref.child("users").queryOrderedByKey().observe(.value, with: { snapshot in

        let users = snapshot.value as! [String : AnyObject]

        for (_, value) in users {

            if let uid = value["uid"] as? String {

                if uid == FIRAuth.auth()?.currentUser?.uid {

                    if let followingUsers = value["following"] as? [String : String] {

                        for (_, user) in followingUsers {
                            self.following.append(user)
                        }
                    }
                    self.following.append(FIRAuth.auth()!.currentUser!.uid)

                    for child in snapshot.children.reversed() {
                        let snap = child as! [FIRDataSnapshot]

                    ref.child("posts").queryOrdered(byChild: "timestamp").observeSingleEvent(of: .value, with: { (snap) in

                            if let userID = value["userID"] as? String {
                                for each in self.following {
                                    if each == userID {

                                        let posst = Post()
                                        if let poster = value["poster"] as? String, let likes = value["likes"] as? Int, let pathToImage = value["pathToImage"] as? String, let postID = value["postID"] as? String, let timestamp = value["timestamp"] as? Int {

                                            posst.poster = poster
                                            posst.likes = likes
                                            posst.pathToImage = pathToImage
                                            posst.postID = postID
                                            posst.userID = userID
                                            posst.timestamp = timestamp
                                            if let people = value["peopleWhoLike"] as? [String : AnyObject] {
                                                for (_, person) in people {
                                                    posst.peopleWhoLike.append(person as! String)
                                                }
                                            }
                                            posts.append(posst)
                                        }
                                    }
                                }
                                self.collectionView.reloadData()
                            }
                        })
                    }
                    ref.removeAllObservers()
                }
            }
        }
    })
}
KingTim
  • 1,281
  • 4
  • 21
  • 29
  • It's generally a good idea to post a snippet of your Firebase Structure with your question; as *text* please, no images. You can get that JSON structure as text via the export (three dots the upper right) function in the Firebase console. – Jay Mar 21 '17 at 18:08
  • @Jay sorry, I'm adding a snippet now - I'm guessing of "posts" (which is what the fetch is grabbing to populate the feed)? – KingTim Mar 21 '17 at 18:14

2 Answers2

1

Try

for child in snapshot.children.reversed() {
     let snap = child as! FIRDataSnapshot
     print(snap)
}

You are ordering by key which will load the oldest to the newest. If you want to reverse the order, and let Firebase do the heavy lifting, use a technique for reverse chronological order posted here

In Firebase, how can I query the most recent 10 child nodes?

Then it's easy to do a reverse query...

"posts" : {
   "-KfWzWv8rP38bUreDupj" : {
      "likes" : 1,
      "pathToImage" : "https:/...",
      "peopleWhoLike" : {
        "-KfX29jTcwaQDpkdIVX8" : "yI6NokUl2mTa7Uah4SgtAiulTJH2",
        "-KfXQJBRemZUCI2ieT94" : "MpnGvQj7ZOdz12zKD0bTeX1kp0B3"
      },
      "postID" : "-KfWzWv8rP38bUreDupj",
      "poster" : "Harry Potter",
      "timestamp" : -1.46081635550362E12,   //Just add this child
      "userID" : "1JSgke8QqFds4CxF2Z4MhuzbRoW2"
},

and then

ref.child("posts").queryOrdered(byChild: "timestamp").observe(...

Also, the duplicate postID is probably not needed as a child as it's the key to the post as well.

Community
  • 1
  • 1
Jay
  • 34,438
  • 18
  • 52
  • 81
  • Thanks, I'm reading your other answer and am trying to add in a timestamp. I edited my post to show what I'm trying but my collection view isn't loading, I think because the posts that were there already don't have a timestamp? – KingTim Mar 21 '17 at 18:34
  • @KingTim Correct. If you want to let Firebase do the ordering, you will need to include the timestamp child node in all of the posts. You can do it in code but it's WAY faster to have Firebase do the ordering at the server level and feed you the data in the correct sequence. – Jay Mar 21 '17 at 18:45
  • Ok cool so I'm on the right track - but I'm not understanding your comment, should I not be adding the timestamp the way I'm doing it now then? Is there a different way to get Firebase to do the ordering? – KingTim Mar 21 '17 at 18:51
  • @KingTim All of the child nodes will need the timestamp child, per my above comment. The way you are doing is almost correct except you are writing the timestamp as a positive value - which would mean the older posts are going to be 'at the bottom'. You want the opposite so the timestamp needs to be written as a negative value; nodeRef.setValue( 0 - t1) //note the negative value. Re-read the linked post again and it explains it. – Jay Mar 21 '17 at 19:09
  • Instead of doing nodeRef, would it be the same thing if I set the negative value when I upload the timestamp in the dictionary with all the other values, with `"timestamp" : (0-Timestamp)`? I put my upload function in my edit. – KingTim Mar 22 '17 at 12:21
  • @KingTim I am not sure what you are asking. Nodes are key:value pairs so *nodeRef.setValue( 0 - t1)* and *someDict: [timestamp : (0-Timestamp)]* followed by *someNode:setValue(someDict)* are functionally equivalent. So I think the answer is yes (?) – Jay Mar 22 '17 at 14:09
  • Ok, I was asking because when I implemented that and ran the app, the feed did not show up at all, and even when I added a post it didn't show up in the app or in Firebase. This problem only occurred after adding the timestamp so I wasn't sure where in that process the mistake was. I have both the upload and fetch code in my question now, everything looks correct so I'm confused as to why the addition of the timestamp is keeping the posts from showing up. – KingTim Mar 22 '17 at 15:34
  • @KingTim Check your structure and make sure it looks like the one in my answer. If it does, then this query *ref.child("posts").queryOrdered(byChild: "timestamp").observeSingleEvent(of: .value, with: { (snap) in * works, as I just tested it. – Jay Mar 22 '17 at 17:44
0

You are correct that you need to cast something somewhere, because Swift only knows that we started with an array of Any. The problem is that you are casting the wrong thing in the wrong place. Cast postSnapshot at the start of the inside of the for loop.

The way to figure out this sort of thing is to make a simplified playground example. You are doing the equivalent of this:

let arr : [Any] = [1,2,3]
for i in arr.reversed() as! [Int] { // crash
}

What we know in that example, however, is not something about arr.reversed(); it is that i is an Int. This is fine:

let arr : [Any] = [1,2,3]
for i in arr.reversed() {
    if let i = i as? Int {
        // now it is safe to use `i`
    }
}

Your case is parallel. At the start of the for loop, you need to cast postSnapshot to a FIRDataSnapshot. Now you can proceed.

matt
  • 515,959
  • 87
  • 875
  • 1,141