警报自动消失(当它不应该时!)

问题描述 投票:0回答:1

我正在使用 MVVM 模式编写一个 SwiftUI 应用程序。我遇到了一种情况,我正在显示应用程序设置屏幕并提示用户输入密码。为了执行密码验证,我的

Publisher
中有一系列组合
ViewModel
,可以根据多种标准进行验证。如果密码验证成功,我的
View
中会启用“完成注册”按钮,点击该按钮即可建立帐户。如果创建帐户时发生错误,则会显示
Alert
。然而,我在测试中发现,当显示警报时,它会立即自行消失。这是我的
ViewModel
的样子:

struct ApplicationSetupState {
    
}

enum ApplicationSetupInput {
    case completeSetup
}

class ApplicationSetupViewModel: ViewModel {
    
    typealias ViewModelState = ApplicationSetupState
    typealias ViewModelInput = ApplicationSetupInput
    
    enum PasswordValidation {
        case valid
        case empty
        case noMatch
    }
    
    @Published var password: String = ""
    @Published var confirmPassword: String = ""
    @Published var useBiometrics: Bool = false
    
    @Published var isValid: Bool = false
    
    @Published var state: ApplicationSetupState
    @Published var errorMessage: String? = nil
    @Published var error: Bool = false
    
    private var cancellables: [AnyCancellable] = []
    
    private var isPasswordEmptyPublisher: AnyPublisher<Bool, Never> {
        $password
            .debounce(for: 0.8, scheduler: RunLoop.main)
            .removeDuplicates()
            .map { password in
                DDLogVerbose("Mapping password=\(password)")
                return password == ""
            }
            .eraseToAnyPublisher()
    }
    
    private var arePasswordsEqualPublisher: AnyPublisher<Bool, Never> {
        Publishers.CombineLatest($password, $confirmPassword)
            .debounce(for: 0.1, scheduler: RunLoop.main)
            .map { password, confirmPassword in
                DDLogVerbose("Mapping password=\(password), confirmPassword=\(confirmPassword)")
                return password == confirmPassword
            }
            .eraseToAnyPublisher()
    }
    
    private var isPasswordValidPublisher: AnyPublisher<PasswordValidation, Never> {
        Publishers.CombineLatest(isPasswordEmptyPublisher, arePasswordsEqualPublisher)
            .map { isPasswordEmpty, arePasswordsEqual in
                DDLogVerbose("Mapping isPasswordEmpty=\(isPasswordEmpty), arePasswordsEqual=\(arePasswordsEqual)")
                if isPasswordEmpty {
                    return .empty
                } else if !arePasswordsEqual {
                    return .noMatch
                }
                
                return .valid
            }
            .eraseToAnyPublisher()
    }
    
    private var isSetupValidPublisher: AnyPublisher<Bool, Never> {
        isPasswordValidPublisher
            .map { isPasswordValid in
                return isPasswordValid == .valid
            }
            .eraseToAnyPublisher()
    }
    
    init() {
        self.state = ApplicationSetupState()
        
        self.isPasswordValidPublisher
            .receive(on: RunLoop.main)
            .map { passwordValidation in
                DDLogVerbose("Mapping passwordValidation=\(passwordValidation)")
                switch passwordValidation {
                case .empty:
                    return "You must provide a master password"
                case .noMatch:
                    return "The passwords do not match.  Try again."
                default:
                    return ""
                }
            }
            .assign(to: \.errorMessage, on: self)
            .store(in: &cancellables)
        
        self.isSetupValidPublisher
            .receive(on: RunLoop.main)
            .assign(to: \.isValid, on: self)
            .store(in: &cancellables)
    }
    
    func trigger(_ input: ApplicationSetupInput) {
        switch input {
        case .completeSetup:
            self.completeApplicationSetup()
        }
    }
    
    // MARK: - Private methods
    
    private func completeApplicationSetup() {
        DDLogInfo("Completing application setup...")
        
        do {
            let status = try ApplicationSetupController.shared.completeApplicationSetup(withPassword: self.password,
                                                                                        useBiometrics: self.useBiometrics)
            DDLogVerbose("status=\(status)")
            if status == true {
                DDLogInfo("Application setup completed successfully.  Posting notification...")
                NotificationCenter.default.post(name: .didCompleteSetup, object: nil)
            } else {
                DDLogWarn("Application setup failed with no error message")
                self.error = true
                self.errorMessage = "An unknown error occurred during setup.  Please try again."
            }
        } catch {
            DDLogError("ERROR while completing application setup - \(error)")
            self.error = true
            self.errorMessage = error.localizedDescription
        }
    }
    
}

如您所见,我有一个名为

@State
error
变量,用于指示是否存在错误情况。我的
View
看起来像这样:

struct MasterPasswordSetupView: View {
    @ObservedObject var viewModel: ApplicationSetupViewModel
    
    var body: some View {
        VStack(alignment: .center) {
            ...stuff...
        }
            .alert(isPresented: self.$viewModel.error) {
                Alert(title: Text("Error"),
                      message: Text(self.viewModel.errorMessage ?? ""),
                      dismissButton: .default(Text("Ok")) {
                        DDLogVerbose("OK pressed!")
                      })
            }
    }
}

您会注意到,如果我的

Alert
中的错误为真,则会显示
ViewModel
。但是,我发现一旦显示
Alert
,它就会立即被忽略,即使我没有按“确定”按钮来关闭它。

我添加了一些日志记录,包括

didSet
上的
error
方法,并且看到在某些时候错误实际上被切换回 false。但是,我没有在代码中的任何地方明确执行此操作,因此我不知道为什么会发生这种情况。有什么想法吗?

swift swiftui combine
1个回答
3
投票

您是否尝试过创建@StateObject而不是@ObservedObject。

@StateObject var viewModel: ApplicationSetupViewModel

我认为问题是你的 viewModel 正在重新加载。

© www.soinside.com 2019 - 2024. All rights reserved.