Child view not refreshing for changes on SwiftData model – Swiftui

by
Alexei Petrov
swift-data swiftui-ontapgesture

Quick Fix: Encapsulate the Post struct in an observable object or use a @Published property wrapper for any properties that change, so that SwiftUI can observe the changes and update the UI accordingly.

The Problem:

In a SwiftUI application using SwiftData, a list displays a count of comments associated with each post. When a new comment is added to a post from a different view, the comment count in the list doesn’t update until the app is relaunched. The goal is to find a solution to automatically update the comment count in the list view when a new comment is added, without requiring the app to be relaunched.

The Solutions:

Solution 1: Remove the subview PostListItem

This solution involves removing the subview PostListItem and rendering each Post directly in the List. This eliminates the need for passing the single Post to PostListItem, thereby resolving the issue.

struct PostsList: View {
    @Query var posts: [Post]

    var body: some View {
        List {
            ForEach(posts) { post in
                Text("\(post.title)")
                Text("\(post.comments?.count ?? 0) comments")
            }
        }
    }
}

In this approach, the Post instances are directly rendered in the List, which simplifies the view structure and avoids the potential issue caused by passing the Post to a subview.

Solution 2: Wrap the Post in a view model annotated with @Observable

This solution involves wrapping the Post in a view model annotated with @Observable and using it as state in PostListItem. This allows SwiftUI to track changes to the view model and update the view accordingly.

struct PostsList: View {
    @Query var posts: [Post]

    var body: some View {
        List {
            ForEach(posts) { post in
                PostListItem(model: post)
            }
        }
    }
}

@Observable
class PostListItemModel {
    var post: Post

    init(post: Post) {
        self.post = post
    }
}

struct PostListItem: View {
    var model: PostListItemModel

    var body: some View {
        Text("\(model.post.title)")
        Text("\(model.post.comments?.count ?? 0) comments")
    }
}

In this approach, the Post instance is wrapped in the PostListItemModel, which is annotated with @Observable. This allows SwiftUI to observe changes to the PostListItemModel and update the UI accordingly. When a comment is added or removed, the PostListItemModel is updated, triggering a refresh of the view.

Q&A

Why SwiftData not refreshing given query?

The issue is in passing single Post to PostListItem.

What are the two solutions for the issue?

  1. Remove PostListItem subview, render each Post directly in the list. 2. Wrap Post in Observable annotated view model used as state in PostListItem.

Video Explanation:

The following video, titled "Let's explore and discover SwiftData (WWDC23) - YouTube", provides additional insights and in-depth exploration related to the topics discussed in this post.

Play video

Let's explore and discover SwiftData (WWDC23). 4.2K views · Streamed 7 months ago ...more. Vincent Pradeilles. 14.2K. Subscribe.