TextField with custom validation

Hi guys i have searched different approaches to validate a TextField but i didn’t found any answer that fits my issue. I have a local JSON file which comes with multiple “questions” and “answers”. I have succesffuly manged to decode JSON and display however i want to let user’s type in an answer and then compare that to all of the possible answers from the JSON for that particular question and maybe add a green chevron if it’s validated. I will post my code bellow so you can understand this better .

// CONTENT VIEW

import SwiftUI

struct ContentView: View {
    var item: [Exercises] = Bundle.main.decode("random.json")
    @State private var items1: [Exercises] = []
    @State private var isMatched = false
    
    var body: some View {
        NavigationStack {
            List($items1) { $item in
                Text(item.question)
                TextField("Tap to create", text: $item.text)
                    .textInputAutocapitalization(.never)
                    
            }
            .navigationTitle("Variables")
        }
            
            
            Button(action: {
              addItem()
            }, label: {
                Text("Button")
            })
        }
    
    private func addItem() {
        guard let newItem = item.randomElement() else {return}
        withAnimation {
            items1.insert(newItem, at: 0)
        }
    }
    
    
    
}
 
#Preview {
    ContentView()
}

// MODEL

import Foundation


struct Exercises: Codable, Identifiable, Hashable {
    let id: Int
    let question: String
    var answer: [String]
    var text: String
}


    extension Bundle {
        func decode<T: Codable>(_ file: String) -> T {
            guard let url = self.url(forResource: file, withExtension: nil) else {
                fatalError("Could not find \(file) in bundle.")
            }
            
            guard let data = try? Data(contentsOf: url) else {
                fatalError("Could not load \(file) from bundle.")
            }
            
            let decoder = JSONDecoder()
            
            guard let loadedData = try? decoder.decode(T.self, from: data) else {
                fatalError("Could not decode \(file) from bundle.")
            }
            
            return loadedData
        }
    }

// JSON

[
    {
        "id": 1,
        "question": "Create a variable called \"greeting\" and assign the value: Hello",
        "answer": ["var greeting = \"Hello\"","var greeting = \"hello\""],
        "text": ""
    },
    
    {
        "id": 2,
        "question": "Create a variable called \"person\" and assign the value: John",
        "answer": ["var person = \"John\"","var person = \"john\""],
        "text": ""
    },
    
    {
        "id": 3,
        "question": "Create a variable called \"city\" and assign the value: London",
        "answer": ["var city = \"London\"","var city = \"london\""],
        "text": ""
    },
    
    {
        "id": 4,
        "question": "Create a variable called \"food\" and assign the value: kebab",
        "answer": ["var food = \"Kebab\"","var food = \"kebab\""],
        "text": ""
    },
    
    {
        "id": 5,
        "question": "Create a variable called \"country\" and assign the value: United Kingdom",
        "answer": ["var country = \"United Kingdom\"","var country = \"united kingdom\""],
        "text": ""
    },
    
    {
        "id": 6,
        "question": "Create a constant called \"car\" and assign the value: orange",
        "answer": ["let car = \"orange\"","let car = \"Orange\""],
        "text": ""
    },
    
    {
        "id": 7,
        "question": "Create a constant called \"laptop\" and assign the value: MacBook",
        "answer": ["let laptop = \"macbook\"","let laptop = \"Macbook\""],
        "text": ""
    },
    
    {
        "id": 8,
        "question": "Create a constant called \"planet\" and assign the value: Mars",
        "answer": ["let planet = \"mars\"","let planet = \"Mars\""],
        "text": ""
    },
    
    {
        "id": 9,
        "question": "Create a constant called \"type\" and assign the value: electric",
        "answer": ["let type = \"electric\"","let type = \"Electric\""],
        "text": ""
    },
    
    {
        "id": 10,
        "question": "Create a constant called \"color\" and assign the value: green",
        "answer": ["let color = \"green\"","let color = \"Green\""],
        "text": ""
    },
    
    {
        "id": 11,
        "question": "Create a variable of type int called \"house\" and assign the value: 5",
        "answer": ["var house = 5"],
        "text": ""
    },
    
    {
        "id": 12,
        "question": "Create a variable of type int called \"doors\" and assign the value: 2",
        "answer": ["var doors: Int = 2"],
        "text": ""
    },
    
    {
        "id": 13,
        "question": "Create a constant of type int called \"windows\" and assign the value: 10",
        "answer": ["let windows: Int = 10"],
        "text": ""
    },
    
    {
        "id": 14,
        "question": "Create a constant of type int called \"wheels\" and assign the value: 4",
        "answer": ["let wheels: Int = 4"],
        "text": ""
    }
]

@OsmanM

Hi Osman,

Welcome to the community.

I managed to get something working using your code and your json file.

I assumed that the item array is to hold all of the exercises that you have in the json file and the intention was to use the Button to add an exercise to the array item1 which would then be at the top of the list for the user to answer.

The issue I have observed is that if you select an exercise at random from the item array there is a good chance that the same one will be selected at random and since the id property of the exercise struct is there to ensure that an array item is unique then you are going to get a clash in the item1 array when you get a duplicate inserted. That will cause a warning message in the console to the effect that a duplicate id will cause unpredictable results.
The way around that is to check that every subsequent random selection has not been previously selected. A check also needs to be made to see if all the questions have already been added and not attempt to add any more.

The tricky part is how to deal with the answer obtained from the user and compare that to the available answers particularly if it is a string assignment question such as let color = "orange"
The reason being that iOS uses a smart double quotes in the TextField and that looks like:

let color = “green”

which, if you look very closely, has an opening smart quote which is different to the closing smart quote around the word green. Each needs to be substituted in turn so that the test to see if the answer is correct is made between two equivalent strings.

Here is the project I created based on your code. I hope you can understand what I have done and hope it helps.

thank you Chris for solving my issue

.randomElement() gave me some real headache as i had to figure out how to stop it from adding the same thing over and over resulting in that console error. Thank you man

1 Like