Manually Call View Update in SwiftUI to Reflect Changes

SwiftUI has many options to trigger a view update, either it being triggered through an action by user or an application. Most of such view updates are triggered automatically trough the change of value in State, Binding, or Published variables, which are being tracked by that view, but sometimes if an action is being performed in the background it might not reflect in UI on update.

In such cases, with the use of the below described ViewModifier, you will be able to call a SwiftUI View redraw from anywhere in code:

Step 1: create a modifier to track a notification.

struct UpdatableModifier: ViewModifier {
    
    @State var localID = UUID()
    
    func body(content: Content) -> some View {
        content
            .id(localID)
            .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("View_Update"))) { _ in
                localID = UUID()
            }
    }
}

extension View {
    var updatable: some View {
        modifier(UpdatableModifier())
    }
}

The .id modifier in SwiftUI allows us to track the value inside of it, and if it has been changed, the view, to which modifier is attached, will be redrawn.

So when this custom modifier is attached to a view, a unique UUID value will be tied to it. Then by sending a notification, in this case “View_Update”, this custom modifier will change the UUID value and thus the corresponding view will be redrawn.

Step 2: Attach modifier to a view that you want to be update on sending of notification.

class ViewUpdateTest {
    static var viewText = "Hello World"
}

struct ContentView: View {

    var body: some View {
        Text(ViewUpdateTest.viewText)
            .updatable
    }
}

I’ve created a sample where a view is displaying a string value from a Non Observable class, so the view will not reflect the change if I update the viewText value to something else.

Step 3: Send notification to update view when needed.

func changeValue() {
    ViewUpdateTest.viewText = "Hey there world"
    
    DispatchQueue.main.async {
        NotificationCenter.default.post(Notification(name: Notification.Name("View_Update")))
    }
}

The function changes the value and then sends a notification on the main thread causing the ContentView() text to update.

Of course in for this example the more optimal solution would be to just have ViewUpdateTest class as ObservableObject and having the variable itself as @Published inside of the class, but there are cases where the above method is more convenient and simpler.

Customizable Option:

There are more optimizations to make the modifier even more useful, such as having having a custom id so that only the view which tracks the id would be updated. This will allow for modifier to be applied to different views at the same time and only update when its own value is called.

Just an another variable id to the Init of of the modifier and on receive of notification check if that variable matches the one from notification.

struct UpdatableModifier: ViewModifier {
    
    @State var localID = UUID()
    var id: String
    
    func body(content: Content) -> some View {
        content
            .id(localID)
            .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("View_Update"))) { id in
                guard let id = id.userInfo?.first?.value as? String else {
                    return
                }
                if id == self.id {
                    localID = UUID()
                }
            }
    }
}

extension View {
    func updateable(_ id: String) -> some View {
        modifier(UpdatableModifier(id: id))
    }
}

Now when attaching the modifier, add some kind of string to identify the view and make sure to use that string value when sending notification.

struct ContentView: View {
    
    var body: some View {
        Text(ViewUpdateTest.viewText)
            .updateable("helloText")
    }
}

func changeValue() {
    ViewUpdateTest.viewText = "Hey there world"
    
    DispatchQueue.main.async {
        NotificationCenter.default.post(Notification(name: Notification.Name("View_Update"), userInfo: ["id": "helloText"]))
    }
}