@plubled更改时,scrollview中的each不会更新
当commentViewModel评论更新时,我在ScrollView中的foreach不会更新。它已成功更新,但是由于某种原因,注释视图不会更新。我已经尝试了一切,但似乎找不到解决方案。 也许评论应该变成可用的或可编码的。但是我不能完全做这项工作。 我还尝试通过添加if语句或空文本来删除滚动浏览的机会。但这不是问题。 任何帮助都会有所帮助。
//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")
}
}
}
}
这是旧的景色
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)
}
}
}
}
}
}
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)
}
}
}
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
@stateObject 属性包装器拥有您创建的对象,因此,一旦通过任何更改进行更新的视图,它就会保持活力。
@observedObject 属性包装器不拥有您创建的对象,因此它将通过任何更改在视图更新中重新创建,以这种方式,属性观察者会丢失并且将无法接收更改。
因此,将ViewModel从 @ObseverObject 更改为 @stateObject 将解决问题。
@StateObject property wrapper own the object you created, so it will keep alive once View updated by any changes.
@ObservedObject property wrapper doesn't own the object you created, so it will recreated on View update by any changes, in this way property observer get lost and will not be able to receive changes.
So, changing your ViewModel from @ObservedObject to @StateObject will fix the issue.
编辑1:考虑新的代码。
请注意,重要的是要对Swiftui基础知识有良好的掌握,尤其是如何使用和通过
observableObject
以及如何使用views
。我建议您再次进行教程。我试图修改您的代码,以使您了解如何重新结构它。支付细节,希望它有所帮助。
请注意,我已经评论了许多行,因为我没有
firebase
和您的其他代码,例如userService
等...调整我的代码以满足您的需求,并取消相关行。
Edit-2:错字修复。
在
bottombar
更改viewModel.Service.uploAddata(post:post,ryventtext:commenttext){_ in}
到
viewModel.uploaddata(帖子:post,ryventtext:commenttext)
EDIT-1: Taking your
new
code into consideration.Note, it is important to have a good grip on the SwiftUI basics, especially how to use and pass
ObservableObject
and how to useViews
. I suggest you do the tutorial again.I have attempted to modify your code to give you an idea on how you could re-structure it. Pay atttention to the details, hope it helps.
Note, I have commented a number of lines, because I do not have
Firebase
and your other code, such asUserService
etc...Adjust my code to suit your needs, and uncomment the relevant lines.
EDIT-2: typo fix.
in
BottomBar
changedviewModel.service.uploadData(post: post, commentText: commentText) {_ in}
to
viewModel.uploadData(post: post, commentText: commentText)