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

时间:2017-01-13 17:18:32

标签: ios swift uitableview firebase firebase-realtime-database

我的主要问题是如何消除闪烁,但我也想知道我是否正确地使用非规范化的Firebase数据,并且效率最高。我的方法是否接近正确?

因此,我正在努力尝试使用已经非规范化的数据正确显示来自firebase数据库的数据。我有帖子,然后是与每个帖子相关的评论。每当有人打开帖子的评论部分时,通过从视图控制器转换到新的视图控制器,它会抓取帖子的唯一键(postKey),然后扫描与postCommentGroup中包含的postKey关联的评论组。注释组是postCommentGroup中每个postKey的子项,它们只是commentKey作为键,而“true”作为值,表示哪些注释与哪些帖子相关联。评论是完全不同的分支,因为我认为Firebase文档建议人们应该这样做。

我基本上有3层嵌套观察者。

为了清楚起见,我在桌面视图中使用dequeuereusableslls回收单元格,而且我还有一个基本的延迟加载/图像缓存机制,可能会干扰事情,但我在其他不那么复杂的机制上也有相同的机制tableviews所以我认为这不是问题所在。

由于我缺乏知识,除了经历这个循环之外,我不知道如何显示数据。我认为这个循环可能会导致闪烁,但我不知道如何使其加载数据。我已经尝试了各种其他方法,例如使用查询,但我从来没有能够让它工作。

作为旁注,我试图快速了解如何查询数据(我认为可能对我有所帮助),但是对Swift的语法进行了更新,并对Firebase进行了更新,以前的例子有点难以理解。

此外,我在Firebase网站或Github上的任何Firebase文档中都找不到以非常复杂的方式正确使用非规范化数据的好的最近的示例。有没有人知道使用Swift 3.0和Firebase(最新版本 - 不是遗留版本)处理非规范化数据的好参考资料,无论是GitHub上的项目,还是博客,或者只是最集合的stackoverflow上的有用帖子?

这是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"
        }
      },

这是我的代码:

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 {

    }

}

更新

我想出了如何使用先前stackoverflow帖子中概述的查询和委托模式:

getting data out of a closure that retrieves data from firebase

但我不知道我是否正确使用了委托模式。

使用查询简化了代码,但它仍然在闪烁。也许我没有正确使用委托模式?

    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()
}

}

和协议

 protocol MyDelegate{
func didFetchData(comments:[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()
    })
}

2 个答案:

答案 0 :(得分:3)

您的方法可能需要通过简化进行调整。我想提供所有的螺母和螺栓,因此它有点冗长,本身可以简化。

虽然非规范化是正常的,但它不是必需的,在某些情况下会增加额外的复杂性。结构postCommentGroup中的“图层”似乎是不必要的。

看起来你有一个包含帖子的视图控制器,以及第二个视图控制器,当用户点击第一个控制器上的帖子时,它会显示注释。

您实际上只需要一个帖子节点和一个评论节点

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"

以及引用帖子的评论节点

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"

设置:

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

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

加载所有帖子的代码:

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

然后,当用户点击帖子时,加载并显示评论。

    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)")
        }
    })

和结果输出

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

以上代码经过测试,没有闪烁。显然需要针对您的用例进行调整,因为您需要加载一些图像等,但上述内容应解决问题并且更易于维护。

答案 1 :(得分:0)

我仅通过图片就经历了这种闪烁。每当收藏视图中发生任何事情时,该应用都会下载图片。

我在网络上找到的解决方案。 NSCache。 您可以缓存图像,如果图像链接未更改,则将从缓存中加载图片。在滚动收藏夹视图(如Instagram或Facebook)时也很有用。 在Swift 5中没有找到太多解决方案。因此,让我与您分享。

待办事项: 创建一个新的swift文件。 复制此代码,它将创建一个自定义ImageView类。 在情节提要ctrl + drag上为此自定义类设置imageview。 或者在您的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()
}
}