🏓

【SwiftUI】Combineを用いたフォームバリデーション

2023/11/07に公開
import SwiftUI
import Combine

final class ViewModel: ObservableObject {
    @Published var name = ""
    @Published var isValidName = false
    @Published var password = ""
    @Published var isValidPassword = false
    @Published var isButtonEnabled = false
    
    static let shared = ViewModel()
    private init() {
        $name
            .removeDuplicates()
            .map { str in
                return str.count >= 4
            }
            .assign(to: &$isValidName)
        $password
            .removeDuplicates()
            .map { str in
                return str.count >= 8
            }
            .assign(to: &$isValidPassword)
        Publishers.CombineLatest($isValidName, $isValidPassword)
            .map { isValidName, isValidPassword in
                return isValidName && isValidPassword
            }
            .assign(to: &$isButtonEnabled)
    }
}

struct ContentView: View {
    @StateObject var vm = ViewModel.shared
    
    var body: some View {
        VStack {
            HStack {
                TextField("Name", text: $vm.name)
                    .textFieldStyle(.roundedBorder)
                Image(systemName: "checkmark")
                    .foregroundStyle(vm.isValidName ? .green : .gray)
                    .fontWeight(vm.isValidName ? .bold : nil)
            }
            .padding()
            HStack {
                TextField("Password", text: $vm.password)
                    .textFieldStyle(.roundedBorder)
                Image(systemName: "checkmark")
                    .foregroundStyle(vm.isValidPassword ? .green : .gray)
                    .fontWeight(vm.isValidPassword ? .bold : nil)
            }
            .padding()
            Button {
                print("name: \(vm.name)")
                print("password: \(vm.password)")
            } label: {
                Text("Submit")
            }
            .disabled(!vm.isButtonEnabled)
            .buttonStyle(.borderedProminent)
        }
    }
}

#Preview {
    ContentView()
}

$nameにcombineLatest($password)で繋げてもいいが、$nameと$passwordの関係性は対等のため、Publishers.CombineLatest($isValidName, $isValidPassword)"としている
個人的な趣向

実際のところ".disabled(!vm.isButtonEnabled)"は".disabled(vm.isValidName && vm.isValidPassword ? false : true)"でOK

Discussion