I am new to SwiftUI and there is a scenario in which I can add more than one person's data and every time I tap on the button, it will collect new person's data.
The scenario is like this:
I add data on one textfield, it updates on every textfield because there is only one state variable for the textfield. My problem is how can I add multiple State variables for the textfield as the textfields have no fixed number. My code is:
import SwiftUI
struct TextFieldText: View {
#State private var name = ""
#State private var email = ""
#State var totalValue: Int = 1
var body: some View {
VStack(spacing: 30) {
ForEach((1...totalValue).reversed(), id: \.self) {_ in
VStack {
CustomTextfield(text: $name, placeHolder: "Enter name", title: "Enter Name")
CustomTextfield(text: $email, placeHolder: "Enter email", title: "Enter Email")
}
}
Button {
print("add person tapped")
totalValue = totalValue + 1
} label: {
ZStack {
RoundedRectangle(cornerRadius: 60)
.frame(width: 180, height: 45)
.foregroundColor(Color(ColorName.appBlue.rawValue))
Text("Add another person")
.foregroundColor(.white)
.font(Font.custom(InterFont.bold.rawValue, size: 14))
}
}
Spacer()
}
}
}
struct TextFieldText_Previews: PreviewProvider {
static var previews: some View {
TextFieldText()
}
}
I want to add different data on every textfield. How can I achieve it in SwiftUI?
You want to handle multiple people but you have only one name and one email property.
You need an array. A swifty way is a custom struct
struct Person {
var name, email : String
}
In the view replace name and email with an empty array of Person
#State private var people = [Person]()
In the ForEach loop iterate over the indices and bind the text parameter to the person at given index.
I don't have your custom text fields, the code uses the default fields
ForEach(people.indices, id: \.self) { index in
VStack {
TextField("Enter name", text: $people[index].name)
TextField("Enter email", text: $people[index].email)
}
.padding()
}
Finally in the button action add a new Person to people
Button {
print("add person tapped")
people.append(Person(name: "", email: ""))
Related
I have a view in which the user enters data into a several textfields, but I can't allow the user to exit the textfields; one of them is always selected. How do I make the fields un-focus when I click on something else (the background, the submit button, etc.)?
Current View:
struct ContentView: View {
var body: some View {
Form {
TextField("Username", text: $username)
TextField("Password", text: $password)
Button("Submit") {
// Submit data
}
}
}
}
You need to use an optional #FocusState and use .allowsHitTesting(true). On the form, you put a .onTapGesture that sets #FocusState to nil.
struct ContentView: View {
#State private var username: String = ""
#State private var password: String = ""
#FocusState private var focusedField: String?
var body: some View {
Form {
TextField("Username", text: $username)
.focused($focusedField, equals: "user")
TextField("Password", text: $password)
.focused($focusedField, equals: "password")
Button("Submit") {
// Submit data
focusedField = nil
}
}
.onTapGesture {
focusedField = nil
}
}
}
The .allowsHitTesting(true) lets the TextFields accept tap gestures directly, otherwise they would be blocked by the Form's .onTapGesture.
On macOS, in the sidebar, the form is only the size of the Button and TextFields. If you wanted a larger tap area, you would need some kind of background to place it on.
I have this SwiftUI view (will remove non-relevant parts)
struct LoginView: View {
var body: some View {
VStack {
Spacer()
InputField(title: "Email", text: $email)
InputField(title: "Password", text: $password, showingSecureField: true)
InputField(title: "Store", text: $userStore)
CallToActionButton(
title: newUser ? "Register User" : "Log In",
action: userAction
)
Button(action: { newUser.toggle() }) {
HStack {
CheckBox(title: "Create Account", isChecked: $newUser)
Spacer()
}
}
Spacer()
}
}
I can write a UI Test for that button with this code:
func testClickLoginButton() throws {
let app = XCUIApplication()
app.launch()
let startButton = app.buttons["Log In"]
XCTAssertTrue(startButton.waitForExistence(timeout: 1))
XCTAssertTrue(startButton.isEnabled)
startButton.tap()
}
And it works, as there's a button with that title on screen.
But then I want to automatically put some text in those InputTexts and test them, but can't find them, as they're neither UITextField nor UITextView. How can I accomplish this? Thanks!
func testAddEmail() throws {
let app = XCUIApplication()
app.launch()
// fails as can't find any text field containing "Email"
let emailText = app.textFields["Email"]
XCTAssertTrue(emailText.waitForExistence(timeout: 1))
emailText.typeText("testemail#realm.com")
XCTAssertEqual(emailText.value as! String, "testemail#realm.com")
}
How about adding:
InputField(title: "Email", text: $email)
.accessibility(identifier: "email_input")
Same for any other element you want to test from UI Tests?
I am not an expert and have seen different ways to do UITests in SwiftUI. There are perhaps better ways with snapshots etc but I quickly recreated your example for fun and added an accessibility identifier:
struct InputField: View {
var title: String
#Binding var email: String
var body: some View {
VStack(alignment: .leading) {
Text("Enter your email ")
TextField(title, text: $email)
.accessibility(identifier: "Email")
}
.padding()
}
}
struct ContentView: View {
#State private var email: String = ""
var body: some View {
InputField(title: "Email: ", email: $email)
}
}
Also it might be sometimes required to add a tap before entering the email or text. It was giving me an error because of this.
The UItest would look like this and is passing :)
func testAddEmail() throws {
let app = XCUIApplication()
app.launch()
let emailText = app.textFields["Email"]
XCTAssertTrue(emailText.waitForExistence(timeout: 1))
emailText.tap()
emailText.typeText("testemail#realm.com")
XCTAssertEqual(emailText.value as! String, "testemail#realm.com")
}
Also a cool trick I learned is to put a breakpoint like this:
and in the debugger at the prompt enter the following:
(lldb) po XCUIApplication().textFields
And so you get the list and you see if identifiers are missing. Same thing for buttons and staticTexts etc.
I am trying to add an icon ( SFSymbol) called "exclamationmark.triangle" if the field is empty to identify that it's a required text field. Below is the method I used. It works, but not properly. When the user clicks on a text field and starts typing after the first character keyboard goes away, so the user has to retap to continue typing.
if name.isEmpty {
HStack {
TextField("First & Last Name", text: $name)
Image(systemName: "exclamationmark.triangle")
}
} else {
TextField("First & Last Name", text: $name)
}
struct ContentView : View {
#State var name: String = ""
var body: some View {
HStack {
TextField("First & Last Name", text: $name)
name.isEmpty ? Image(systemName: "exclamationmark.triangle") : nil
}
}
}
I put the fieldtext in view called fieldtextmydesine and also put the button in view named login and I called fieldtextmydesin view and login view in contentview how do I print the field text value when I press the login button
So you want to use NavigationView and NavigationLink instead of a button.
struct ContentView: View {
#State var name: String = "Tim"
var body: some View {
NavigationView {
VStack {
TextField("Enter your name", text: $name)
Text("Hello, \(name)!")
NavigationLink(destination: SecondView(name: self.$name)){
Text("LogIn")
}
}
}
}
//Second ContenView
struct SecondView: View {
#Binding var name: String
var body: some View {
Text("Hello \(text)")
}
}
From what I understand from your question, you are trying to pass a value entered into a Text-Field from one View to another. If this is what your asking then this is the best solution.
This snippet can help you:
You can bind property to textfield like this
struct ContentView: View {
#State private var name: String = "Tim"
var body: some View {
VStack {
TextField("Enter your name", text: $name)
Text("Hello, \(name)!")
}
}
}
You can add button and print name on button tap line you need. You can pass name property to another text on tap. Or hide text view and show on tap and another ways
You can display print name either on the console or can display name in an alert. In the below snippet, to fetch name entered in text field on button click requires state variable instead of normal variable. It is created with #State keyword. State parameter manages the state in the View. So whenever there is change in state all the components that are associated with the state will be rendered again.
import SwiftUI
struct LoginUI: View {
#State var textName: String = ""
#State var showAlert = false
var body: some View {
VStack(alignment: .center, spacing: 2.0) {
TextField("Enter your name", text: $textName).padding(10)
Button(action: {
print("Entered name is \(self.textName)")
self.showAlert = true
}, label: {Text("Login")}).padding().background(Color.gray)
}
.padding(5.0)
.alert(isPresented: $showAlert) {
Alert(title: Text("Entered name is"), message: Text("\(self.textName)"))
}
}
}
struct LoginUI_Previews: PreviewProvider {
static var previews: some View {
LoginUI()
}
}
I am learning SwiftUI and I am trying to implement a Forgot Password Functionality . The text field will say by default Enter your email then an http call takes places if the email is found in our system then I would like the Text PlaceHolder to Say "Enter Verification Code" . I already have everything else working . This is my code below. They enter their email then the HTTP call handles the rest and returns either a 0 or 1 in a closure depending on if the email is found . In the code below if the foundEmail is 1 then the Text placeholder should change to Enter Verification Code
struct ForgotPassWordView: View {
#State private var textResponse = ""
var body: some View {
ZStack
{
Color.black
VStack {
NavigationView {
Form {
Section {
TextField("Enter Email", text: $textResponse)
// change to Verification Code if foundEmail is 1
}
Section {
HStack {
Spacer()
Button(action: {
if !self.textResponse.isEmpty {
_ = ForgotPasswordRequest(email: self.textResponse, section: 1) {(foundEmail) in
if foundEmail == 0 {
// not found do nothing
} else if foundEmail == 1
{
// found email change to : Enter Verification Code
}
}
}
}) {
Spacer()
Text("Submit").fontWeight(.bold).frame(width: 70.0)
Spacer()
}
Spacer()
}
}
}
.navigationBarTitle(Text(""))
}
.padding(.horizontal, 15.0)
Text("Error Response").foregroundColor(.white)
}
.frame(height: 400.0)
}.edgesIgnoringSafeArea(.all)
}
}
Hello there I think you can use two TextField and switch views using a boolean because the placeHolder of text field not Binding so it cannot be edit... let me show what I mean in code:
struct ContentView: View {
#State var email: String = ""
#State var verificationCode: String = ""
#State var showCodeField: Bool = false
var body: some View {
NavigationView {
VStack {
if (!showCodeField) {
TextField("Enter Valid Email", text: $email)
} else {
TextField("Enter Verification Code", text: $verificationCode)
}
Button(action: {
self.showCodeField.toggle()
}) {
Text("Verify Email")
}
}
.padding()
}
}
}
also if you have a complex view you can extract them as variables and control which one is visible or not like this:
var someField: some View {
ZStack(alignment: .center) {
RoundedRectangle(cornerRadius: 8).foregroundColor(Color.white)
TextField("Enter Email", text: $email)
}
}
}
and in body you just call it like this
var body: some View {
VStack {
if(isVisible) {
someField
}
}