how can I use the methods textFieldDidBeginEditing and textFieldDidEndEditing with the default TextField struct by apple.
TextField has onEditingChanged and onCommit callbacks.
For example:
#State var text = ""
#State var text2 = "default"
var body: some View {
VStack {
TextField($text, placeholder: nil, onEditingChanged: { (changed) in
self.text2 = "Editing Changed"
}) {
self.text2 = "Editing Commited"
}
Text(text2)
}
}
The code in onEditingChanged is only called when the user selects the textField, and onCommit is only called when return, done, etc. is tapped.
Edit: When the user changes from one TextField to another, the previously selected TextField's onEditingChanged is called once, with changed (the parameter) equaling false, and the just-selected TextField's onEditingChanged is also called, but with the parameter equaling true. The onCommit callback is not called for the previously selected TextField.
Edit 2:
Adding an example for if you want to call a function committed() when a user taps return or changes TextField, and changed() when the user taps the TextField:
#State var text = ""
var body: some View {
VStack {
TextField($text, placeholder: nil, onEditingChanged: { (changed) in
if changed {
self.changed()
} else {
self.committed()
}
}) {
self.committed()
}
}
}
SwiftUI 3 (iOS 15+)
Since TextField.init(_text:onEditingChanged:) is scheduled for deprecation in a future version it may be best to use #FocusState. This method also has the added benefit of knowing when the TextField is no longer the "first responder" which .onChange(of:) and .onSubmit(of:) alone will not do.
#State private var text = ""
#FocusState private var isTextFieldFocused: Bool
var body: some View {
TextField("Text Field", text: $text)
.focused($isTextFieldFocused)
.onChange(of: isTextFieldFocused) { isFocused in
if isFocused {
// began editing...
} else {
// ended editing...
}
}
}
SwiftUI 2
ios15 and above
The syntax has changed for swiftui2
a modifier onChange is triggered when a property value has changed and onSubmit is triggered when form is submitted ie you press enter
TextField("search", text: $searchQuery)
.onChange(of: searchQuery){ newValue in
print("textChanged")
}
.onSubmit {
print("textSubmitted")
}
Related
This simple TextField might be part of a chat feature, and I would like to be able to send chat messages when I press the keyboard button "send".
(Imagine in this chat I don't need to allow users to enter newline, by overriding the return key, to be send with the submitLabel(.send) view modifier.)
TextField(
"Chat...",
text: $draft
)
.submitLabel(.send)
.onSubmit {
if !draft.isEmpty {
sendMessage(draft: draft)
}
}
However, this will hide the keyboard, and I would like to know:
is there any way to prevent the keyboard from hiding when I press send??
I know how to refocus the field, I can do that with #FocusState but that still results in a hide keyboard animation starting which then aborts, so looks glithy.
Here is something you can work with. During .onSubmit set the focusedField back to .field, and use .transaction to capture the current transaction and set the animation to nil:
struct ContentView: View {
enum FocusField: Hashable {
case field
}
#State private var draft = "Hello, world"
#FocusState private var focusedField: FocusField?
var body: some View {
TextField(
"Chat...",
text: $draft
)
.submitLabel(.send)
.focused($focusedField, equals: .field)
.onSubmit {
focusedField = .field
if !draft.isEmpty {
sendMessage(draft: draft)
}
}
.transaction { t in
t.animation = nil
}
}
func sendMessage(draft: String) {
print("The message: \(draft)")
}
}
It is possible to prevent keyboard hiding using a somewhat "hacky" solution, combing re-focus of field together with disable animation.
struct ChatView {
enum Field: String, Hashable {
case chat
}
#State var draft = ""
#FocusState var focusedField: Field?
var body: some View {
VStack {
// messages view omitted
TextField(
"Chat...",
text: $draft
)
.submitLabel(.send)
.onSubmit {
if !draft.isEmpty {
sendMessage()
// We just lost focus because "return" key was pressed
// we re-fucus
focusedField = .chat
}
}
Button("Send") {
sendMessage()
}
}
// Prevent hacky keyboard hide animation
.transaction { $0.animation = nil }
}
func sendMessage() {
// impl omitted, sending message `draft` using some dependency.
}
}
on ios16 the keyboard bounces back everytime when you click on send.
My problem:
I want to call a function when my customView clicked
So I use #State variable to observe the click action from my custom view. But the problem is my value changed but didSet function not triggered
My code:
Main struct:
#State var buttonClicked : Bool = false {
didSet{
needToCallMyFunction() // not triggered
}
}
Custom struct:(for my customview)
#Binding var isClicked : Bool
someview
.onTapGesture(perform: {
print("custom clicked")
isClicked.toggle()
})
Use instead in main view .onChange(of:) modifier, like
SomeView()
.onChange(of: buttonClicked) { _ in
self.needToCallMyFunction()
}
Update: variant for SwiftUI 1.0 / iOS 13+
import Combine // needed to use Just
...
SomeView()
.onReceive(Just(buttonClicked)) { _ in
self.needToCallMyFunction()
}
Asperi solution working fine.
But it have one bug in lower versions. It called multiple times
I found another solution
SomeView(buttonClicked:
Binding(get: { self.buttonClicked },
set: { self.buttonClicked = $0
print("your action here ")
}))
In my SwiftUI app, I would like to be notified when the TextEditor loses focus/has finished editing. Ideally, something like TextField's onCommit callback would be perfect.
Using the new onChange as below does work for receiving every new character that the user types, but I really want to know when they are finished.
#State var notes = ""
...
TextEditor(text: $notes)
.onChange(of: notes, perform: { newString in
print(newString)
})
I figured it out, you can use the UIResponder.keyboardWillHideNotification.
TextEditor(text: $notes)
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in
print("done editing!")
}
On iOS 15.0, there is now #FocusState.
struct MyView: View {
#State var text: String
#FocusState private var isFocused: Bool
var body: some View {
Form {
TextEditor(text: $text)
.focused($isFocused)
.onChange(of: isFocused) { isFocused in
// do stuff based off focus state change
}
}
}
}
I am trying a simple app that is a List with items, they lead to detail view. I also have a search bar that opens keyboard, and I need to hide the keyboard when the user taps anywhere outside of the keyboard.
#State private var keyboardOpen: Bool = false
var body: some View {
NavigationView {
Form {
Section {
TextField("Search", text: $cityStore.searchTerm, onCommit: debouncedFetch)
.keyboardType(.namePhonePad)
.disableAutocorrection(true)
.onTapGesture { self.keyboardOpen = true }
.onDisappear { self.keyboardOpen = false }
}
Section {
List {
ForEach(cities) { city in
NavigationLink(
destination: DetailView(city: city)) {
VStack(alignment: .leading) {
Text("\(city.name)")
}
}
}
}
}
}
.navigationBarTitle("City list")
.onTapGesture {
if self.keyboardOpen {
UIApplication.shared.endEditing()
self.keyboardOpen = false
}
}
}
}
Do you know if it's possible to keep both gesture tap and follow to detail view?
Actually it should work, but it is not due to bug of .all GestureMask. I submitted feedback to Apple #FB7672055, and recommend to do the same for everybody affected, the more the better.
Meanwhile, here is possible alternate approach/workaround to achieve similar effect.
Tested with Xcode 11.4 / iOS 13.4
extension UIApplication { // just helper extension
static func endEditing() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
}
}
struct TestEndEditingOnNavigate: View {
#State private var cities = ["London", "Berlin", "New York"]
#State private var searchTerm = ""
#State private var tappedLink: String? = nil // track tapped link
var body: some View {
NavigationView {
Form {
Section {
TextField("Search", text: $searchTerm)
}
Section {
List {
ForEach(cities, id: \.self) { city in
self.link(for: city) // decompose for simplicity
}
}
}
}
.navigationBarTitle("City list")
}
}
private func link(for city: String) -> some View {
let selection = Binding(get: { self.tappedLink }, // proxy bindng to inject...
set: {
UIApplication.endEditing() // ... side effect on willSet
self.tappedLink = $0
})
return NavigationLink(destination: Text("city: \(city)"), tag: city, selection: selection) {
Text("\(city)")
}
}
}
I think you could easily handle this scenario with boolean flags, when your keyboard opens you can set a flag as true and when it dismisses a the flag goes back to false, so in that case when the keyboard is open and the tap gesture is triggered you can check if the keyboard flag is active and not go to detail but instead effectively dismiss the keyboard and viceversa. Let me know if maybe I misunderstood you.
I have a TextField in SwiftUI that needs to use a different keyboard depending on the value of a #State variable determined by a SegementedControl() picker.
How can I dismiss the keyboard (like send an endEditing event) when the user taps a different segment? I need to do this because I want to change the keyboard type and if the textField is the responder, the keyboard won't change.
I have this extension:
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
And I can do something like
UIApplication.shared.endEditing()
But I don't know where or how to call this when the user taps a different segment.
I have tried putting a tapGesture on the Picker and the keyboard does dismiss, but the tap does not pass through to the picker so it does not change.
Code snippet here:
#State private var type:String = "name"
.
.
.
Form {
Section(header: Text("Search Type")) {
Picker("", selection: $type) {
Text("By Name").tag("name")
Text("By AppId").tag("id")
}.pickerStyle(SegmentedPickerStyle())
}
Section(header: Text("Enter search value")) {
TextField(self.searchPlaceHolder, text: $searchValue)
.keyboardType(self.type == "name" ? UIKeyboardType.alphabet : UIKeyboardType.numberPad)
}
}
Attach a custom Binding to the Picker that calls endEditing() whenever it is set:
Section(header: Text("Search Type")) {
Picker("", selection: Binding(get: {
self.type
}, set: { (res) in
self.type = res
UIApplication.shared.endEditing()
})) {
Text("By Name").tag("name")
Text("By AppId").tag("id")
}.pickerStyle(SegmentedPickerStyle())
}
Update since iOS 13 / iPadOS 13 was released.
Since there is now support for multiple windows in one app you need to loop through the UIWindows and end editing one-by-one.
UIApplication.shared.windows.forEach { $0.endEditing(false) }
SwiftUI 2.0
Now it can be done in more elegant way, with .onChange (actually it can be attached to any view, but at TextField looks appropriate, by intention)
TextField("Placeholder", text: $searchValue)
.keyboardType(self.type == "name" ? UIKeyboardType.alphabet : UIKeyboardType.numberPad)
.onChange(of: type) { _ in
UIApplication.shared.endEditing() // << here !!
}
SwiftUI 1.0+
There are much similar to above approaches
a) requires import Combine...
TextField("Placeholder", text: $searchValue)
.keyboardType(self.type == "name" ? UIKeyboardType.alphabet : UIKeyboardType.numberPad)
.onChange(of: type) { _ in
UIApplication.shared.endEditing()
}
.onReceive(Just(type)) { _ in
UIApplication.shared.endEditing() // << here !!
}
b) ... and not
TextField("Placeholder", text: $searchValue)
.keyboardType(self.type == "name" ? UIKeyboardType.alphabet : UIKeyboardType.numberPad)
.onReceive([type].publisher) { _ in
UIApplication.shared.endEditing() // << here !!
}
For SwiftUI 3 use FocusState and .focused(…)