From 494dba46216388a7723eac293d02081eebeb56f4 Mon Sep 17 00:00:00 2001 From: Igor Kulman Date: Sat, 9 Nov 2024 16:24:32 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Basic=20form=20validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core/{ => Extensions}/View+Error.swift | 0 .../Sources/Core/Extensions/View+If.swift | 20 +++++++ .../Setup/Extensions/String+Extensions.swift | 16 ++++++ .../Setup/ViewModels/AddSourceViewModel.swift | 14 +---- .../Features/Setup/Views/AddSourceView.swift | 17 ++---- .../Features/Setup/Views/FormField.swift | 54 +++++++++++++++++++ 6 files changed, 95 insertions(+), 26 deletions(-) rename sources/Core/Sources/Core/{ => Extensions}/View+Error.swift (100%) create mode 100644 sources/Core/Sources/Core/Extensions/View+If.swift create mode 100644 sources/Features/Sources/Features/Setup/Extensions/String+Extensions.swift create mode 100644 sources/Features/Sources/Features/Setup/Views/FormField.swift diff --git a/sources/Core/Sources/Core/View+Error.swift b/sources/Core/Sources/Core/Extensions/View+Error.swift similarity index 100% rename from sources/Core/Sources/Core/View+Error.swift rename to sources/Core/Sources/Core/Extensions/View+Error.swift diff --git a/sources/Core/Sources/Core/Extensions/View+If.swift b/sources/Core/Sources/Core/Extensions/View+If.swift new file mode 100644 index 0000000..3909629 --- /dev/null +++ b/sources/Core/Sources/Core/Extensions/View+If.swift @@ -0,0 +1,20 @@ +// +// File.swift +// Core +// +// Created by Igor Kulman on 09.11.2024. +// + +import Foundation +import SwiftUI + +public extension View { + @ViewBuilder + func `if`(_ conditional: Bool, content: (Self) -> Content) -> some View { + if conditional { + content(self) + } else { + self + } + } +} diff --git a/sources/Features/Sources/Features/Setup/Extensions/String+Extensions.swift b/sources/Features/Sources/Features/Setup/Extensions/String+Extensions.swift new file mode 100644 index 0000000..49199b3 --- /dev/null +++ b/sources/Features/Sources/Features/Setup/Extensions/String+Extensions.swift @@ -0,0 +1,16 @@ +// +// File.swift +// Features +// +// Created by Igor Kulman on 09.11.2024. +// + +import Foundation + +extension String { + var isValidURL: Bool { + let regEx = "((?:http|https)://)?(?:www\\.)?[\\w\\d\\-_]+\\.\\w{2,3}(\\.\\w{2})?(/(?<=/)(?:[\\w\\d\\-./_]+)?)?" + let predicate = NSPredicate(format: "SELF MATCHES %@", argumentArray: [regEx]) + return predicate.evaluate(with: self) + } +} diff --git a/sources/Features/Sources/Features/Setup/ViewModels/AddSourceViewModel.swift b/sources/Features/Sources/Features/Setup/ViewModels/AddSourceViewModel.swift index 7352d42..ac7193a 100644 --- a/sources/Features/Sources/Features/Setup/ViewModels/AddSourceViewModel.swift +++ b/sources/Features/Sources/Features/Setup/ViewModels/AddSourceViewModel.swift @@ -16,20 +16,8 @@ final class AddSourceViewModel { var rssUrl: String = "" var imageUrl: String = "" - var isTitleValid: Bool { - !title.isEmpty - } - - var isUrlValid: Bool { - !url.isEmpty && URL(string: url) != nil - } - - var isRssUrlValid: Bool { - !rssUrl.isEmpty && URL(string: rssUrl) != nil - } - var isValid: Bool { - isTitleValid && isUrlValid && isRssUrlValid + !title.isEmpty && url.isValidURL && rssUrl.isValidURL && (imageUrl.isEmpty || imageUrl.isValidURL) } private let onFinished: (RssSource?) -> Void diff --git a/sources/Features/Sources/Features/Setup/Views/AddSourceView.swift b/sources/Features/Sources/Features/Setup/Views/AddSourceView.swift index 0c17b77..28555dc 100644 --- a/sources/Features/Sources/Features/Setup/Views/AddSourceView.swift +++ b/sources/Features/Sources/Features/Setup/Views/AddSourceView.swift @@ -21,25 +21,16 @@ struct AddSourceView: View { var body: some View { Form { Section(header: Text("Title", bundle: .module)) { - TextField("", text: $viewModel.title) + FormField(type: .string(required: true), text: $viewModel.title) } Section(header: Text("URL", bundle: .module)) { - TextField("", text: $viewModel.url) - .keyboardType(.URL) - .disableAutocorrection(true) - .autocapitalization(.none) + FormField(type: .url(required: true), text: $viewModel.url) } Section(header: Text("RSS URL", bundle: .module)) { - TextField("", text: $viewModel.rssUrl) - .keyboardType(.URL) - .disableAutocorrection(true) - .autocapitalization(.none) + FormField(type: .url(required: true), text: $viewModel.rssUrl) } Section(header: Text("Image URL (optional)", bundle: .module)) { - TextField("", text: $viewModel.imageUrl) - .keyboardType(.URL) - .disableAutocorrection(true) - .autocapitalization(.none) + FormField(type: .url(required: false), text: $viewModel.imageUrl) } }.navigationBarTitle(Text("Add source", bundle: .module)) .toolbar { diff --git a/sources/Features/Sources/Features/Setup/Views/FormField.swift b/sources/Features/Sources/Features/Setup/Views/FormField.swift new file mode 100644 index 0000000..f1dcb17 --- /dev/null +++ b/sources/Features/Sources/Features/Setup/Views/FormField.swift @@ -0,0 +1,54 @@ +// +// File.swift +// Features +// +// Created by Igor Kulman on 09.11.2024. +// + +import Foundation +import SwiftUI + +struct FormField: View { + enum FieldType { + case string(required: Bool) + case url(required: Bool) + + var isURL: Bool { + switch self { + case .url: + return true + case .string: + return false + } + } + + func validate(text: String) -> Bool { + switch self { + case let .string(required): + return !required || !text.isEmpty + case let .url(required): + return !required || text.isValidURL + } + } + } + + let type: FieldType + @Binding var text: String + @State var isValid = true + + var body: some View { + TextField("", text: $text, onEditingChanged: { isEditing in + if isEditing { + isValid = true + } else { + isValid = type.validate(text: text) + } + }) + .foregroundColor(isValid ? .primary : .red) + .if(type.isURL) { + $0.keyboardType(.URL) + .disableAutocorrection(true) + .autocapitalization(.none) + } + } +}