Как быстро разбивать запросы с помощью TableView - PullRequest
0 голосов
/ 19 июня 2020

Я пытаюсь использовать разбиение на страницы Firestore с быстрым TableView. Я использовал схему кода, предоставленную Google в их документации Firestore. Вот мой код, который загружает первые 4 сообщения из Firestore.

func loadMessages(){
        let postDocs = db
            .collectionGroup("userPosts")
            .order(by: "postTime", descending: false)
            .limit(to: 4)

        postDocs.addSnapshotListener { [weak self](querySnapshot, error) in
            self?.q.async{
                self!.posts = []

                guard let snapshot = querySnapshot else {
                    if let error = error {
                        print(error)
                    }
                    return
                }

                guard let lastSnapshot = snapshot.documents.last else {
                    // The collection is empty.
                    return
                }
                //where do I use this to load the next 4 posts?
                let nextDocs = Firestore.firestore()
                    .collectionGroup("userPosts")
                    .order(by: "postTime", descending: false)
                    .start(afterDocument: lastSnapshot)

                if let postsTemp = self?.createPost(snapshot){

                    DispatchQueue.main.async {
                        self!.posts = postsTemp
                        self!.tableView.reloadData()
                    }
                }
            }
        }
    }

    func createPost(_ snapshot: QuerySnapshot) ->[Post]{
        var postsTemp = [Post]()
        for doc in snapshot.documents{
            if let firstImage = doc.get(K.FStore.firstImageField) as? String,
                let firstTitle = doc.get(K.FStore.firstTitleField) as? String,
                let secondImage = doc.get(K.FStore.secondImageField) as? String,
                let secondTitle = doc.get(K.FStore.secondTitleField) as? String,
                let userName = doc.get(K.FStore.poster) as? String,
                let uID = doc.get(K.FStore.userID) as? String,
                let postDate = doc.get("postTime") as? String,
                let votesForLeft = doc.get("votesForLeft") as? Int,
                let votesForRight = doc.get("votesForRight") as? Int,
                let endDate = doc.get("endDate") as? Int{
                let post = Post(firstImageUrl: firstImage,
                                secondImageUrl: secondImage,
                                firstTitle: firstTitle,
                                secondTitle: secondTitle,
                                poster: userName,
                                uid: uID,
                                postDate: postDate,
                                votesForLeft: votesForLeft,
                                votesForRight:votesForRight,
                                endDate: endDate)
                postsTemp.insert(post, at: 0)
            }else{

            }
        }
        return postsTemp
    }

Вот мой делегат, который также определяет конец TableView:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let post = posts[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: K.cellIdentifier, for: indexPath) as! PostCell
        cell.delegate = self

        let seconds = post.endDate
        let date = NSDate(timeIntervalSince1970: Double(seconds))
        let formatter = DateFormatter()
        formatter.dateFormat = "M/d h:mm"

        if(seconds <= Int(Date().timeIntervalSince1970)){
            cell.timerLabel?.text = "Voting Done!"
        }else{
            cell.timerLabel?.text = formatter.string(from: date as Date)
        }

        let firstReference = storageRef.child(post.firstImageUrl)
        let secondReference = storageRef.child(post.secondImageUrl)

        cell.firstTitle.setTitle(post.firstTitle, for: .normal)
        cell.secondTitle.setTitle(post.secondTitle, for: .normal)
        cell.firstImageView.sd_setImage(with: firstReference)
        cell.secondImageView.sd_setImage(with: secondReference)
        cell.userName.setTitle(post.poster, for: .normal)
        cell.firstImageView.layer.cornerRadius = 8.0
        cell.secondImageView.layer.cornerRadius = 8.0

        if(indexPath.row + 1 == posts.count){
            print("Reached the end")
        }

        return cell
    }

Раньше у меня был addSnapshotListener без ограничения на Query и просто снес все сообщения по мере их поступления. Однако я хотел бы ограничить количество сообщений, удаляемых за раз. Я не знаю, куда мне загружать данные в мою модель. Раньше он загружался в конце addSnapshotListener, и я все еще мог это сделать, но когда мне использовать next Query? Спасибо за любую помощь, и, пожалуйста, дайте мне знать, если я смогу подробнее рассказать о своем вопросе.

Ответы [ 2 ]

1 голос
/ 20 июня 2020

Я предполагаю, что ваш метод определения того, когда пользователь достигает нижней части элементов tableView, является правильным.

По моему личному мнению, установка слушателей в реальном времени для разбивки на страницы будет довольно сложной задачей. Я рекомендую вам использовать для этого несколько вызовов get.

Если все сделано таким образом, вам нужна функция, которая каждый раз при вызове приносит следующий набор сообщений. Например, при первом вызове он получит 4 последних документа AKA. При втором вызове он получит следующий последний набор сообщений (4). Чтобы уточнить полученные сообщения из первого звонка новее, чем из второго звонка. Надеюсь, это имеет смысл.

Как? Поддерживать два свойства, одно, которое отслеживает последний полученный документ, и другое, которое хранит все сообщения, полученные до сих пор (массив или любой другой структуры данных). Если функция вызывается 4 раза, массив, о котором я здесь говорю, будет иметь 16 сообщений (при условии, что в хранилище имеется> = 16 сообщений).

Теперь, когда у нас есть точка, из которой мы получили сообщения firestore, мы можем использовать Firestore API для настройки запроса на выборку следующего набора при первом вызове. Каждый раз, когда приходит набор документов / сообщений, он добавляется к массиву.

Чуть не забыл, функция, о которой я говорю здесь, должна вызываться каждый раз, когда пользователь достигает конца tableView.

Это решение может или не может быть идеальным для вас, но, надеюсь, оно, по крайней мере, приведет вас к поиску решения. Любые вопросы приветствуются, рады помочь ..

1 голос
/ 19 июня 2020
//I have this solution working in a project, the approach is to detect when the user scrolls and the offset is getting close to the top
//When this happens, you get the next bunch of elements from firestore, insert them in your data source and finallly reload the tableview keeping the scroll offset.
//below are the related methods, hope it helps.

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    if scrollView.contentOffset.y < 300{
        self.stoppedScrolling()
    }
}

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if !decelerate {
        if scrollView.contentOffset.y < 300{
            self.stoppedScrolling()
        }
    }
}

//When the tableview stops scrolling you call your method getNextPosts which should be very similar to your loadMessages, maybe you dont need a listener, you just need the next posts.
func stoppedScrolling() {
    getNextPosts { posts in
        self.insertNextPosts(posts)
    }
}

//Insert the new messages that you just got
private func insertNextPosts(_ posts: [Post]){
    self.messages.insert(contentsOf: posts, at: 0)
    self.messagesCollectionView.reloadDataAndKeepOffset()
}

//This function es from MessageKit: https://messagekit.github.io, take it only as reference, besides is for a collectionview but you can adapt it to tableview
public func reloadDataAndKeepOffset() {
    // stop scrolling
    setContentOffset(contentOffset, animated: false)

    // calculate the offset and reloadData
    let beforeContentSize = contentSize
    reloadData()
    layoutIfNeeded()
    let afterContentSize = contentSize

    // reset the contentOffset after data is updated
    let newOffset = CGPoint(
        x: contentOffset.x + (afterContentSize.width - beforeContentSize.width),
        y: contentOffset.y + (afterContentSize.height - beforeContentSize.height))
    setContentOffset(newOffset, animated: false)
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...