以下の場合にkeyboard
を使用してSwiftUI
を非表示にする方法は?
ケース1
私にはTextField
があり、ユーザーがkeyboard
ボタンをクリックしたときにreturn
を非表示にする必要があります。
ケース2
私にはTextField
があり、ユーザーが外側をタップしたときにkeyboard
を非表示にする必要があります。
SwiftUI
を使用してこれを行うにはどうすればよいですか?
注:
私はUITextField
について質問していません。 SwifUI
(TextField
)を使用して実行します。
共有アプリケーションにアクションを送信することにより、ファーストレスポンダを強制的に辞任させることができます。
_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()
}
}
_
@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でテスト済み)
'SceneDelegate.Swift'ファイルのSwiftUIは、次のコードを追加するだけです。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()
}
}
これは、アプリでキーボードを使用する各ビューに十分です...
多くの試みの後、私は(現在のところ)コントロールをブロックしないソリューションを見つけました-UIWindow
にジェスチャー認識機能を追加します。
UITapGestureRecognizer
を使用してステップ3をコピーするだけで十分です。任意のタッチで機能するカスタムジェスチャレコグナイザクラスを作成します。
class AnyGestureRecognizer: UIGestureRecognizer {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
state = .began
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
state = .ended
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
state = .cancelled
}
}
SceneDelegate.Swift
次のコードを追加:
let tapGesture = AnyGestureRecognizer(target: window, action:#selector(UIView.endEditing))
tapGesture.requiresExclusiveTouchType = false
tapGesture.cancelsTouchesInView = false
tapGesture.delegate = self //I don't use window as delegate to minimize possible side effects
window.addGestureRecognizer(tapGesture)
UIGestureRecognizerDelegate
を実装して、同時にタッチできるようにします。
extension SceneDelegate: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
これで、任意のビューのキーボードは、タッチするか、外側にドラッグすると閉じます。
追伸特定のTextFieldのみを閉じたい場合は、TextFieldのコールバックが呼び出されたときにジェスチャー認識機能をウィンドウに追加および削除しますonEditingChanged
keyWindow
プロパティへのアクセスを必要としないキーボードを閉じる別の方法を見つけました。実際のところ、コンパイラは次を使用して警告を返します
UIApplication.shared.keyWindow?.endEditing(true)
'keyWindow'はiOS 13.0で廃止されました:複数のシーンをサポートするアプリケーションには使用しないでください。接続されているすべてのシーンでキーウィンドウが返されます。
代わりに私はこのコードを使用しました:
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
NavigationView内でTextFieldを使用しているときにこれを経験しました。これが私の解決策です。スクロールを開始すると、キーボードが非表示になります。
NavigationView {
Form {
Section {
TextField("Receipt amount", text: $receiptAmount)
.keyboardType(.decimalPad)
}
}
}
.gesture(DragGesture().onChanged{_ in UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)})
ユーザーのタップを検出するビューにこの修飾子を追加します
.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
は廃止されたためです。
extension View {
func endEditing(_ force: Bool) {
UIApplication.shared.windows.forEach { $0.endEditing(force)}
}
}
@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))
}
}
allジェスチャーを使用すると競合する可能性があり、私はそれを解決する適切な解決策を思いついていないことに注意してください。
endEditing
ソリューションが@rraphaelのように指摘された唯一のソリューションのようです。
これまでに見た中で最もクリーンな例は次のとおりです。
extension View {
func endEditing(_ force: Bool) {
UIApplication.shared.keyWindow?.endEditing(force)
}
}
そして、それをonCommit:
私は.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)
}
確認してください https://github.com/michaelhenry/KeyboardAvoider
KeyboardAvoider {}
メインビューの上に表示されます。
KeyboardAvoider {
VStack {
TextField()
TextField()
TextField()
TextField()
}
}
ユーザーが外部をタップしたときにソフトウェアキーボードを非表示にする方法。 Viewコンテナ全体を検出するには、contentShape
をonLongPressGesture
とともに使用する必要があります。 onTapGesture
へのフォーカスのブロックを回避するには、TextField
が必要です。 onTapGesture
の代わりにonLongPressGesture
を使用できますが、NavigationBarアイテムは機能しません。
extension View {
func endEditing() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
struct KeyboardAvoiderDemo: View {
@State var text = ""
var body: some View {
VStack {
TextField("Demo", text: self.$text)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.contentShape(Rectangle())
.onTapGesture {}
.onLongPressGesture(
pressing: { isPressed in if isPressed { self.endEditing() } },
perform: {})
}
}
このメソッドを使用すると、キーボードを非表示スペーサー!
最初にこの関数を追加します(クレジット提供先:Casper Zandbergen、から SwiftUIはHStackのスペーサーをタップできません )
_extension Spacer {
public func onTapGesture(count: Int = 1, perform action: @escaping () -> Void) -> some View {
ZStack {
Color.black.opacity(0.001).onTapGesture(count: count, perform: action)
self
}
}
}
_
次に、次の2つの関数を追加します(指定されたクレジット:rraphael、この質問から)
_extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
_
以下の関数がViewクラスに追加されます。詳細については、rraphaelのこちらのトップアンサーを参照してください。
_private func endEditing() {
UIApplication.shared.endEditing()
}
_
最後に、単に呼び出すことができます...
_Spacer().onTapGesture {
self.endEditing()
}
_
これにより、スペーサー領域がキーボードを閉じるようになります。大きな白い背景ビューはもう必要ありません!
このextension
の手法を、現在サポートされていないTapGesturesをサポートする必要があるすべてのコントロールに仮説的に適用し、onTapGesture
関数をself.endEditing()
と組み合わせて呼び出して、あなたが望むどんな状況でもキーボード。
@Sajjonの答えに基づいて、ここでは、キーボードをタップ、長押し、ドラッグ、拡大、回転ジェスチャーを選択に応じて閉じることができるソリューションがあります。
このソリューションはXCode 11.4で動作しています
struct MyView: View {
@State var myText = ""
var body: some View {
VStack {
DismissingKeyboardSpacer()
HStack {
TextField("My Text", text: $myText)
Button("Return", action: {})
.dismissKeyboard(on: [.longPress])
}
DismissingKeyboardSpacer()
}
}
}
struct DismissingKeyboardSpacer: View {
var body: some View {
ZStack {
Color.black.opacity(0.0001)
Spacer()
}
.dismissKeyboard(on: Gestures.allCases)
}
}
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 {
AnyView(view.highPriorityGesture(
gesture.onChanged { _ in
voidAction()
}
))
}
switch self {
case .tap:
return AnyView(view.gesture(TapGesture().onEnded(voidAction)))
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(AnyView(content)) { $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))
}
}