Swiftui: how to watch variable binded to text field always - swift

I am using Swift-UI for creating my app.
There is an AccountView is listing user's attributes and you can update it.
Once you click an Update button on the user's variable row of the list, navigate to EditVariableView, where you can change the variable with Text Field.
Of course, the text field has a validation of the inputted text, and you can commit the change by the Submit button on the right-up corner of EditVariableView.
For validation of the input, I use onCommit, detecting the change of the input, but here is a problem.
When you touch the text field, the keyboard comes out, and also you can input the text. But onCommit emits an event only when you close the keyboard.
If you input the text and click the Submit button without closing the keyboard, certainly onCommit does not emit an event for the validation. So, of course, the validation won't be done.
I want you to tell me, how to detect the input change on every text change.

You can disable Submit button if TextField is in editing state
import SwiftUI
struct ContentView: View {
#State var txt: String = ""
#State var editingFlag = false
var body: some View {
VStack {
TextField("text", text: $txt, onEditingChanged: { (editing) in
self.editingFlag = editing
}) {
print("commit")
}.padding().border(Color.red)
Button(action: {
print("submit")
}) {
Text("SUBMIT")
}.disabled(editingFlag)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

SwiftUI 2.0
With the Xcode 12 and from iOS 14, macOS 11, or any other OS contains SwiftUI 2.0, there is a new modifier called 'onChange' that detects any change of the given state and can be performed on any view. So with some minor refactoring:
struct ContentView: View {
#State var txt: String = ""
#State var editingFlag = false
var body: some View {
VStack {
TextField("text", text: $txt)
.onChange(of: txt) {
print("Changed to :\($0)")
}
}
.padding()
.border(Color.red)
Button("SUBMIT") {
print("submit")
}
.disabled(editingFlag)
}
}

from: hacking with with Swift. Paul Hudson
struct ContentView : View {
#State private var name = ""
var body: some View {
TextField("Enter your name:", text: $name)
.textFieldStyle(RoundedBorderTextFieldStyle())
.onChange(of: name) { newValue in
print("Name changed to \(name)!")
}
}
}

Related

TextField typing not completing

I have a Form and TextField input that should update a variable in a separate view model with that text input.
The problem is that the first letter appears in the field fine, but when I click the next letter it deletes the first letter. Then when I click a letter again, nothing happens. Then I click a letter again and the same thing starts from the beginning.
Thanks for your help!
struct FormView: View {
#StateObject var listVM = ListVM()
var body: some View {
NavigationView {
VStack {
Form {
TextField("Summary", text: $listVM.textInput)
}
Button {
} label: {
HStack {
Image(systemName: "plus.circle.fill")
Text("Add summary").fontWeight(.bold)
}
}.buttonStyle(.borderedProminent)
.padding(.top)
.tint(.indigo)
}.navigationTitle("Create summary")
}
}
}
class ListVM: ObservableObject {
#State var textInput = ""
}
#State is only for SwiftUI Views you should be using #Published
#Published var textInput = ""

How do I remove focus from a TextField in SwiftUI on MacOS?

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.

Why does my SwiftUI View not update on updating of an #State var?

I am having a strange issue with an #State var not updating an iOS SwiftUI view.
I have an edit screen for themes for a small game with a NavigationView with a list of game themes. When in edit mode and I select one of these themes, I open up an editor view, passing the theme as a binding to the editor view struct.
In my editor view I then have sections that allow the user to edit properties of the theme. I do not want to use bindings to the various theme properties in my edit fields because I do not want the changes to take effect immediately. Instead, I have created #State vars for each of these properties and then use bindings to these in the edit fields. That way, I give the user the option to either cancel without and changes taking effect, or select "Done" to assign the changes back to the theme via the binding.
In order to initialise the #State vars I have an onAppear block that assign the #State vars values from the respective theme properties.
The issue I am having is that when the onAppear block is executed and the vars are assigned, the relevant edit fields are not updating!
Here is a cut-down version of my code:
struct EditorView: View {
/// The current presentation mode of the view.
#Environment(\.presentationMode) var presentationMode
#Binding var theme: GameTheme
#State private var name = ""
...
var body: some View {
NavigationView {
Form {
nameSection
...
}
.navigationTitle("Edit \(theme.name)")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel", action: cancel)
}
ToolbarItem(placement: .confirmationAction) {
Button("Done", action: saveTheme)
.disabled(!canSaveTheme)
}
}
.onAppear {
name = theme.name
...
}
}
.frame(minWidth: Constants.minViewSize.width, minHeight: Constants.minViewSize.height)
}
var nameSection: some View {
Section(header: Text("Name")) {
TextField(LocalizedStringKey("Name"), text: $name)
}
}
...
}
So the view gets shown an on appearing, the #State var name does correctly get assigned the value from theme.name; however, this allocation does not cause an update of the view and the value of "name" is not entered into the TextField.
Interestingly, and I do not know if this is a good thing to do, if I wrap the contents of the onAppear block in a DispatchQueue.main.async, everything works fine!
i.e.
.onAppear {
DispatchQueue.main.async {
name = theme.name
...
}
}
Does anyone have any idea as to how, within the onAppear, I can force a view refresh? Or, why the assignment to "name" does not force an update?
Thanks.
This isn't the answer per se, but I went ahead and created a new iOS project with the following code (based on your post, but I cleaned it up a bit and came up with the missing GameTheme object myself).
It's more or less the same, and shows that your posted structure does re-render.
I'm wondering if there's more to the code we can't see in your post that could be causing this.
Are you possibly setting the name state variable anywhere else in a way that could be overriding the value on load?
import SwiftUI
#main
struct TestIOSApp: App {
#State var gameTheme: GameTheme = GameTheme(name: "A game theme")
var body: some Scene {
WindowGroup {
ContentView(theme: $gameTheme)
}
}
}
struct GameTheme {
var name:String;
}
struct ContentView: View {
#Binding var theme:GameTheme;
/// The current presentation mode of the view.
#Environment(\.presentationMode) var presentationMode
#State private var name = "DEFAULT SHOULD NOT BE DISPLAYED"
var body: some View {
NavigationView {
Form {
nameSection
}
.navigationTitle("Edit \(theme.name)")
.onAppear {
name = theme.name
}
}
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel", action: {})
}
ToolbarItem(placement: .confirmationAction) {
Button("Done", action: {})
}
}
.frame(maxWidth:.infinity, maxHeight: .infinity)
}
var nameSection: some View {
Section(header: Text("Name")) {
TextField(LocalizedStringKey("Name"), text: $name)
}
}
}
I seem to have solved my problem with an init(). I created init(theme: Binding<GameTheme>) and then within the init assigned the theme via _theme = theme and then assigned the name via _name = State(initialValue: theme.name.wrappedValue).

