TextField onValueChange is Called When Focused - android-jetpack-compose

Using TextField in Compose, I found that looks like a bug.
When I change the textfield value , focus out and in again,
onValuseChange method is called even there is no change.
this is not What I want, So I write some defending code for this comparing prev value.
But I want to know another nice solution.
here is my code.
onValueChange = { _inputText ->
if(_inputText.text != countState.value){
}
},

Related

Allow ONLY Letters in Keyboard - Jetpack Compose

I'm trying to create a TextField in Jetpack Compose for users to enter their first and last name.
Is there a way to allow ONLY letters and whitespaces ([a-zA-Z\s]) as input? I've tried every single option available, including KeyboardType.Text, KeyboardType.Ascii, KeyboardType.Uri, but they all show numbers!
Am I missing something obvious? Honestly, I'm kind of shocked that Google doesn't offer this option out of the box.
Try this:
val pattern = remember { Regex("[a-zA-z\\s]*") }
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = {
if (it.matches(pattern)) {
text = it
}
}
)
In Android it's not possible to show a keyboard containing only letters (and it's not related to compose). Keyboard apps don't support it either.
There are other kinds of keyboards (like number-only keyboard), but the view of the keyboard is still controlled by the keyboard app.
It's best to filter the given input based on the needed criteria. For example:
onValueChange = {
text = it.letters()
}
private fun String.letters() = filter { it.isLetter() }

Vaadin TextField's value not available in shortcut listener

