我正在编写一个简单的密码管理器,作为熟悉SwiftUI的学习练习。到目前为止一切顺利,但我遇到了一个问题,我的
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)
}
}
…以及显示器
查看
已设置为呼叫
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>
}
这工作得很好,我的秘密正如预期的那样被展示出来。如您所见,编辑
查看
当用户点击“编辑”按钮时,显示为一张纸。编辑视图模型如下所示:
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)
}
}
…和我的编辑
查看
看起来像这样:
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>
}
正如你在2所看到的
查看
s、 当我的编辑视图被关闭时,显示
查看
会打电话来
self.viewModel.loadSecret()
使用核心数据中的值刷新视图模型。如果我设置断点或打印出视图模型的描述,我会看到底层数据模型中的值正在按预期更新。然而,我的
查看
未更新!例如,如果我更改了秘密的名称或用户名,则编辑后视图模型中的数据将显示为正确
查看
已关闭,但显示
查看
其本身没有用适当的新值更新。如果我回到我的
NavigationView
堆叠到机密列表(代码未显示,因为没有必要),然后再次查看有问题的机密,值会按预期更新。只是没有
立即
在编辑之后,即使数据本身已经更新。
我错过了什么吗?我是新手
@State
,
@Published
,
@ObservedObject
等等,所以我很可能误解了什么。非常感谢您提供的任何帮助!