Created
April 18, 2024 16:00
-
-
Save cutiko/d97e39e509ed0d2eed62994d4728b140 to your computer and use it in GitHub Desktop.
Hoisted state for forms in iOS (with UI Model)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
struct ContentView: View { | |
@ObservedObject var viewModel = FormViewModel() | |
var body: some View { | |
VStack { | |
SimpleInput($viewModel.screenState.firstInput) { | |
print("CUTIKO_TAG FIRST focus lost") | |
viewModel.validateFirstInput() | |
} | |
Text(viewModel.screenState.firstInputError) | |
SimpleInput($viewModel.screenState.secondInput) { | |
print("CUTIKO_TAG SECOND focus lost") | |
viewModel.validateSecondInput() | |
} | |
Text(viewModel.screenState.secondInputError) | |
SimpleInput($viewModel.screenState.thirdInput) { | |
print("CUTIKO_TAG THIRD focus lost") | |
} | |
Text(verbatim: "isEnabled: \(viewModel.screenState.isValid)") | |
Button( | |
action: { | |
print("CUTIKO_TAG UI MODEL IS: \(viewModel.screenState)") | |
}, | |
label: { | |
Text("Validate UI MODEL in logs") | |
} | |
) | |
} | |
.padding() | |
} | |
} | |
private struct SimpleInput: View { | |
var text: Binding<String> | |
@FocusState private var isFocused: Bool | |
private let onFocusLost: () -> Void | |
@State private var previousFocus = false | |
init(_ text: Binding<String>, onFocusLost: @escaping () -> Void) { | |
self.text = text | |
self.onFocusLost = onFocusLost | |
} | |
var body: some View { | |
TextField( | |
"INPUT:", | |
text: self.text | |
) | |
.focused($isFocused) | |
.onChange(of: isFocused) { newFocus in | |
if (previousFocus && !newFocus) { | |
self.onFocusLost() | |
} | |
previousFocus = newFocus | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
struct FormState { | |
private static let noError = "NO ERROR" | |
private var firstInputLostFocus = false | |
var firstInput: String = "" { | |
didSet { | |
if firstInputLostFocus { validateFirstInput() } | |
} | |
} | |
private var secondInputLostFocus = false | |
var firstInputError = FormState.noError | |
var secondInput = "" { | |
didSet { | |
if secondInputLostFocus { validateSecondInput() } | |
} | |
} | |
var secondInputError = FormState.noError | |
var thirdInput = "" | |
var isValid: Bool { | |
return isFirstInputValid() && isSecondInputValid() && !thirdInput.isEmpty | |
} | |
private func isFirstInputValid() -> Bool { | |
return !firstInput.isEmpty | |
} | |
private func isSecondInputValid() -> Bool { | |
return secondInput.count >= 3 | |
} | |
mutating func validateFirstInput() { | |
firstInputLostFocus = true | |
if isFirstInputValid() { | |
firstInputError = FormState.noError | |
} else { | |
firstInputError = "PLEASE WRITE THE FIRST INPUT" | |
} | |
} | |
mutating func validateSecondInput() { | |
secondInputLostFocus = true | |
if isSecondInputValid() { | |
secondInputError = FormState.noError | |
} else { | |
secondInputError = "Second input is VERY important, 3 char min" | |
} | |
} | |
} | |
extension FormState { | |
static let initialValue = FormState() | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
class FormViewModel: ObservableObject { | |
@Published var screenState = FormState.initialValue | |
func validateFirstInput() { | |
screenState.validateFirstInput() | |
} | |
func validateSecondInput() { | |
screenState.validateSecondInput() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment