SwiftUI textfield proxy binding code to ignore spaces not working? - ios

I am trying to make my SwiftUI Textfield input ignore spaces every time the spacebar button is pressed so that account input data does not contain any spaces.
I saw the code below for achieving this with "proxy binding" but the answer is so concise for me and I am new to { get set }.
Ignore left whitespaces on imput in TextField SwiftUI Combine
I want the code in AccountInput to return if the new input is a space so that it does not go into the textfield & loginViewModel.input.
How can I make this code work?
MAIN VIEW
struct LoginView: View {
#StateObject var loginViewModel = LoginViewModel()
var body: some View {
VStack {
AccountInput(placeholder: "", input: $loginViewModel.input)
}
}
}
ACCOUNT INPUT
struct AccountInput: View {
var placeholder: String
#Binding var input: String
var body: some View {
HStack {
TextField(placeholder, text: Binding(
get: { self.input },
set: {
var newValue = $0
if newValue == " " { // How can I make new values return if a space?
return
}
self.input = newValue
}))
}
}
}

No, we should not return, there should be assignment, because it generates feedback.
It can be like
TextField(placeholder, text: Binding(
get: { self.input },
set: {
var newValue = $0
newValue.removeAll { $0 == " " } // << here !!
self.input = newValue
}))

Related

SwiftUI - TextField - Clear value of type Double on focus, restore on deselect

Scenario:
In SwiftUI, my TextField has a Double value. On tapping/focusing the TextField the current Double value is cleared, to allow the user to easily enter a new value without having to select and delete the current value. If the user does not enter a new value, the old value is restored on deselect.
Issue
Utilising .focused() and .onChange(of:) this does not appear to be possible. Below code correctly clears the value (visible in console). However, the change is not reflected within the UI. Forcing the UI to update by utilising .id() on the TextField deselects the TextField, making this workaround useless.
Why does the UI not update from the change to the TextField value property?
struct ContentView: View {
#FocusState var focus: Bool
#State var test: Double? = 100
#State var id: UUID = UUID()
var body: some View {
VStack {
TextField("Placeholder", value: $test, format: .number)
.id(id)
.focused($focus)
.onChange(of: focus, perform: { editing in
var oldValue: Double? = test
if editing {
self.$test.wrappedValue = nil
self.$id.wrappedValue = UUID()
} else {
if test == nil {
self.$test.wrappedValue = oldValue
self.$id.wrappedValue = UUID()
}
}
})
}
.padding()
}
}
Is there a way to do this with SwiftUI's TextField?
Why UI not update from the change to the TextField value ?
Because it doesn't bind TextField's value (String) to your value (Double) directly.
To clear value on focus, restore on deselect, you need to do it on TextField with text, not value. Here's an example:
struct TestView: View {
#FocusState var focus: Bool
#State var value: String = "100"
#State private var oldValue: String = ""
var body: some View {
VStack {
TextField("", text: .constant("other"))
TextField("Placeholder", text: $value)
.focused($focus)
.onChange(of: focus) { editing in
if editing {
if !value.isEmpty {
oldValue = value
value = ""
}
} else {
if value.isEmpty {
value = oldValue
}
}
}
}
.textFieldStyle(.roundedBorder)
.padding()
}
}
If you want to make it Double only, handle it outside (in ViewModel or something)
Extending on #meomeomeo' answer, please find a reusable TextField view below that satisfies the need for the TextField text to be of type Double.
The second .onChange(of:) updates the input binding as the user is typing but only if the typed text is of type Double. This is in case the user never deselects the TextField before e.g. saving.
struct TextFieldDoubleClearView: View {
#FocusState var focus: Bool
#Binding var input: Double
#State var value: String = "100"
#State private var oldValue: String = ""
var body: some View {
VStack {
TextField("Placeholder", text: $value)
.keyboardType(.decimalPad)
.focused($focus)
.onChange(of: focus, perform: { editing in
if editing {
if !value.isEmpty {
oldValue = value
value = ""
}
} else {
if value.isEmpty {
value = oldValue
} else {
if let valueDouble: Double = Double(value) {
input = valueDouble
} else {
value = oldValue
}
}
}
})
.onChange(of: value, perform: { _ in
if let valueDouble: Double = Double(value) {
input = valueDouble
}
})
}
.padding()
.onAppear {
value = String(input)
}
}
}

Laggy typing and cursor jumps with TextField?

