ios – SwiftUI – View not updated after edit

I’m writing a simple password manager as a learning exercise to get familiar with SwiftUI. So far so good, however I’m running into an issue where my View is not being updated as expected after editing a value. There are 2 Views that I’m dealing with right now: one for displaying a secret, and another for editing its properties. I’m using the MVVM pattern for my underlying data model, with Core Data/Combine being used for accessing the datastore. My “display” View Model looks like this:

class SecretDisplayViewModel: BaseViewModel, ObservableObject {
    
    private let secretIdentifier: String
    
    @Published var secret: Secret? = nil
    
    private var cancellables = (AnyCancellable)()

    init(managedObjectContext: NSManagedObjectContext, secretIdentifier: String) {
        self.secretIdentifier = secretIdentifier
        super.init(managedObjectContext: managedObjectContext)
    }
    
    init(managedObjectContext: NSManagedObjectContext, secret: Secret) {
        self.secretIdentifier = secret.id
        self.secret = secret
        super.init(managedObjectContext: managedObjectContext)
    }
    
    func loadSecret() {
        let _ = secretsService.loadSecret(secretIdentifier: self.secretIdentifier)
            .map({ (entities: (SecretEntity)) -> (Secret) in
                entities.compactMap { $0.toSecret() }
            })
            .replaceError(with: ()) // FIXME: How should I handle errors?
            .sink { completion in
                if case .failure(let error) = completion {
                    // TODO: Handle error
                    print("ERROR: (error)")
                }
            } receiveValue: { (weak self) secrets in
                guard let strongSelf = self else {
                    return
                }
                
                if secrets.count > 0 {
                    strongSelf.secret = secrets(0)
                    print("strongSelf.secret=(String(describing: strongSelf.secret))")
                }
            }.store(in: &cancellables)
    }
}

…and the display View is set to call loadSecret() through onAppear like this:

struct SecretDisplayView: View {

    <snip>

    @ObservedObject var viewModel: SecretDisplayViewModel
    
    @State private var isEditing: Bool = false
    
    var body: some View {
        buildViewForSecret(secret: viewModel.secret)
            .listStyle(GroupedListStyle())
            .navigationTitle("Secret")
            .onAppear {
                if isPreview {
                    // Do nothing
                } else {
                    self.viewModel.loadSecret()
                }
            }
            .toolbar {
                Button("Edit", action: { self.isEditing.toggle() })
            }
            .sheet(isPresented: $isEditing, onDismiss: {
                // FIXME: Secret not refreshed after dismiss
                self.viewModel.loadSecret()
            }, content: {
                if let secret = self.viewModel.secret {
                    SecretCreateEditView(editViewModel: SecretEditViewModel(managedObjectContext: self.viewContext,
                                                                            secret: secret))
                } else {
                    EmptyView()
                }
            })
    }

    <snip>
}

This works just fine, and my secret is displayed as expected. As you see, the edit View is presented as a sheet when the user hits the Edit button. The edit view model looks like this:

class SecretEditViewModel: SecretCreateViewModel {
    
    @Published var secret: Secret
    
    init(managedObjectContext viewContext: NSManagedObjectContext, secret: Secret) {
        self.secret = secret
        super.init(managedObjectContext: viewContext)
        
        self.name = secret.name
        self.notes = secret.notes
        
        <snip>
    }
    
    func updateSecret() {
        let updatedSecret = self.buildSecret(secretCategory: secret.category,
                                             secretIdentifier: secret.id,
                                             createdDate: secret.createdDate)
        
        secretsService.updateSecret(secret: updatedSecret)
    }
    
}

…and my edit View looks like this:

struct SecretCreateEditView: View {
    
    <snip>

    @ObservedObject var viewModel: SecretCreateViewModel
    
    @State var secretCategory: SecretCategory?
    
    private let editMode: Bool
    
    init(viewModel: SecretCreateViewModel) {
        self.viewModel = viewModel
        self.editMode = false
    }
    
    init(viewModel: SecretCreateViewModel, secretCategory: SecretCategory) {
        self.viewModel = viewModel
        _secretCategory = State(initialValue: secretCategory)
        self.editMode = false
        self.viewModel.secretCategory = secretCategory
    }
    
    init(editViewModel: SecretEditViewModel) {
        self.viewModel = editViewModel
        _secretCategory = State(initialValue: editViewModel.secret.category)
        self.editMode = true
    }
    
    var body: some View {
        NavigationView {
            List {
                buildViewForSecretCategory(secretCategory: self.secretCategory)
                
                Section(header: Text("Notes")) {
                    TextEditor(text: $viewModel.notes)
                        .lineLimit(10)
                }
                .isHidden(secretCategory == nil)
            }
            .listStyle(GroupedListStyle())
            .navigationTitle("Edit Secret")
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    Button("Cancel") {
                        self.presentationMode.wrappedValue.dismiss()
                    }
                }
                
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button(action: saveSecret) {
                        Text(self.editMode ? "Done" : "Save")
                    }
                    .disabled(self.secretCategory == nil || self.viewModel.name.isEmpty)
                }
            }
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
    
    // MARK: - Private methods
    
    private func saveSecret() {
        if let model = self.viewModel as? SecretEditViewModel {
            model.updateSecret()
            self.presentationMode.wrappedValue.dismiss()
        } else {
            if let _ = self.secretCategory {
                self.viewModel.saveSecret()
                self.presentationMode.wrappedValue.dismiss()
            } else {
                // TODO: Handle error
            }
        }
    }
    
    <snip>
}

As you can see in the 2 Views, when my edit View is dismissed the display View will call self.viewModel.loadSecret() to refresh the View Model with the value from Core Data. If I set a breakpoint or print out a description of the View Model I see the values being updated as expected in the underlying data model. However, my View is not updated! For example, if I change the name of the secret or the username, the data in the View Model is shown to be correct after the edit View is dismissed, but the display View itself is not updated with the appropriate new values. If I go back in my NavigationView stack to the list of secrets (code not shown because it’s not necessary) and then view the secret in question again the values are updated as expected. Just not immediately following an edit, even though the data itself is updated.

Am I missing something here? I am new to @State, @Published, @ObservedObject, etc. so I very well could be misunderstanding something. Any help you can give is greatly appreciated!