Skip to content

Instantly share code, notes, and snippets.

@Alex-Ozun
Last active January 10, 2025 18:33
Show Gist options
  • Save Alex-Ozun/9103832faac41dd34ccfd87868a92002 to your computer and use it in GitHub Desktop.
Save Alex-Ozun/9103832faac41dd34ccfd87868a92002 to your computer and use it in GitHub Desktop.
struct Email {
init?(_ string: String) {
// parsing
}
}
struct PhoneNumber {
init?(_ string: String) {
// parsing
}
}
enum ContactDetails {
case email(Email)
case phone(PhoneNumber)
case both(Email, PhoneNumber)
init?(email: String, phoneNumber: String) {
switch (Email(email), PhoneNumber(phoneNumber)) {
case let (email?, phoneNumber?):
self = .both(email, phoneNumber)
case let (email?, .none):
self = .email(email)
case let (.none, phoneNumber?):
self = .phone(phoneNumber)
case (.none, .none):
return nil
}
}
}
struct Form: View {
@State var email: String = ""
@State var phoneNumber: String = ""
var contactDetails: ContactDetails? {
ContactDetails(email: email, phoneNumber: phoneNumber)
}
var body: some View {
VStack {
TextField("Email", text: $email)
TextField("Phone Number", text: $phoneNumber)
Button("Register") {
if let contactDetails {
register(with: contactDetails)
}
}
.disabled(contactDetails == nil)
}
}
func register(with contactDetails: ContactDetails) {}
}
// WITH INPUT VALIDATION
protocol Parsable {
associatedtype ParsingError: Error
init(input: String) throws(ParsingError)
}
enum InputState<Value: Parsable> {
case empty
case invalid(inputText: String, errorMessage: String?)
case valid(inputText: String, value: Value)
// To be used as proof of validation
var value: Value? {
guard case let .valid(_, value) = self else { return nil }
return value
}
// To be displayed on UI
var inputText: String {
switch self {
case .empty:
return ""
case let .invalid(inputText, _),
let .valid(inputText, _):
return inputText
}
}
// To be displayed on UI
var errorMessage: String? {
guard case let .invalid(_, errorMessage) = self else { return nil }
return errorMessage
}
init(inputText: String) {
guard inputText.isEmpty else {
self = .empty
}
do {
self = .valid(inputText: inputText, value: try Value(input: inputText))
} catch {
self = .invalid(inputText: inputText, errorMessage: error.localizedDescription)
}
}
}
struct FormWithInputValidation: View {
@State var email: InputState<Email> = .empty
@State var phoneNumber: InputState<PhoneNumber> = .empty
var contactDetails: ContactDetails? {
ContactDetails(email: email, phoneNumber: phoneNumber)
}
var body: some View {
VStack {
TextField(
"Email",
text: .init(
get: {
email.inputText
}, set: { inputText in
email = InputState<Email>(inputText: inputText)
}
)
)
if let errorMessage = email.errorMessage {
Text(errorMessage)
}
TextField(
"Phone Number",
text: .init(
get: {
phoneNumber.inputText
}, set: { inputText in
phoneNumber = InputState<PhoneNumber>(inputText: inputText)
}
)
)
if let errorMessage = phoneNumber.errorMessage {
Text(errorMessage)
}
Button("Register") {
if let contactDetails {
register(with: contactDetails)
}
}
.disabled(contactDetails == nil)
}
}
func register(with contactDetails: ContactDetails) {}
}
extension ContactDetails {
init?(email: InputState<Email>, phoneNumber: InputState<PhoneNumber>) {
switch (email.value, phoneNumber.value) {
case let (email?, phoneNumber?):
self = .both(email, phoneNumber)
case let (email?, .none):
self = .email(email)
case let (.none, phoneNumber?):
self = .phone(phoneNumber)
case (.none, .none):
return nil
}
}
}
extension Email: Parsable {
struct ParsingError: Error {
let errorMessage: String
}
init(input: String) throws(ParsingError) {
// parsing
}
}
extension PhoneNumber: Parsable {
struct ParsingError: Error {
let errorMessage: String
}
init(input: String) throws(ParsingError) {
// parsing
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment