Firestore не обновляет SwiftUI grid без перезагрузки приложения

#swift #google-cloud-firestore #swiftui

#swift #google-облако-firestore #swiftui

Вопрос:

Я следил за онлайн-руководством и, похоже, у меня есть несколько вещей, которые работают правильно, однако я пытаюсь автоматически обновлять свое представление на основе порядка, который работает, но только если я закрываю и снова открываю приложение.

Наблюдатель.swift

 class postsobserver: ObservableObject {
    @Published var posts = [datatype1]()

    init() {
        let settings = FirestoreSettings()
        settings.isPersistenceEnabled = true

        let db = Firestore.firestore()
        db.settings = settings

        db.collection("posts").order(by: "likes", descending: true).addSnapshotListener { snap, err in

            if err != nil {
                print((err?.localizedDescription)!)

                return
            }

            for i in snap!.documentChanges {
                if i.type == .added {
                    let id = i.document.documentID
                    let name = i.document.get("name") as! String
                    let image = i.document.get("image") as! String
                    let likes = i.document.get("likes") as! String

                    self.posts.append(datatype1(id: id, name: name, image: image, likes: likes))
                }

                if i.type == .removed {
                    let id = i.document.documentID

                    for j in 0 ..< self.posts.count {
                        if self.posts[j].id == id {
                            self.posts.remove(at: j)
                            return
                        }
                    }
                }

                if i.type == .modified {
                    let id = i.document.documentID
                    let likes = i.document.get("likes") as! String

                    for j in 0 ..< self.posts.count {
                        if self.posts[j].id == id {
                            self.posts[j].likes = likes
                            return
                        }
                    }
                }
            }
        }
    }
}
  

Главная страница.swift

 @ObservedObject var postsobserved = postsobserver()

let columns = [
    GridItem(.adaptive(minimum: 100)),
]

var body: some View {
    ScrollView(.vertical, showsIndicators: false) {
        VStack {
            if postsobserved.posts.isEmpty {
                Text("no new posts").fontWeight(.heavy)
            } else {
                LazyVGrid(columns: columns, spacing: 20) {
                    ForEach(postsobserved.posts) { i in

                        postCard(user: i.name, image: i.image, id: i.id, likes: i.likes)
                    }
                }
            }
        }
    }
}
  

Открытка.swift

 import SwiftUI
import SDWebImageSwiftUI
import Firebase
import FirebaseFirestore

struct postCard: View {
    var user = ""
    var image = ""
    var id = ""
    var likes = ""

    var body: some View {
        VStack(alignment: .leading) {
            HStack {
                Text(user)
                Spacer()
            }

            AnimatedImage(url: URL(string: image))
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(height: 100)

            HStack {
                Button(action: {
                    let db = Firestore.firestore()

                    let like = Int.init(self.likes)!
                    db.collection("posts").document(self.id).updateData(["likes": "(like   1)"]) { err in

                        if err != nil {
                            print(err)
                            return
                        }

                        print("updated...")
                    }

                }) {
                    Image(systemName: "arrow.up.square")
                        .frame(width: 26, height: 26)
                }

                Button(action: {
                    let db = Firestore.firestore()

                    let like = Int.init(self.likes)!
                    db.collection("posts").document(self.id).updateData(["likes": "(like - 1)"]) { err in

                        if err != nil {
                            print(err)
                            return
                        }

                        print("updated...")
                    }

                }) {
                    Image(systemName: "arrow.down.square")
                        .frame(width: 26, height: 26)
                }

                Spacer()

            }.padding(.top, 8)

            Text("(likes) Likes")

        }.padding(8)
    }
}
  

Я почти уверен, что где-то допустил ошибку, но последние 4 часа пытался кое-что предпринять, и подумал, что пришло время обратиться за помощью

Редактировать:

изображение интерфейса

Когда я нажимаю стрелки, они, похоже, обновляются корректно без перезагрузки приложения, но .order(by: "likes", descending: true) , похоже, порядок не меняется, пока я не перезагружу приложение.

