代码之家  ›  专栏  ›  技术社区  ›  Shadowman

SwiftUI-编辑后视图未更新

  •  0
  • Shadowman  · 技术社区  · 5 年前

    我正在编写一个简单的密码管理器,作为熟悉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 等等,所以我很可能误解了什么。非常感谢您提供的任何帮助!

    0 回复  |  直到 5 年前