简体   繁体   English

新的 Firebase 数据导致 TableView 单元格闪烁 (Firebase/iOS/Swift)

[英]New Firebase Data Causes Flicker of TableView Cells (Firebase/iOS/Swift)

My main question is how to get rid of the flicker, but I also just want to know if I am working with denormalized Firebase data correctly, and most efficiently.我的主要问题是如何摆脱闪烁,但我也只想知道我是否正确且最有效地处理非规范化 Firebase 数据。 Is my approach anywhere near being correct?我的方法是否接近正确?

So I am struggling with trying to properly display the data from a firebase database with data that's been denormalized.因此,我正在努力尝试使用非规范化的数据正确显示来自 Firebase 数据库的数据。 I have posts, and then comments associated with each post.我有帖子,然后是与每个帖子相关的评论。 Every time somebody opens up the comments section of a post, by segueing from a viewcontroller to a new viewcontroller, it grabs the unique key for the post (postKey), and then scans the group of comments associated with the postKey contained in the postCommentGroup.每次有人打开帖子的评论部分时,通过从视图控制器转移到新的视图控制器,它会获取帖子的唯一键 (postKey),然后扫描与包含在 postCommentGroup 中的 postKey 关联的评论组。 The group of comments, which are children of each postKey in the postCommentGroup are just the commentKey as key and "true" as the value, which indicates which comments are associated with which posts.评论组,即 postCommentGroup 中每个 postKey 的子项,只是将 commentKey 作为键,“true”作为值,表示哪些评论与哪些帖子相关联。 The comments are in an entirely different branch as that is what I think the Firebase documentation suggests one should do.评论在一个完全不同的分支中,因为我认为 Firebase 文档建议人们应该这样做。

I essentially have 3 layers of nested observers.我基本上有 3 层嵌套观察者。

For the sake of clarity I'm recycling the cells with dequeuereusablecells in a tableview, and I also have a rudimentary lazy load/image caching mechanism that might be interfering with things too, but I have the same mechanism on other less complicated tableviews so I don't think that's the problem.为了清楚起见,我在 tableview 中使用 dequeuereusablecells 回收单元格,并且我还有一个基本的延迟加载/图像缓存机制,它也可能会干扰事情,但我在其他不太复杂的 tableviews 上有相同的机制,所以我不要认为这是问题所在。

Due to my lack of knowledge I don't know how else to display the data other than going through this cycle.由于我缺乏知识,除了经历这个循环之外,我不知道如何显示数据。 I think this cycle, may be causing the flicker, but I don't know how else to make it load the data.我认为这个循环,可能会导致闪烁,但我不知道如何让它加载数据。 I have tried various other ways of doing it, such as using a query, but I've never been able to get it to work.我尝试了其他各种方法,例如使用查询,但我一直无法让它工作。

As a side note, I've tried to get up to speed on how to query data (which I assume might help me), but there's been an update to the syntax of Swift, and also an update to Firebase, making the previous examples a little difficult to follow.作为旁注,我试图加快了解如何查询数据(我认为这可能对我有帮助),但是 Swift 的语法已经更新,Firebase 也有更新,使前面的例子有点难以遵循。

Also, I just can't find good, recent examples of properly using denormalized data in a somewhat complex way in any of the Firebase documentation, either on the Firebase site or on Github.另外,我在 Firebase 站点或 Github 上的任何 Firebase 文档中都找不到以某种复杂的方式正确使用非规范化数据的好的最近示例。 Does anyone know of good reference material to look at with regard to working with denormalized data using Swift 3.0 and Firebase (newest version - not the legacy version), whether it's a project on GitHub, or a blog, or just a collection of the most useful posts on stackoverflow?有没有人知道关于使用 Swift 3.0 和 Firebase(最新版本 - 不是旧版本)处理非规范化数据的良好参考资料,无论是 GitHub 上的项目,还是博客,或者只是最stackoverflow 上有用的帖子?

