Did you ever try to handle the event when you press intro and the keyboard dismiss in SwiftUI using TextField?
TextField("type your name here", text: $yourName)
How can I know when user dismiss keyboard pressing 'intro' button?
Thank you so much!
You can use onCommit parameter from TextField. Here is an example:
TextField("type your name here", text: $yourName, onCommit: {
print("return key pressed")
})
From the documentation:
/// [...]
/// - onCommit: An action to perform when the user performs an action
/// (for example, when the user presses the Return key) while the text
/// field has focus.
public init(_ titleKey: LocalizedStringKey, text: Binding<String>, onEditingChanged: #escaping (Bool) -> Void = { _ in }, onCommit: #escaping () -> Void = {})
After the TextField is declared, add a closure and fill it with this code: UIApplication.shared.keyWindow?.endEditing(true)
So altogether it looks like
TextField($yourText){
UIApplication.shared.keyWindow?.endEditing(true)
}
It is explained in this video around the 8:00 mark.
https://www.youtube.com/watch?v=TfJ70vHABjs
Related
What is wrong with this SwiftUI code?
When the button "Reset Text" is pressed the document data is changed (as seen in the debugger), but the view is not updated.
If the document is then saved, the modified data is not saved.
The method "func fileWrapper" is not called because it does not think that the field "text" has changed.
If characters are typed in the TextEditor so the field is dirty and then saved, the data is saved.
If characters are types in the TextEditor so the field is dirty, then the "Reset Text" button pressed, the TextEditor view is not updated, but the "Reset Text" is saved.
If I wait a while 1 to 3 minutes, the TextEditor will sometimes update to the new text.
import SwiftUI
import UniformTypeIdentifiers
#main
struct DocumentAppApp: App {
var body: some Scene {
DocumentGroup(newDocument: DocumentAppDocument()) { file in
ContentView(document: file.$document)
}
}
}
extension UTType {
static var exampleText: UTType {
UTType(importedAs: "com.example.plain-text")
}
}
class DocumentAppDocument: ObservableObject, FileDocument {
#Published var text: String
init(text: String = "This is some text") {
self.text = text
}
static var readableContentTypes: [UTType] { [.exampleText] }
required init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let string = String(data: data, encoding: .utf8)
else {
throw CocoaError(.fileReadCorruptFile)
}
text = string
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let data = text.data(using: .utf8)!
return .init(regularFileWithContents: data)
}
}
struct ContentView: View {
#Binding var document: DocumentAppDocument
var body: some View {
VStack {
TextEditor(text: $document.text)
Button(action: {
// document.objectWillChange.send()
document.text = "Reset Text"
}, label: { Text("Reset Text") })
.frame(width: 200, height: 50)
.border(Color.black)
.padding()
}
}
}
I tried added a call to document.objectWillChange.send() and it made no difference.
I tried as both a struct and a class
I've tried various combinations of #State, #StateObject and #Binding.
It looks like I could call .save on the DocumentGroup, but I couldn't figure out how to get ahold of it.
Test case
Create a new document.
Type in some text -> test is there
Save document.
Open document -> text is there.
Press "Reset Text" Button. Nothing happens.
Save.
Open -> previous text is there.
Type some text, press button -> nothing happens.
Save.
Open -> "Reset Text: text is present.
I found the problem.
class DocumentAppDocument: ObservableObject, FileDocument {
Should not be a class and ObservableObject.
The Binding of the class and DocumentAppDocument and the #Published don't work with each other.
A workaround I used when I had a similar issue was to add a Boolean property to the document struct that stores whether the document has been edited. Changing the value from false to true should mark the document as changed. In your case you would set the property to true when clicking the Reset Text button.
I know there are answers for dismissing keyboard, but mostly they are triggered on tapped outside of the keyboard.
As I stated in the question, how to achieve dismissing keyboard on swipe (to bottom).
UIScrollView has keyboardDismissMode which when set to interactive, will achieve what you want. SwiftUI doesn’t provide direct support for this, but since under the hood, SwiftUI is using UIScrollView, you can use this which sets keyboardDismissMode to interactive for all scroll views in your app.
UIScrollView.appearance().keyboardDismissMode = .interactive
You must have a ScrollView in your view hierarchy for this to work. Here’s is a simple view demonstrating the behavior:
struct ContentView: View {
#State private var text = "Hello, world!"
var body: some View {
ScrollView {
TextField("Hello", text: $text)
.padding()
}
.onAppear {
UIScrollView.appearance().keyboardDismissMode = .interactive
}
}
}
The only caveat is that this affects all scroll views in your app. I don’t know of a simple solution if you only want to affect one scroll view in your app.
For example if you have a list of messages then you can :
List {
ForEach(...) { ...
}
}.resignKeyboardOnDragGesture()
extension View {
func resignKeyboardOnDragGesture() -> some View {
return modifier(ResignKeyboardOnDragGesture())
}
}
struct ResignKeyboardOnDragGesture: ViewModifier {
var gesture = DragGesture().onChanged { _ in
UIApplication.shared.endEditing(true)
}
func body(content: Content) -> some View {
content.gesture(gesture)
}
}
By the way it came from here : https://stackoverflow.com/a/58564739/7974174
You can add
.simultaneousGesture(
// Hide the keyboard on scroll
DragGesture().onChanged { _ in
UIApplication.shared.sendAction(
#selector(UIResponder.resignFirstResponder),
to: nil,
from: nil,
for: nil
)
}
)
to the view.
I have a SwiftUI Form that contains a Picker, a TextField, and a Text:
struct ContentView: View {
var body: some View {
Form {
Section {
Picker(selection: $selection, label: label) {
// Code to populate picker
}.pickerStyle(SegmentedPickerStyle())
HStack {
TextField(title, text: $text)
Text(text)
}
}
}
}
}
The code above results in the following UI:
I am able to easily select the second item in the picker, as shown below:
Below, you can see that I am able to initiate text entry by tapping on the TextField:
In order to dismiss the keyboard when the Picker value is updated, a Binding was added, which can be seen in the following code block:
Picker(selection: Binding(get: {
// Code to get selected segment
}, set: { (index) in
// Code to set selected segment
self.endEditing()
}), label: label) {
// Code to populate picker
}
The call to self.endEditing() is provided in the following method:
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
The following screenshot displays that selecting a different segment of the Picker dismisses the keyboard:
Up to this point, everything works as expected. However, I would like to dismiss the keyboard when tapping anywhere outside of the TextField since I am unable to figure out how to dismiss the keyboard when dragging the Form's containing scroll view.
I attempted to add the following implementation to dismiss the keyboard when tapping on the Form:
Form {
Section {
// Picker
HStack {
// TextField
// Text
}
}
}.onTapGesture {
self.endEditing()
}
Below, the following two screenshot displays that the TextField is able to become the first responder and display the keyboard. The keyboard is then successfully dismissed when tapping outside of the TextField:
However, the keyboard will not dismiss when attempting to select a different segment of the `Picker. In fact, I cannot select a different segment, even after the keyboard has been dismissed. I presume that a different segment cannot be selected because the tap gesture attached to the form is preventing the selection.
The following screenshot shows the result of attempting to select the second value in the Picker while the keyboard is shown and the tap gesture is implemented:
What can I do to allow selections of the Picker's segments while allowing the keyboard to be dismissed when tapping outside of the TextField?
import SwiftUI
struct ContentView: View {
#State private var tipPercentage = 2
let tipPercentages = [10, 15, 20, 25, 0]
#State var text = ""
#State var isEdited = false
var body: some View {
Form {
Section {
Picker("Tip percentage", selection: $tipPercentage) {
ForEach(0 ..< tipPercentages.count) {
Text("\(self.tipPercentages[$0])%")
}
}
.pickerStyle(SegmentedPickerStyle())
HStack {
TextField("Amount", text: $text, onEditingChanged: { isEdited in
self.isEdited = isEdited
}).keyboardType(.numberPad)
}
}
}.gesture(TapGesture().onEnded({
UIApplication.shared.windows.first{$0.isKeyWindow }?.endEditing(true)
}), including: isEdited ? .all : .none)
}
}
Form's tap gesture (to finish editing by tap anywhere) is enabled only if text field isEdited == true
Once isEdited == false, your picker works as before.
You could place all of your code in an VStack{ code }, add a Spacer() to it and add the onTap to this VStack. This will allow you to dismiss the keyboard by clicking anywhere on the screen.
See code below:
import SwiftUI
struct ContentView: View {
#State private var text: String = "Test"
var body: some View {
VStack {
HStack {
TextField("Hello World", text: $text)
Spacer()
}
Spacer()
}
.background(Color.red)
.onTapGesture {
self.endEditing()
}
}
func endEditing() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
Changing the background color of an HStack or VStack to red simplifies figuring out where the user may click to dismiss.
Copy and paste code for a ready to run example.
The new SwiftUI is fantastic to play with... I'm trying to use Forms instead of Eureka. A couple of questions:
What is the best way to let the user enter a number? I used to do that with a UIPickerView, see image .
With SwiftUI I only found Textfield, as in the following code:
import SwiftUI
struct SettingsView : View {
#State var email = ""
#State var amount = ""
var body: some View {
NavigationView {
Form {
Section(header: Text("Email")) {
TextField("Your email", text: $email)
.textFieldStyle(.roundedBorder)
}
Section(header: Text("Amount")) {
TextField("Amount", text: $amount)
.textFieldStyle(.roundedBorder)
}
.navigationBarTitle("Settings")
}
}
}
}
When you click in the field, the ABC keyboard comes up. The user can select '123' to get the number keyboard. But I would like to see a number pad instead.
Also, the keyboard blocks the view (if you have more fields); the view doesn't scroll up to make room for the keyboard.
Is it possible to get rid of the keyboard when the user clicks outside a TextField?
And is there a way to 'validate the entries'? For instance, the amount should be between 10 and 1.000?
I'm working on the SwiftUI, and feeling it's very similar with React. Just now I'm customizing a Button of SwiftUI and have a problem which can't access to the children views of Button dynamically
Following codes is what I'm going to do:
struct FullButton : View {
var action: () -> Void
var body: some View {
Button(action: action) {
// render children views here even what is that
children
}
}
}
and usage:
VStack {
FullButton(action: {
print('touched')
}) {
Text("Button")
}
}
Please, do I have a wrong idea?
Update
Depends on #graycampbell 's answer I tried as following
struct FullButton<Label> where Label : View {
var action: () -> Void
var label: () -> Label
init(action: #escaping () -> Void, #ViewBuilder label: #escaping () -> Label) {
self.action = action
self.label = label
}
var body: some View {
Button(action: action, label: label)
}
}
So the FullButton looks well as itself. But I have another compile error in usage at this time.
VStack {
FullButton(action: { print("touched") }) {
Text("Fullbutton")
}
}
The error is Referencing initializer 'init(alignment:spacing:content:)' on 'VStack' requires that 'FullButton<Text>' conform to 'View'.
It means FullButton hasn't return the body now?
I'm not sure why it is because the FullButton still extends View class.
Please let me know what's the correct body definition of that type of class.
This is what you're looking for, if I'm understanding your question correctly:
struct FullButton<Label>: View where Label: View {
var action: () -> Void
var label: () -> Label
var body: some View {
Button(action: self.action, label: self.label)
}
}
This would allow you to pass whatever content you want to be displayed on your button, meaning that the code you have here would now work:
FullButton(action: {
print("touched")
}) {
Text("Button")
}
Update
After looking over your question several times, I've realized that your confusion is stemming from a misunderstanding of what is happening when you create a normal Button.
In the code below, I'm creating a Button. The button takes two arguments - action and label.
Button(action: {}, label: {
Text("Button")
})
If we look at the documentation for Button, we see that it is declared like this:
struct Button<Label> where Label : View
If we then look at the initializers, we see this:
init(action: #escaping () -> Void, #ViewBuilder label: () -> Label)
Both action and label expect closures. action expects a closure with a return type of Void, and label expects a #ViewBuilder closure with a return type of Label. As defined in the declaration for Button, Label is a generic representing a View, so really, label is expecting a closure that returns a View.
This is not unique to Button. Take HStack, for example:
struct HStack<Content> where Content : View
init(alignment: VerticalAlignment = .center, spacing: Length? = nil, #ViewBuilder content: () -> Content)
Content serves the same purpose here that Label does in Button.
Something else to note - when we create a button like this...
Button(action: {}) {
Text("Button")
}
...we're actually doing the same thing as this:
Button(action: {}, label: {
Text("Button")
})
In Swift, when the last argument in a method call is a closure, we can omit the argument label and append the closure to the outside of the closing parenthesis.
In SwiftUI, you cannot implicitly pass content to any View. The View must explicitly accept a #ViewBuilder closure in its initializer.
And so, you cannot pass a #ViewBuilder closure to FullButton unless FullButton accepts a #ViewBuilder closure as an argument in its initializer, as shown at the beginning of my answer.
There is a ViewInspector library that uses Swift's reflection for extracting SwiftUI views from any hierarchy.
let view = FullButton()
let button = try view.inspect().button()
let children = button.anyView().view(OtherView.Type)
// By the way, you can even tap the button programmatically:
try button.tap()