首页 > 解决方案 > SwiftUI - 编辑后视图未更新

问题描述

我正在编写一个简单的密码管理器作为熟悉 SwiftUI 的学习练习。到目前为止一切顺利,但是我遇到了一个问题,即我View在编辑值后没有按预期更新。View我现在正在处理2 个:一个用于显示秘密,另一个用于编辑其属性。我将 MVVM 模式用于我的底层数据模型,Core Data/Combine 用于访问数据存储。我的“显示”视图模型如下所示:

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)
    }
}

...并且显示View设置为这样loadSecret()调用onAppear

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()
                }
            })
    }

@ViewBuilder
private func buildViewForSecret(secret: Secret?) -> some View {
    List {
        if let secretImpl = secret {
            SecretDisplayHeaderView(name: secretImpl.name, category: secretImpl.category.title)
        } else {
            EmptyView()
        }
        
        if let secretImpl = secret as? LoginSecret {
            LoginSecretDisplayForm(secret: secretImpl)
        } else if let secretImpl = secret as? BankAccountSecret {
            BankAccountSecretDisplayForm(secret: secretImpl)
        } else if let secretImpl = secret as? CreditCardSecret {
            CreditCardSecretDisplayForm(secret: secretImpl)
        } else if let secretImpl = secret as? WifiNetworkSecret {
            WifiNetworkSecretDisplayForm(secret: secretImpl)
        } else if let secretImpl = secret as? RewardProgramSecret {
            RewardProgramSecretDisplayForm(secret: secretImpl)
        } else {
            // TODO: Display a loading view or something
            EmptyView()
        }
        
        if let notes = secret?.notes {
            Section(header: Text("Notes")) {
                TextEditor(text: Binding.constant(notes))
            }
            .isHidden(notes.isEmpty)
        }
        
        // TODO: Secret actions (favorite/delete/share/etc.)
    }
}
    <snip>
}

这工作得很好,我的秘密按预期显示。如您所见,View当用户点击“编辑”按钮时,编辑显示为工作表。编辑视图模型如下所示:

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)
    }
    
}

...我的编辑View看起来像这样:

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>
}

正如您在 2View秒中看到的,当我的编辑视图被关闭时,显示View将调用self.viewModel.loadSecret()以使用来自核心数据的值刷新视图模型。如果我设置断点或打印出视图模型的描述,我会看到底层数据模型中的值正在按预期更新。但是,我View的没有更新!例如,如果我更改了密码的名称或用户名,则视图模型中的数据在编辑View被关闭后显示为正确,但显示View本身并未使用适当的新值进行更新。如果我回到我的NavigationView堆栈中的秘密列表(代码未显示,因为它不是必需的),然后再次查看有问题的秘密,则值会按预期更新。只是没有立即编辑后,即使数据本身已更新。

我在这里错过了什么吗?我是, , 等的新手@State,所以我很可能会误解某些东西。非常感谢您提供的任何帮助!@Published@ObservedObject

标签: iosswiftmvvmviewswiftui

解决方案


推荐阅读