Here is the firebase data structure:这是 Firebase 数据结构:

 "comments" : { "-KaEl8IRyIxRbYlGqyXC" : { "description" : "1", "likes" : 1, "postID" : "-KaEfosaXYQzvPX5WggB", "profileImageUrl" : "https://firebasestorage.googleapis.com", "timePosted" : 1484175742269, "userID" : "9yhij9cBhJTmRTexsRfKRrnmDRQ2", "username" : "HouseOfPaine" } }, "postCommentGroup" : { "-KaEfosaXYQzvPX5WggB" : { "-KaEl8IRyIxRbYlGqyXC" : true, "-KaEl9HiPCmInE0aJH_f" : true, "-KaF817rRpAd2zSCeQ-M" : true }, "-KaF9ZxAekTEBtFgdB_5" : { "-KaFEcXsSJyJwvlW1w2u" : true }, "-KaJyENJFkYxCffctymL" : { "-KaQYa0d08D7ZBirz5B4" : true } }, "posts" : { "-KaEfosaXYQzvPX5WggB" : { "caption" : "Test", "comments" : 11, "imageUrl" : "https://firebasestorage.googleapis.com/", "likes" : 0, "profileImageUrl" : "https://firebasestorage.googleapis.com/", "timePosted" : 1484174347995, "title" : "test", "user" : "17lIDKNx6LgzQmaeQ2ING582zi43", "username" : "Freedom" } },

Here is my code:这是我的代码:

func commentGroupObserver() {

    DataService.ds.REF_POST_COMMENT_GROUP.observeSingleEvent(of: .value, with: { (snapshot) in

        if snapshot.value != nil {

            if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] , snapshots.count > 0 {

                self.comments = []

                for snap in snapshots {

                    if let tempVarPostKeyForCommentGroup = snap.key as String? {

                        if tempVarPostKeyForCommentGroup == self.post.postKey {

                            self.postKeyForCommentGroup = tempVarPostKeyForCommentGroup

                            self.commentObservers()
                        } else {

                        }
                    } else {

                    }
                }

            }

        } else {
            print("error")
        }

    })


}


func commentObservers() {

    if postKeyForCommentGroup != nil {

        constantHandle = DataService.ds.REF_POST_COMMENT_GROUP.child(postKeyForCommentGroup).observe(.value, with: { (snapshot) in

            if snapshot.value != nil {

                if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot], snapshots.count > 0

                {

                    self.comments = []

                    for snap in snapshots {

                        if let theCommentIDForEachComment = snap.key as String? {
                             DataService.ds.REF_COMMENTS.child(theCommentIDForEachComment).queryOrdered(byChild: "timePosted").observeSingleEvent(of: .value, with: { (snapshots) in

                                if let commentDict = snapshots.value as? Dictionary<String, AnyObject> {

                                    let key = snapshots.key
                                    let comment = Comment(commentKey: key, dictionary: commentDict)
                                    self.comments.insert(comment, at: 0)     
                                }
                                self.tableView.reloadData()
                            })
                        }
                    } 
                }

            } else {

            }
        })

    } else {

    }

}

UPDATE:更新:

I figured out how to use queries and a delegate pattern outlined in a previous stackoverflow post:我想出了如何使用之前的 stackoverflow 帖子中概述的查询和委托模式:

getting data out of a closure that retrieves data from firebase 从从 firebase 检索数据的闭包中获取数据

But I don't know if I am using the delegate pattern correctly.但我不知道我是否正确使用了委托模式。

The code has been simplified by using the query, but it is still flickering.代码已经通过使用查询进行了简化,但它仍然闪烁。 Maybe I am not using the delegate pattern correctly?也许我没有正确使用委托模式?

    func commentGroupObserver() {
    DataService.ds.REF_POST_COMMENT_GROUP.queryOrderedByKey().queryStarting(atValue: post.postKey).queryEnding(atValue: post.postKey).observeSingleEvent(of: .value, with: { (snapshot) in
        self.postKeyForCommentGroup = self.post.postKey
        self.commentObservers()
    })

}