Ответ №1:

Вам необходимо обновить ваши @Published свойства в главном потоке.

Похоже, что db.collection("posts").order(by: "likes", descending: true).addSnapshotListener это асинхронный ie. выполняется в фоновом режиме.

Где бы вы ни обновляли свою posts переменную:

 self.posts.append(datatype1(id: id, name: name, image: image, likes: likes))
  

или

 self.posts.remove(at: j)
  

и т.д…

оберните это в DispatchQueue.main :

 DispatchQueue.main.async {
    self.posts.append(datatype1(id: id, name: name, image: image, likes: likes))
}
  

Вы также можете обернуть весь свой код в асинхронный ответ в DispatchQueue.main :

 db.collection("posts").order(by: "likes", descending: true).addSnapshotListener { (snap, err) in
    DispatchQueue.main.async {
        ...
    }
}
  

Редактировать

Похоже, вы перезагружаете свои сообщения только в init . Вот почему это правильно только в начале (когда вы открываете приложение). Если вы хотите перезагрузить их при каком-либо действии, попробуйте следующее:

 class postsobserver: ObservableObject {
    @Published var posts = [datatype1]()
    
    private let settings = FirestoreSettings()
    private let db = Firestore.firestore()

    init() {
        settings.isPersistenceEnabled = true
        db.settings = settings

        loadPosts()
    }
    
    // function to reload your posts (in the correct order)
    func loadPosts() {
        db.collection("posts").order(by: "likes", descending: true).addSnapshotListener { snap, err in
            DispatchQueue.main.async {
                // ...
            }
        }
    }
}
  

а затем вызывайте его из своего представления всякий раз, когда вы хотите загрузить обновленные записи:

 postsobserved.loadPosts()
  

Кроме того, вместо того, чтобы каждый раз перезагружать записи, вы можете сортировать их локально:

 ForEach(postsobserved.posts.sorted(by: { /* condition */ })) {i in ...
  

Примечание: Я также рекомендую вам переместить вашу логику Firebase в ObservableObject.

Действие этой кнопки:

 Button(action: {
    let db = Firestore.firestore()

    let like = Int(self.likes)!
    db.collection("posts").document(self.id).updateData(["likes": "(like - 1)"]) { err in

        if err != nil {
            print(err)
            return
        }

        print("updated...")
    }

}) {
    Image(systemName: "arrow.down.square")
        .frame(width: 26, height: 26)
}
  

может быть извлечен в какую-либо функцию внутри вашего postsobserver и вызван в Button напрямую:

 Button(action: {
    postsobserver.updateLikes(...)
}) {
    Image(systemName: "arrow.down.square")
        .frame(width: 26, height: 26)
}
  

Комментарии:

1. завернул код в DispatchQueue. кажется, что main запускается без ошибок, но, похоже, все еще обновляет сетку, я добавил postCard.swift, возможно, я что-то делаю не так

2. @zeem Вы проверяли (печатали, отлаживали), что ваша асинхронная функция firebase возвращает все, как ожидалось?

3. кажется, что он работает правильно, просто scrollview / LazyVGrid, похоже, не обновляет порядок без перезагрузки приложения.

4. спасибо, @pawello2222 внес изменения, однако не уверен, где вызывать postsobserved.loadPosts(), который я добавил как оператор .onAppear после else, но думаю, что это просто зациклилось и выдало ошибку «каждый элемент макета может встречаться только один раз ..» ForEach(postsobserved.posts.sorted (по условию, похоже, не нравится, когда добавление «лайков» просто завершается сбоем. У меня такое чувство, что я где-то делаю что-то очень глупое

5. иногда все, что вам нужно, это кофе покрепче 🙂 огромное спасибо за всю вашу помощь @pawello2222 ForEach(postsobserved.posts.sorted(by: {0.лайков > 1.лайков })) {Я в… решил, что теперь все работает так, как должно