Как скрыть клавиатуру при использовании SwiftUI?
Как скрыть keyboard
используя SwiftUI
для следующих случаев?
Случай 1
У меня есть TextField
и мне нужно скрыть keyboard
когда пользователь нажимает кнопку return
.
Дело 2
У меня есть TextField
и мне нужно скрыть keyboard
когда пользователь нажимает снаружи.
Как я могу сделать это с помощью SwiftUI
?
Замечания:
Я не задавал вопрос относительно UITextField
. Я хочу сделать это с помощью SwifUI
(TextField
).
Ответы
Ответ 1
Вы можете заставить первого респондента уйти в отставку, отправив действие в общее приложение:
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
Теперь вы можете использовать этот метод, чтобы закрыть клавиатуру в любое время:
struct ContentView : View {
@State private var name: String = ""
var body: some View {
VStack {
Text("Hello \(name)")
TextField("Name...", text: self.$name) {
// Called when the user tap the return button
// see 'onCommit' on TextField initializer.
UIApplication.shared.endEditing()
}
}
}
}
Если вы хотите закрыть клавиатуру касанием, вы можете создать полноэкранный белый вид с действием касания, которое вызовет endEditing(_:)
:
struct Background<Content: View>: View {
private var content: Content
init(@ViewBuilder content: @escaping () -> Content) {
self.content = content()
}
var body: some View {
Color.white
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
.overlay(content)
}
}
struct ContentView : View {
@State private var name: String = ""
var body: some View {
Background {
VStack {
Text("Hello \(self.name)")
TextField("Name...", text: self.$name) {
self.endEditing()
}
}
}.onTapGesture {
self.endEditing()
}
}
private func endEditing() {
UIApplication.shared.endEditing()
}
}
Ответ 2
SwiftUI
в файле 'SceneDelegate.swift' просто добавьте: .onTapGesture {window.endEditing(true)}
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow 'window' to the provided UIWindowScene 'scene'.
// If using a storyboard, the 'window' property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see 'application:configurationForConnectingSceneSession' instead).
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(
rootView: contentView.onTapGesture { window.endEditing(true)}
)
self.window = window
window.makeKeyAndVisible()
}
}
этого достаточно для каждого просмотра с помощью клавиатуры в вашем приложении...
Ответ 3
Я нашел другой способ отклонить клавиатуру, который не требует доступа к свойству keyWindow
; по сути, компилятор возвращает предупреждение, используя
UIApplication.shared.keyWindow?.endEditing(true)
"keyWindow" устарело в iOS 13.0: его не следует использовать для приложений, которые поддерживают несколько сцен, поскольку оно возвращает ключевое окно для всех связанных сцен
Вместо этого я использовал этот код:
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
Ответ 4
Похоже, решение endEditing
- единственное, на которое указал @rraphael.
Самый чистый пример, который я видел до сих пор, таков:
extension View {
func endEditing(_ force: Bool) {
UIApplication.shared.keyWindow?.endEditing(force)
}
}
а затем использовать его в onCommit:
Ответ 5
добавьте этот модификатор в представление, которое вы хотите обнаруживать касания пользователя
.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)
}
Ответ 6
@RyanTCB ответ хороший; Вот несколько уточнений, которые упрощают использование и позволяют избежать возможного сбоя:
struct DismissingKeyboard: ViewModifier {
func body(content: Content) -> some View {
content
.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)
}
}
}
"Исправление ошибки" состоит в том, что keyWindow!.endEditing(true)
должен быть keyWindow?.endEditing(true)
(да, вы можете утверждать, что этого не может быть.)
Более интересно, как вы можете его использовать. Например, предположим, что у вас есть форма с несколькими редактируемыми полями. Просто оберните это так:
Form {
.
.
.
}
.modifier(DismissingKeyboard())
Теперь, нажав на любой элемент управления, который сам не представляет клавиатуру, произойдет соответствующее отклонение.
(Протестировано с бета 7)
Ответ 7
Потому что keyWindow
устарела.
extension View {
func endEditing(_ force: Bool) {
UIApplication.shared.windows.forEach { $0.endEditing(force)}
}
}
Ответ 8
Расширяя ответ @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))
}
}
Слово предостережения
Обращаем ваше внимание, что если вы используете все жесты, они могут конфликтовать, и я не смог придумать какого-либо изящного решения, которое бы решало эту проблему.
Ответ 9
Это очень просто, просто используйте эту установку pod, https://github.com/hackiftekhar/IQKeyboardManager
pod 'IQKeyboardManagerSwift', '6.3.0'
просто импортируйте IQKeyboardManagerSwift и добавьте один фрагмент кода в ваш AppDelegate (это решит проблему в глобальном масштабе в вашем приложении)
import IQKeyboardManagerSwift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
IQKeyboardManager.shared.enable = true
return true
}
}