func commentObservers() {
    if postKeyForCommentGroup != nil {
        constantHandle = DataService.ds.REF_POST_COMMENT_GROUP.child(postKeyForCommentGroup).observe(.value, with: { (snapshot) in
            if snapshot.value != nil {
                if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot]
                {
                    self.comments = []
                    for snap in snapshots {
                        if let theCommentIDForEachComment = snap.key as String? {
                            DataService.ds.REF_COMMENTS.child(theCommentIDForEachComment).queryOrdered(byChild: "timePosted").observe(.value, with: { (snapshots) in

                                if let commentDict = snapshots.value as? Dictionary<String, AnyObject> {

                                    let key = snapshots.key
                                    let comment = Comment(commentKey: key, dictionary: commentDict)
                                    self.comments.insert(comment, at: 0)

                                }

                                self.didFetchData(comments: self.comments)

                            })



                        }

                    }

                }

            } else {

            }
        })

    } else {

    }

}

func didFetchData(comments data:[Comment]){
    self.tableView.reloadData()
}

} }

And the protocol和协议

 protocol MyDelegate{
func didFetchData(comments:[Comment]) }

The code on my end which solved it:我这边的代码解决了这个问题:

Upon Jay's suggestion I eliminated the unnecessary postCommentGroup and just queried the UID of the post the comment belongs to under the comment:根据 Jay 的建议,我删除了不必要的 postCommentGroup,并在评论下查询了该评论所属的帖子的 UID:

    func commentObservers() {

    let queryRef = DataService.ds.REF_COMMENTS.queryOrdered(byChild: "postID").queryEqual(toValue: self.post.postKey)

    queryRef.observe(.value, with: { snapshot in

        if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {

            for snap in snapshots {

                if let commentDict = snap.value as? Dictionary<String, AnyObject> {
                    let key = snap.key
                    let comment = Comment(commentKey: key, dictionary: commentDict)
                    self.comments.insert(comment, at: 0)
                }
            }
        }

        self.tableView.reloadData()
    })
}

Your approach may need to be adjusted by simplifying.您的方法可能需要通过简化来调整。 I wanted to provide all of the nuts and bolts so it's a bit lengthy and could in itself be simplified.我想提供所有的螺母和螺栓,所以它有点冗长,而且本身可以简化。

While denormalizing is normal, it's not a requirement and in some cases can add additional complexity.虽然非规范化是正常的,但它不是必需的,并且在某些情况下会增加额外的复杂性。 The 'layer' in your structure postCommentGroup, appears to be unneeded.您的结构 postCommentGroup 中的“层”似乎是不需要的。

It looks like you have a view controller containing posts, and a second view controller that displays the comments when the user taps a post on the first controller.看起来您有一个包含帖子的视图控制器,以及当用户点击第一个控制器上的帖子时显示评论的第二个视图控制器。

You really only need a posts node and a comments node你真的只需要一个帖子节点和一个评论节点

posts
   post_id_0
     title: "my post title"
     caption: "some caption"
     uid: "uid_0"
   post_id_1
     title: "another post title
     caption: "another caption
     uid: "uid_0"

and a comments node that references the post和引用帖子的评论节点

comments
   comment_0
     post_id: "post_id_0"
     uid: "uid_1"
     likes: "10"
   comment_1
     post_id: "post_id_0"
     uid: "uid_1"
     likes: "7"
   comment_2
     post_id: "post_id_1"
     uid: "uid_1"
     likes: "2"

The setup:设置:

class CommentClass {
    var commentKey = ""
    var comment = ""
    var likes = ""
}

var postsArray = [ [String: [String:AnyObject] ] ]()
var commentsArray = [CommentClass]()

The code to load all of the posts:加载所有帖子的代码:

    let postsRef = ref.child("posts")

    postsRef.observeSingleEvent(of: .value, with: { snapshot in

        for snap in snapshot.children {
            let postSnap = snap as! FIRDataSnapshot
            let postKey = postSnap.key //the key of each post
            let postDict = postSnap.value as! [String:AnyObject] //post child data

            let d = [postKey: postDict]
            self.postsArray.append(d)
        }
        //postsTableView.reloadData
        print(self.postsArray) //just to show they are loaded
    })

