如何使用 SwiftUI
针对以下情况隐藏 keyboard
?
情况 1
我有一个TextField
,需要在用户点击“返回”按钮时隐藏 keyboard
。
情况 2
我有一个TextField
,需要在用户点击外部区域时隐藏 keyboard
。
我可以通过使用 SwiftUI
来实现这个功能吗?
注意:
我没有问关于 UITextField
的问题。 我想通过使用 SwifUI.TextField
来完成此操作。
如何使用 SwiftUI
针对以下情况隐藏 keyboard
?
情况 1
我有一个TextField
,需要在用户点击“返回”按钮时隐藏 keyboard
。
情况 2
我有一个TextField
,需要在用户点击外部区域时隐藏 keyboard
。
我可以通过使用 SwiftUI
来实现这个功能吗?
注意:
我没有问关于 UITextField
的问题。 我想通过使用 SwifUI.TextField
来完成此操作。
iOS15中这个功能运行得非常完美。
VStack {
// Some content
}
.onTapGesture {
// Hide Keyboard
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
.gesture(
DragGesture(minimumDistance: 0, coordinateSpace: .local).onEnded({ gesture in
// Hide keyboard on swipe down
if gesture.translation.height > 0 {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}))
您的TextField上不需要任何其他操作,下拉和点击都可以隐藏它。我使用的方式是,在我的主NavigationView
上添加此代码,然后它下面的所有内容都将起作用。唯一的例外是,任何Sheet
都需要添加这个代码,因为它作用于不同的状态。
.onTapGesture
是不同的。 - Joe Scotto将此修饰符添加到您希望检测用户轻拍的视图中
.onTapGesture {
let keyWindow = UIApplication.shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.map({$0 as? UIWindowScene})
.compactMap({$0})
.first?.windows
.filter({$0.isKeyWindow}).first
keyWindow!.endEditing(true)
}
我更喜欢使用.onLongPressGesture(minimumDuration: 0)
,这样当另一个TextView
被激活时不会导致键盘闪烁(在.onTapGesture
中的副作用)。隐藏键盘的代码可以是可重用的函数。
.onTapGesture(count: 2){} // UI is unresponsive without this line. Why?
.onLongPressGesture(minimumDuration: 0, maximumDistance: 0, pressing: nil, perform: hide_keyboard)
func hide_keyboard()
{
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
由于keyWindow
已经被弃用。
extension View {
func endEditing(_ force: Bool) {
UIApplication.shared.windows.forEach { $0.endEditing(force)}
}
}
force
参数未被使用。应该是 { $0.endEditing(force)}
。 - Davide更新了答案,使用 Swift 5.7 版本:
extension UIApplication {
func dismissKeyboard() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
然后在需要的地方使用它,例如作为按钮动作:
Button(action: {
// do stuff
UIApplication.shared.dismissKeyboard()
}, label: { Text("MyButton") })
扩展josefdolezal上面的答案,您可以像下面这样在用户点击文本框之外的任何地方时隐藏键盘:
struct SwiftUIView: View {
@State private var textFieldId: String = UUID().uuidString // To hidekeyboard when tapped outside textFields
@State var fieldValue = ""
var body: some View {
VStack {
TextField("placeholder", text: $fieldValue)
.id(textFieldId)
.onTapGesture {} // So that outer tap gesture has no effect on field
// any more views
}
.onTapGesture { // whenever tapped within VStack
textFieldId = UUID().uuidString
//^ this will remake the textfields hence loosing keyboard focus!
}
}
}
Return
键除了关于在文本字段之外轻敲的所有答案之外,您可能还想在用户点击键盘上的返回键时关闭键盘:
定义这个全局函数:
func resignFirstResponder() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
并在 onCommit
参数中添加 use it:
TextField("title", text: $text, onCommit: {
resignFirstResponder()
})
TextEditor
中,回车键实际上是将新行添加到编辑区域中,覆盖这个功能并不是一个好的做法。你可以使用竖直的 TextField
代替。 - Mojtaba Hosseini我发现一些非常好用的东西
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
然后将其添加到视图结构中:
private func endEditing() {
UIApplication.shared.endEditing()
}
那么
struct YourView: View {
var body: some View {
ParentView {
//...
}.contentShape(Rectangle()) //<---- This is key!
.onTapGesture {endEditing()}
}
}
扩展@Feldur的回答(基于@RyanTCB的答案),这里是一个更加表达和强大的解决方案,允许您在除onTapGesture
之外的其他手势上消除键盘,您可以在函数调用中指定想要的手势。
// MARK: - View
extension RestoreAccountInputMnemonicScreen: View {
var body: some View {
List(viewModel.inputWords) { inputMnemonicWord in
InputMnemonicCell(mnemonicInput: inputMnemonicWord)
}
.dismissKeyboard(on: [.tap, .drag])
}
}
或者使用All.gestures
(只是Gestures.allCases
的简化形式)
.dismissKeyboard(on: All.gestures)
enum All {
static let gestures = all(of: Gestures.self)
private static func all<CI>(of _: CI.Type) -> CI.AllCases where CI: CaseIterable {
return CI.allCases
}
}
enum Gestures: Hashable, CaseIterable {
case tap, longPress, drag, magnification, rotation
}
protocol ValueGesture: Gesture where Value: Equatable {
func onChanged(_ action: @escaping (Value) -> Void) -> _ChangedGesture<Self>
}
extension LongPressGesture: ValueGesture {}
extension DragGesture: ValueGesture {}
extension MagnificationGesture: ValueGesture {}
extension RotationGesture: ValueGesture {}
extension Gestures {
@discardableResult
func apply<V>(to view: V, perform voidAction: @escaping () -> Void) -> AnyView where V: View {
func highPrio<G>(
gesture: G
) -> AnyView where G: ValueGesture {
view.highPriorityGesture(
gesture.onChanged { value in
_ = value
voidAction()
}
).eraseToAny()
}
switch self {
case .tap:
// not `highPriorityGesture` since tapping is a common gesture, e.g. wanna allow users
// to easily tap on a TextField in another cell in the case of a list of TextFields / Form
return view.gesture(TapGesture().onEnded(voidAction)).eraseToAny()
case .longPress: return highPrio(gesture: LongPressGesture())
case .drag: return highPrio(gesture: DragGesture())
case .magnification: return highPrio(gesture: MagnificationGesture())
case .rotation: return highPrio(gesture: RotationGesture())
}
}
}
struct DismissingKeyboard: ViewModifier {
var gestures: [Gestures] = Gestures.allCases
dynamic func body(content: Content) -> some View {
let action = {
let forcing = true
let keyWindow = UIApplication.shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.map({$0 as? UIWindowScene})
.compactMap({$0})
.first?.windows
.filter({$0.isKeyWindow}).first
keyWindow?.endEditing(forcing)
}
return gestures.reduce(content.eraseToAny()) { $1.apply(to: $0, perform: action) }
}
}
extension View {
dynamic func dismissKeyboard(on gestures: [Gestures] = Gestures.allCases) -> some View {
return ModifiedContent(content: self, modifier: DismissingKeyboard(gestures: gestures))
}
}
请注意,如果您使用所有手势,它们可能会相互冲突,并且我没有想出任何简洁的解决方案来解决这个问题。
eraseToAny()
的意思是什么? - Ravindra_BhatieraseToAnyView
- PeacemoonKeyboardAvoider {}
即可。KeyboardAvoider {
VStack {
TextField()
TextField()
TextField()
TextField()
}
}