My ForEach in a Scrollview does not get updated when CommentViewModel comments get updated. It gets successfully updated, but for some reason, CommentView does not get updated. I have tried everything, but can't seem to find a solution. Maybe Comment should become a Hashable or Codable. But I can't quite make this work. I also tried removing the chance of Scrollview being empty, by adding an if statement or empty Text. But this was not the problem. Any help would be helpfull.
//These are the updated View
struct CommentView: View {
@StateObject var commentViewModel = CommentViewModel()
static let emptyScrollToString = "emptyScrollToString"
@State var commentCommentUser = ""
@State var showCommentComment = false
@State var post: Post
init(_ post: Post) {
self.post = post
}
var body: some View {
VStack {
commentView
Divider()
if showCommentComment {
HStack {
Text("Svarer \(commentCommentUser)")
.foregroundColor(.black)
.font(.system(size: 16))
.opacity(0.3)
Spacer()
Button {
withAnimation(Animation.spring().speed(2)) {
showCommentComment.toggle()
}
} label: {
Text("x")
.font(.system(size: 16))
.foregroundColor(.black)
}
}
.padding()
.background(Color(r: 237, g: 237, b: 237))
}
BottomBar(post: post)
.frame(minHeight: 50,maxHeight: 180)
.fixedSize(horizontal: false, vertical: true)
.shadow(radius: 60)
.navigationBarTitle("Kommentar", displayMode: .inline)
}
.onAppear() {
UINavigationBar.appearance().tintColor = UIColor(red: 20/255, green: 147/255, blue: 2/255, alpha: 1)
commentViewModel.fetchComments(post: post)
}
}
private var commentView: some View {
ScrollView {
ScrollViewReader { scrollViewProxy in
VStack {
HStack{ Spacer() }
.id(Self.emptyScrollToString)
ForEach(commentViewModel.comments, id: \.id) { comment in
CommentCell(post: post, comment: comment, commentCommentUser: $commentCommentUser, showCommentComment: $showCommentComment)
}
}
.onReceive(Just(commentViewModel.comments.count)) { _ in // <-- here
withAnimation(.easeOut(duration: 0.5)) {
print("Scroll to top")
scrollViewProxy.scrollTo(Self.emptyScrollToString, anchor: .bottom)
}
}
}
}
}
public func uploadData(commentText: String) {
guard let uid = FirebaseManager.shared.auth.currentUser?.uid else {return}
guard let id = post.id else {return}
let data = ["fromId":uid, "commentText":commentText, "likes":0, "timestamp": Timestamp()] as [String : Any]
FirebaseManager.shared.firestore.collection("posts").document(id).collection("comments")
.document().setData(data) { error in
if error != nil {
print("failed to post comment", error ?? "")
return
}
print("Update")
commentViewModel.fetchComments(post: post) //Gets error here
}
}
}
struct BottomBar: View {
var commentView: CommentView
init(post: Post) {
self.commentView = CommentView(post)
}
var body: some View {
bottomBar
}
private var bottomBar: some View {
HStack{
TextEditorView(string: $commentText)
.overlay(RoundedRectangle(cornerRadius: 12)
.stroke(lineWidth: 1)
.opacity(0.5))
VStack {
Spacer()
Button {
commentView.uploadData() // This also reset all @State variables in Commentview, for some reason
commentText = ""
} label: {
Text("Slå op")
.font(.system(size: 20, weight: .semibold))
.opacity(commentText.isEmpty ? 0.5 : 1)
.foregroundColor(Color(r: 20, g: 147, b: 2))
}
.padding(.bottom, 10)
}
}
.padding()
}
}
struct Comment: Identifiable, Decodable {
@DocumentID var id: String?
let commentText: String
let fromId: String
var likes: Int
let timestamp: Timestamp
var user: PostUser?
var didLike: Bool? = false
}
class CommentViewModel: ObservableObject {
@Published var comments = [Comment]()
@Published var count = 0
let service: CommentService
let userService = UserService()
init(post: Post) {
self.service = CommentService(post: post)
fetchComments()
}
func fetchComments() {
service.fetchComments { comments in
self.comments = comments
self.count = self.comments.count
for i in 0 ..< comments.count {
let uid = comments[i].fromId
self.userService.fetchUser(withUid: uid) { user in
self.comments[i].user = user
}
}
}
}
}
struct CommentService {
let post: Post
func fetchComments(completion: @escaping([Comment]) -> Void) {
guard let id = post.id else {return}
FirebaseManager.shared.firestore.collection("posts").document(id).collection("comments")
.order(by: "timestamp", descending: true)
.getDocuments { snapshot, error in
if error != nil {
print("failed fetching comments", error ?? "")
return
}
guard let docs = snapshot?.documents else {return}
do {
let comments = try docs.compactMap({ try $0.data(as: Comment.self) })
print("COmplete")
completion(comments)
}
catch {
print("failed")
}
}
}
}
This is the old views
struct Comment: Identifiable, Decodable {
@DocumentID var id: String?
let commentText: String
let fromId: String
var likes: Int
let timestamp: Timestamp
var user: PostUser?
var didLike: Bool? = false
}
class CommentViewModel: ObservableObject {
@Published var comments = [Comment]()
@Published var count = 0
let service: CommentService
let userService = UserService()
init(post: Post) {
self.service = CommentService(post: post)
fetchComments()
}
func fetchComments() {
service.fetchComments { comments in
self.comments = comments
self.count = self.comments.count
for i in 0 ..< comments.count {
let uid = comments[i].fromId
self.userService.fetchUser(withUid: uid) { user in
self.comments[i].user = user
}
}
}
}
}
struct CommentService {
let post: Post
func fetchComments(completion: @escaping([Comment]) -> Void) {
guard let id = post.id else {return}
FirebaseManager.shared.firestore.collection("posts").document(id).collection("comments")
.order(by: "timestamp", descending: true)
.getDocuments { snapshot, error in
if error != nil {
print("failed fetching comments", error ?? "")
return
}
guard let docs = snapshot?.documents else {return}
do {
let comments = try docs.compactMap({ try $0.data(as: Comment.self) })
print("COmplete")
completion(comments)
}
catch {
print("failed")
}
}
}
}
struct CommentView: View {
@ObservedObject var commentViewModel: CommentViewModel
static let emptyScrollToString = "emptyScrollToString"
init(post: Post) {
commentViewModel = CommentViewModel(post: post)
}
var body: some View {
VStack {
commentView
Divider()
BottomBar(post: commentViewModel.service.post)
.frame(minHeight: 50,maxHeight: 180)
.fixedSize(horizontal: false, vertical: true)
.shadow(radius: 60)
.navigationBarTitle("Kommentar", displayMode: .inline)
}
.onAppear() {
UINavigationBar.appearance().tintColor = UIColor(red: 20/255, green: 147/255, blue: 2/255, alpha: 1)
}
}
private var commentView: some View {
ScrollView {
ScrollViewReader { scrollViewProxy in
VStack {
HStack{ Spacer() }
.id(Self.emptyScrollToString)
ForEach(commentViewModel.comments, id: \.id) { comment in // Here should it update
let _ = print("Reload")
CommentCell(post: commentViewModel.service.post, comment: comment)
}
}
.onReceive(commentViewModel.$count) { _ in // It doesn't update here either
withAnimation(.easeOut(duration: 0.5)) {
print("Scroll to top")
scrollViewProxy.scrollTo(Self.emptyScrollToString, anchor: .bottom)
}
}
}
}
}
}