then, when a user taps a post, load and display the comments.然后,当用户点击帖子时,加载并显示评论。

    self.commentsArray = [] //start with a fresh array since we tapped a post
    //placeholder, this will be the post id of the tapped post
    let postKey = "post_id_0" 
    let commentsRef = ref.child("comments")
    let queryRef = commentsRef.queryOrdered(byChild: "post_id")
                              .queryEqual(toValue: postKey)

    //get all of the comments tied to this post
    queryRef.observeSingleEvent(of: .value, with: { snapshot in

        for snap in snapshot.children {
            let commentSnap = snap as! FIRDataSnapshot
            let commentKey = commentSnap.key //the key of each comment
            //the child data in each comment
            let commentDict = commentSnap.value as! [String:AnyObject] 
            let comment = commentDict["comment"] as! String
            let likes = commentDict["likes"] as! String
            let c = CommentClass()
            c.commentKey = commentKey
            c.comment = comment
            c.likes = likes

            self.commentsArray.append(c)
        }

        //commentsTableView.reload data

        //just some code to show the posts are loaded
        print("post:  \(postKey)")
        for aComment in self.commentsArray {
            let comment = aComment.comment
            print("  comment: \(comment)")
        }
    })

and the resultant output和结果输出

post:  post_id_0
  comment: I like post_id_0
  comment: post_id_0 is the best evah

The above code is tested and has no flicker.上面的代码经过测试,没有闪烁。 Obviously it will need to be tweaked for your use case as you have some images to load etc but the above should resolve issues and be more maintainable.显然,它需要针对您的用例进行调整,因为您有一些图像要加载等,但上述内容应该可以解决问题并且更易于维护。

I experienced this flickering only with the pictures.我只在图片中经历过这种闪烁。 The app downloaded the pictures every time when something happened inside the collection view.每次在集合视图中发生某些事情时,该应用程序都会下载图片。

My solution what I have found on the web.我在网上找到的解决方案。 NSCache. NSCache。 You can Cache the image, and if the image link did not change the picture will be loaded from cache.您可以缓存图片,如果图片链接没有改变,图片将从缓存中加载。 Also helpful when you scrolling a collectionview like Instagram or Facebook.当您滚动 Instagram 或 Facebook 等收藏视图时也很有帮助。 I did not find too many solution for this in Swift 5. So let me share with you.我在 Swift 5 中没有找到太多解决方案。所以让我与您分享。

TODO: Create a new swift file. TODO:创建一个新的 swift 文件。 Copy this code, it creates a custom ImageView class.复制此代码,它会创建一个自定义 ImageView 类。 Set up you imageview for this custom class on storyboard, ctrl+drag.在情节提要上为这个自定义类设置图像视图,ctrl+drag。 Or do the same programatically in you swift file.或者在你的 swift 文件中以编程方式执行相同的操作。

let imageCache = NSCache<NSString, UIImage>()
class CustomImageView: UIImageView {
var imageUrlString: String?

func loadImageUsingUrlString(urlString: String) {
    
    imageUrlString = urlString
    
    guard let url = URL(string: urlString) else { return }
    
    image = nil
    
    if let imageFromCache = imageCache.object(forKey: urlString as NSString) {
        self.image = imageFromCache
        print("local")
        return
    }
    
    URLSession.shared.dataTask(with: url, completionHandler: { (data, respones, error) in
        
        if error != nil {
            print(error ?? "")
            return
        }
        
        DispatchQueue.main.async {
            guard let imageToCache = UIImage(data: data!) else { return }
            
            if self.imageUrlString == urlString {
                self.image = imageToCache
                print("most mentem a kepet")
            }
            
            imageCache.setObject(imageToCache, forKey: urlString as NSString)
        }
        
    }).resume()
}
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM