I’m trying to create a button with a generic function that chooses random elements from an array , checks if the textfield input matches the correct words associated with a particular element from the array then returns true if it does. Please see my code to understand this better
my head is burning trying to make this a generic approach please advise guys.
I just want a button that i can add more cards to the view. Lets say i don’t want just variables i want to create another set of instructions with constants or integers
//MARK: LOGIC
import Foundation import Observation @Observable class VariablesViewModel { var textField = "" private var words = ["\"Hello\"","\"Hello \"" ,"\"hello\"", "\" Hello\"", "\" hello\""] func wordMatch() -> Bool { words.contains(where: textField.contains) } } //MARK: VIEW import SwiftUI struct Variables: View { @State var viewModel = VariablesViewModel() var body: some View { VStack(alignment: .leading, spacing: 40) { Text("Create a variable called \"greeting\" and assign the value \"Hello\".") .font(.title2) .fontWeight(.semibold) HStack { Text("1") .font(.title2) .foregroundStyle(.gray) Text("var") .font(.title2) .foregroundStyle(.pink) Text("greeting =") .font(.title2) TextField(text: $viewModel.textField ) { Text("Tap to assign \"Hello\"") .font(.title2) } .font(.title2) .foregroundStyle(.orange) if viewModel.wordMatch() { Image(systemName: "checkmark.circle.fill") .imageScale(.large) .foregroundStyle(.green) } } } .alignment(.topLeading) } } #Preview { Variables() } extension View { @ViewBuilder func alignment(_ alignment: Alignment) -> some View { self.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: alignment) } }
>Solution :
Here is one way to make the view model generic where you pass both the array values and a matching logic closure at init
class VariablesViewModel<Value> {
var input: Value?
var matcher: (Value, [Value]) -> Bool
private var values: [Value]
init(values: [Value], matcher: @escaping (Value, [Value]) -> Bool) {
self.matcher = matcher
self.values = values
}
func valueMatch() -> Bool {
guard let input else { return false }
return matcher(input, values)
}
}
Examples
let viewModel = VariablesViewModel(values: ["Hello","Hello " ,"hello", " Hello", " hello"]) {
$1.contains(where: $0.contains)
}
let viewModel2 = VariablesViewModel(values: [1,3,5,7,9]) {
$1.contains($0)
}
Personally I would prefer to pass the value to match rather than having it as a property, in your view it would then be a @State property instead than a view model property
class VariablesMatcher<Value> {
var matcher: ([Value], Value) -> Bool
private var values: [Value]
init(values: [Value], matcher: @escaping ([Value], Value) -> Bool) {
self.matcher = matcher
self.values = values
}
func valueMatch(input: Value) -> Bool {
return matcher(values, input)
}
}
Examples
let matcher1 = VariablesMatcher(values: ["Hello","Hello " ,"hello", " Hello", " hello"]) {
$0.contains(where: $1.contains)
}
matcher1.valueMatch(input: "Hello")
let matcher2 = VariablesMatcher(values: [1,3,5,7,9]) {
$0.contains($1)
}
matcher2.valueMatch(input: 3)