I want to read a Vaadin Flow TextField's value when the user presses the Enter key. I can add a ShortcutListener to listen for the keypress, but the value of the TextField is null in the listener even though the TextField is not empty:
TextField textField = new TextField();
Shortcuts.addShortcutListener(UI.getCurrent(), () -> {
String text = texttextField.getValue();
// text doesn't contain the latest text
}, Key.ENTER);
Why is this happening and how can I read the value that the user has entered when the keypress listener is triggered?
The behavior boils down to browser events. While the user may have entered some text into the input element, the value of the TextField is not updated to the server until there's a ValueChange event - even the vaadin-text-field element in the browser doesn't "know" of the text (the value property has not been updated) when the enter keypress event occurs. By default, the value of the text field is only updated when the input loses focus. You can work around the issue by explicitly toggling a blur of the TextField:
// member field in class
boolean shortcutFired = false;
// ...
Shortcuts.addShortcutListener(UI.getCurrent(), () -> {
textField.blur();
shortcutFired = true;
}, Key.ENTER);
and listening to the value change event instead of the shortcut listener:
textField.addValueChangeListener(e -> {
if( shortcutFired ) {
String value = e.getValue();
// do something with the value
shortcutFired = false;
}
}
This approach doesn't work too well if you need to keep track of multiple fields; in that case, you might want to make the TextFields update their values to the server more eagerly by setting their ValueChangeMode to ValueChangeMode.EAGER. The downside of this approach is that it increases the amount of traffic between the browser and the network, as every keypress will trigger a server request.

Button handling in SwiftUI

In a SwiftUI app I have a few buttons (let us say 3 as an example). One of them is highlighted.
When I tap on a non-highlighted button, the previously highlighted button toggles to non-highlighted and the tapped button becomes highlighted. If I tap the already highlighted button, nothing happens.
This scenario is simple and I have a highlighBtn variable, when a button is tapped highlighBtn takes the value of the tapped button. This variable is then used when the next tap happens to toggle off the previously highlighted button.
This cycle is OK, but the problem is when I do the first tap. For some reasons, things don't work.
This is how I handle the creation of the highlighBtn variable:
class ActiveButton: ObservableObject {
#Published var highlighBtn = Button(....)
}
#StateObject var box = ActiveButton()
Here is the relevant code, when the button is tapped:
#EnvironmentObject var box: ActiveButton
....
Button(action: {
// Toggle off the previously highlighted button.
box.highlighBtn.highLight = false
.... some useful processing ....
box.highlighBtn = self
})
One detail I should give: if I tap the highlighted button to start, then all works as it should.
I have tried various method to solve this apparently simple problem but failed.
The first method was to try to initialize the highlighBtn variable.
The second was to try to simulate a tap on the highlighted button.
I must be missing something simple.
Any tip would be welcome.
After further investigation .....
I have created a demo app to expose my problem.
Since I lack practice using GitHub, it took me some time, but it is now available here.
For that I created a SwiftUI app in Xcode.
In SceneDelegate.swift, the four lines of code right after this one have been customized for the needs of this app:
// Create the SwiftUI view that provides the window contents.
Beside that all I did resides inside the file ContentView.swift.
To save some time to anyone who is going to take a look; here is the way to get to the point (i.e. the issue I am facing).
Run the app (I did it on an iPhone 7). You will see seven buttons appear. One of them (at random) will be highlighted. Starting with the highlighted button, tap on a few buttons one after another as many times as you want. You will see how the app is supposed to work.
(After switching it off) Run the app a second time. This time you will also tap on a few buttons one after another as many times as you want; but start by tapping one of the non-highlighted button. You will see the problem appear at this point.
Here is a solution for the first part of your question: Three buttons where the last one tapped gets highlighted with a background:
import SwiftUI
struct ContentView: View {
enum HighlightTag {
case none, first, second, third
}
#State private var highlighted = HighlightTag.none
var body: some View {
VStack {
Button("First") {
highlighted = .first
}.background(
Color.accentColor.opacity(
highlighted == .first ? 0.2 : 0.0
)
)
Button("Second") {
highlighted = .second
}.background(
Color.accentColor.opacity(
highlighted == .second ? 0.2 : 0.0
)
)
Button("Third") {
highlighted = .third
}.background(
Color.accentColor.opacity(
highlighted == .third ? 0.2 : 0.0
)
)
}
}
}
Update:
After reviewing your sample code on GitHub, I tried to understand your code, I tried to make some simplifications and tried to find a working solution.
Here are some opinions:
The Attribute "#State" in front of "var butnsPool" is not needed and confusing.
The Attribute "#State" in front of "var initialHilight" is not needed and confusing.
Your ActiveButton stores a copy of the selected Button View because it is a struct which is probably the main reason for the strange behaviour.
The needInit in your ObservableObject smells bad at least. If you really need to initialize something, you may consider doing it with some .onAppear() modifier in you ContentView.
There is probably no need to use .environmentObject and #EnvironmentObject. You could consider using a parameter and #ObsservedObject
There is probably no need for the ActiveButton at all, if you only use it internally. You could consider using a #State with the selected utton name
Your BtnTxtView is fine, but consider replacing the conditional (func if) with some animatable properties, if you want to animate the transition.
Based on your code I created a much simpler and working solution.
I removed the ActiveButton class and also the BttnView struct.
And I replaced the ContentView with this:
struct ContentView: View {
var butnsPool: [String]
var initialHilight: Int
#State var selectedBox: String = ""
var body: some View {
ForEach(butnsPool, id: \.self) { buttonName in
Button(action: {
selectedBox = buttonName
})
{
BtnTxtView(theLabel: buttonName,
highLight: buttonName == selectedBox)
}
}.onAppear {
selectedBox = butnsPool[initialHilight]
}
}
}

Setting custom text if TextField is empty

I have a textField called titleTextField in which the user can enter the title of their choice before saving a note. What I want to do is automatically fill this field with the text "Untitled" if the user has left this field blank.
I've tried several solutions, but they either do nothing or set the title always to Untitled even if the user has entered text.
This is the closest I've gotten, but it's still not working right:
if(![self.titleTextField.text isEqual:#""]){
self.titleTextField.text = #"Untitled";
}
My thought was that this would check whether the titleTextField is empty, and if so, it would populate the field with the text "Untitled." I've tried applying this in the viewDidLoad, viewWillAppear, and viewWillDisappear -- I really don't care whether the "Untitled" text is populated when the screen loads or after the user is finished with the page; I just want the end result to be an empty title field saving with the text "Untitled".
Is the coding wrong, or am I just applying it in the wrong place?
Solution as per the way you are looking for is :
if([self.titleTextField.text isEqualToString:#""]){
self.titleTextField.text = #"Untitled";
}
You can also use below :
if([self.titleTextField.text length] == 0){
self.titleTextField.text = #"Untitled";
}

IOS Swift - Manually trigger textfield events

How do I trigger the events manually given the UITextField object?
textFieldShouldBeginEditing(_:)
textFieldDidBeginEditing(_:)
textFieldShouldEndEditing(_:)
textFieldDidEndEditing(_:)
In the current scenario. There is some logic which goes into the above callbacks. And there are times when I manually update a textfield
textfield.text = <new text>
And I would like the
textFieldDidBeginEditing
to be trigger, like (hopefully)
textfield.trigger("textFieldDidBeginEditing")
While my approach might not be entirely the right way, the above solution is what would most likely works for me. Although, If I can word my question differently, it would be:
How can I run some logic (code) when a UITextFields value (text) is changed via the UI or via textfield.text = <new value>
Brushing aside the situation you're trying to solve; regardless of wether this is the right approach; you can trigger UITextField events like this:
textField.sendActions(for: UIControl.Event.editingChanged)
To elaborate on my answer and my reasoning:
I don't think it is possible to trigger those kind of events "by hand" - neither should you be able to. It would just cause possibly more problems than it solves. E.g. what should happen with the caret for example, should it be displayed in the textfield?
Therefore I suggest you extract the logic into a second method and call that method from your current code and from the handler
Instead of
func textFieldDidEndEditing(textField: UITextField) {
// your logic is here
let k = 1 + 2
}
func yourCurrentCode() {
// somehow trigger the event "textFieldDidEndEditing"
}
do something like
func textFieldDidEndEditing() {
myLogic()
}
func yourCurrentCode() {
myLogic()
}
func myLogic() {
// your logic should be here
let k = 1 + 2
}
If you would like to be notified every time the value of the UITextField changes you should use the notification
UITextFieldTextDidChangeNotification

Resources