My app uses TextFields everywhere to modify CoreData entities' String attributes. They work very poorly - typing a space or getting an auto correct event seems to make the cursor jump to the end of the window. Keystrokes are missed and the whole experience is laggy. TextEditors, on the other hand, work fine. The behavior doesn't appear on the simulator, only on (multiple) real devices.
What am I doing wrong here? Am I using TextFields wrong?
Code is below, it's basically the starter Xcode app with a "text: String?" attribute added to the "item" CoreData entity.
struct Detail: View {
#ObservedObject var item: Item
var body: some View {
VStack {
Form {
Section(content: {
TextField("Title", text: $item.text ?? "")
}, header: {
Text("TextField")
})
Section(content: {
TextEditor(text: $item.text ?? "")
}, header: {
Text("TextEditor")
})
}
}
}
}
// Optional binding used
func ??<T>(lhs: Binding<Optional<T>>, rhs: T) -> Binding<T> {
Binding(
get: { lhs.wrappedValue ?? rhs },
set: { lhs.wrappedValue = $0 }
)
}
Update:
I ended up just putting the TextFields into a subview and then writing their value back to the NSManagedObject via a binding every time the value changes.
I have no idea why, but this fixes the problem for me.
struct CustomTextField: View {
#Binding var string: String?
#State var localString: String
let prompt: String
init(string: Binding<String?>, prompt: String) {
_string = string
_localString = State(initialValue: string.wrappedValue ?? "")
self.prompt = prompt
}
var body: some View {
TextField(prompt, text: $localString, axis: .vertical)
.onChange(of: localString, perform: { _ in
string = localString
})
}
}
Example of using onSubmit, which does not cause CoreData to save the data on every input by the keyboard.
struct Detail: View {
#ObservedObject var item: Item
#State var text: String = "" // for starting with an empty textfield
// Alternative if you want the data from item:
// #State var text: String = item.text.wrappedValue // This only works if text is a binding.
var body: some View {
VStack {
Form {
Section(content: {
TextField("Title", text: $text)
.onSubmit {
item.text = self.text
}
}, header: {
Text("TextField")
})
Section(content: {
TextEditor(text: $text)
.onSubmit {
item.text = self.text
}
}, header: {
Text("TextEditor")
})
}
}
}
}
If that does not help, it would be nice to know how Item looks like.

Search bar with auto-text suggestion in text suggestion in swiftUI

I have created a form in SwiftUI, now I am trying to add a search bar and make the List searchable without system built NavigationView. I was wondering is there any possible way to do so?
One easy way is you can customise TextField in SwiftUI, via text field text to filter data.
struct Sample006: View {
#State var dataSource: [String] = ["1", "2", "3"]
#State var filterData: [String] = []
#State var filterText: String = ""
var body: some View {
VStack {
TextField("Input Text", text: self.$filterText)
.textFieldStyle(.roundedBorder)
.padding(.horizontal, 20)
Form {
ForEach(filterData, id: \.self) { data in
Text(data)
}
}
}
.onAppear {
reset()
}
.onChange(of: filterText) { newValue in
guard !newValue.isEmpty else {
reset()
return
}
filterData = dataSource.filter { text in
text == newValue
}
}
}
private func reset() {
filterData = dataSource
}
}

Is it possible to make SwiftUI TextField ignore space-bar / space entry?

Is it possible to make a SwiftUI Textfield simply ignore space-bar key-strokes?
I want to make it so that when a user enters their sign-up / login details that the space bar is ignored since that data does not need spaces.
Other solutions I found for this still allow the user to enter spaces in the textfield but then erase them on the next character entered, which is not ideal, such as this SO article:
Ignore left whitespaces on imput in TextField SwiftUI Combine
I need the Textfield to completely ignore the space-bar.
struct IgnoreSpacesTextfield: View {
#State private var email: String = ""
var body: some view {
TextField("e-mail", text: $email)
// ^ needs to ignore space-bar entry
}
}
Try this custom TextField:
import SwiftUI
struct ContentView: View {
#State private var test = ""
var body: some View {
//completely ignore space
CustomTextField("enter", text: $test)
}
}
//custom textfield
struct CustomTextField: View {
let label: LocalizedStringKey
#Binding var text: String
init(_ label: LocalizedStringKey, text: Binding<String>) {
self.label = label
self._text = Binding(projectedValue: text)
}
var body: some View {
TextField(label, text: $text)
.onChange(of: text) { _ in
//try one of these
//input = input.replacing(" ", with: "")
//input = input.replacingOccurrences(of: " ", with: "")
//input = input.filter{ s in s != " "}
}
}
}

#State property keeping initial value instead of updating

From a settings page, I want to :
Navigate to a child view
Let the user input update some value in a textfield
Save this value in the user defaults
Navigate back to the settings
If the user opens the child view again, pre-fill the textfield with the previously saved value
Given the following (simple) code :
// User defaults wrapper
class SettingsProvider: ObservableObject {
static let shared = SettingsProvider()
var savedValue: String {
get { UserDefaults.standard.string(forKey: "userdefaultskey") ?? "Default value" }
set {
UserDefaults.standard.setValue(newValue, forKey: "userdefaultskey")
objectWillChange.send()
}
}
}
struct SettingsView: View {
var body: some View {
NavigationView {
NavigationLink("Open child", destination: ChildView())
}
}
}
struct ChildView: View {
#ObservedObject var settingsProvider = SettingsProvider.shared
#State var text: String = SettingsProvider.shared.savedValue
var body: some View {
Text("Value is \(settingsProvider.savedValue)")
TextField("Enter value", text: $text).background(Color.gray)
Button("Save value") {
settingsProvider.savedValue = text
}
}
}
I'm having the following behaviour : video
Can somebody explain to me why the TextField contains Default value the second time I open it ?
Is it a bug in SwiftUI that I should report, or am I missing something ?
If I kill & re-open the app, the textfield will contain (as expected) Other value.
You can just add an onAppear { text = SettingsProvider.shared.savedValue } under the Button like this:
var body: some View {
Text("Value is \(settingsProvider.savedValue)")
TextField("Enter value", text: $text).background(Color.gray)
Button("Save value") {
settingsProvider.savedValue = text
}
.onAppear {
text = SettingsProvider.shared.savedValue // <= add this
}
}

Resources