TextField with animation crashes app and looses focus

Minimal reproducible example:
In SceneDelegate.swift:
let contentView = Container()
In ContentView.swift:
struct SwiftUIView: View {
#State var text: String = ""
var body: some View {
VStack {
CustomTextFieldView(text: $text)
}
}
}
struct Container: View {
#State var bool: Bool = false
#State var text: String = ""
var body: some View {
Button(action: {
self.bool.toggle()
}) {
Text("Sheet!")
}
.sheet(isPresented: $bool) {
SwiftUIView()
}
}
}
In CustomTextField.swift:
struct CustomTextFieldView: View {
#Binding var text: String
#State var editing: Bool = false
var body: some View {
Group {
if self.editing {
textField
.background(Color.red)
} else {
ZStack(alignment: .leading) {
textField
.background(Color.green)
Text("Placeholder")
}
}
}
.onTapGesture {
withAnimation {
self.editing = true
}
}
}
var textField: some View {
TextField("", text: $text)
}
Problem:
After running the above code and focusing the text field, the app crashes. Some things I noticed:
If I remove the withAnimation code, or the ZStack in CustomTextField file, the app doesn't crash, but the TextField looses focus.
If I remove the VStack in SwiftUIView, the app doesn't crash, but the TextField looses focus.
If I use a NavigationLink or present the TextField without a sheet, the app doesn't crash, but the TextField looses focus.
Questions:
Is this a problem in the current version of SwiftUI?
Is there a solution to this problem using SwiftUI? I want to stay out of
ViewRepresentables as much as possible.
How can I keep the focus of the TextField after the body is recalculated because of a change in state?
How can I keep the focus of the TextField after the body is
recalculated because of a change in state?
You have two of them. Two different TextField could not be in editing state at the same time.
The approach suggested by Asperi is the only possible.
The reason, why your code crash is not easy explain, but expected in current SwiftUI.
You have to understand, that Group is not a standard container, it just like a "block" on which you can apply some modifiers. Removing Group and using wraping body in ViewBuilder
struct CustomTextFieldView: View {
#Binding var text: String
#State var editing: Bool = false
#ViewBuilder
var body: some View {
if self.editing {
TextField("", text: $text)
.background(Color.red)
.onTapGesture {
self.editing.toggle()
}
} else {
ZStack(alignment: .leading) {
TextField("", text: $text)
.background(Color.green)
.onTapGesture {
self.editing.toggle()
}
}
}
}
}
the code will stop to crash, but there is other issue, the keyboard will dismiss immediately. That is due the tap gesture applied.
So, believe or not, you have to use ONE TextField ONLY.
struct CustomTextFieldView: View {
#Binding var text: String
#State var editing = false
var textField: some View {
TextField("", text: $text, onEditingChanged: { edit in
self.editing = edit
})
}
var body: some View {
textField.background(editing ? Color.green : Color.red)
}
}
Use this custom text field elsewhere in your code, as you want
Try the following for CustomTextFieldView (tested & works with Xcode 11.2 / iOS 13.2)
struct CustomTextFieldView: View {
#Binding var text: String
#State var editing: Bool = false
var body: some View {
TextField("", text: $text)
.background(self.editing ? Color.red : Color.green)
.onTapGesture {
withAnimation {
self.editing = true
}
}
}
}
How can I keep the focus of the TextField after the body is recalculated because of a change in state?
You don't loose focus, you just remove entire text field, so the solution is not replace text field, but modify its property, ie background. It's ok to put it into ZStack, but keep it one.

How to pass a fieldtext value from view another view swiftui

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()
}
}