【问题标题】:SwiftUI - dismissing keyboard on tapping anywhere in the view - issues with other interactive elementsSwiftUI - 在视图中的任意位置点击时关闭键盘 - 其他交互元素的问题
【发布时间】:2020-06-06 11:50:47
【问题描述】:

我在视图中有一个TextField 和一些可操作的元素,例如ButtonPicker。当使用在TextField 之外敲击时,我想关闭键盘。使用this question 中的答案,我做到了。然而,问题出现在其他可操作的项目上。

当我点击Button 时,会发生操作,但不会关闭键盘。与Toggle 开关相同。 当我点击 SegmentedStyle Picker 的一个部分时,键盘被关闭,但选择器选择没有改变。

这是我的代码。


struct SampleView: View {

    @State var selected = 0
    @State var textFieldValue = ""

    var body: some View {
        VStack(spacing: 16) {
            TextField("Enter your name", text: $textFieldValue)
                .padding(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
                .background(Color(UIColor.secondarySystemFill))
                .cornerRadius(4)


            Picker(selection: $selected, label: Text(""), content: {
                Text("Word").tag(0)
                Text("Phrase").tag(1)
                Text("Sentence").tag(2)
            }).pickerStyle(SegmentedPickerStyle())            

            Button(action: {
                self.textFieldValue = "button tapped"
            }, label: {
                Text("Tap to change text")
            })

        }.padding()
        .onTapGesture(perform: UIApplication.dismissKeyboard)
//        .gesture(TapGesture().onEnded { _ in UIApplication.dismissKeyboard()})
    }
}

public extension UIApplication {

    static func dismissKeyboard() {
        let keyWindow = shared.connectedScenes
                .filter({$0.activationState == .foregroundActive})
                .map({$0 as? UIWindowScene})
                .compactMap({$0})
                .first?.windows
                .filter({$0.isKeyWindow}).first
        keyWindow?.endEditing(true)
    }
}

正如您在代码中看到的,我尝试了两种方法来获取点击手势,但没有任何效果。

【问题讨论】:

  • 从你帖子中的链接this answer 可以完美配合任何控件,你为什么选择其他方法(对我来说这绝对不可靠)?
  • 感谢您的指出。在尝试了前 5-6 个答案并发布了这个问题后,我失去了希望。
  • @Asperi 它可以工作,不幸的是,你失去了内置的文本字段行为(文本选择等)......一般来说,它不存在通用解决方案,我更喜欢解决它临时性的(逐案)

标签: ios swift swiftui


【解决方案1】:

你可以像这样在 View 上创建扩展

extension View {
  func endTextEditing() {
    UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder),
                                    to: nil, from: nil, for: nil)
  }
}

并将其用于要关闭键盘的视图。

.onTapGesture {

      self.endTextEditing()
} 

我刚刚在最近的 raywenderlich 教程中看到了这个解决方案,所以我认为它是目前最好的解决方案。

【讨论】:

  • 如果 Picker 和 Button 应该工作,您将在哪个控件上应用 .onTapGesture 修饰符?不幸的是,这种方法在这里不可用...
  • 我认为您可以像 OP 那样在 VStack 上应用 .onTapGesture 吗?
【解决方案2】:

通过点击任意位置(如其他人建议的那样)关闭键盘可能会导致很难找到错误(或不需要的行为)。

  1. 您失去了默认的内置 TextField 行为,例如部分文本 选择、复制、分享等。
  2. onCommit 未被调用

我建议您根据字段的编辑状态考虑手势遮罩

/// Attaches `gesture` to `self` such that it has lower precedence
    /// than gestures defined by `self`.
    public func gesture<T>(_ gesture: T, including mask: GestureMask = .all) -> some View where T : Gesture

这有助于我们写作

.gesture(TapGesture().onEnded({
            UIApplication.shared.windows.first{$0.isKeyWindow }?.endEditing(true)
        }), including: (editingFlag) ? .all : .none)

点击修改后的视图将关闭键盘,但前提是editingFlag == true不要在 TextField 上应用它!否则我们又回到了故事的开头:-)

这个修饰符将帮助我们解决Picker 的问题,但不能解决Button 的问题。这很容易解决,同时从它自己的动作处理程序中关闭键盘。我们没有任何其他控件,所以我们几乎完成了

最后我们必须为视图的其余部分找到解决方案,所以点击任意位置(不包括我们的 TextFields)关闭键盘。使用填充一些透明视图的ZStack 可能是最简单的解决方案。

让我们看看这一切的实际效果(复制-粘贴-在您的 Xcode 模拟器中运行)

import SwiftUI
struct ContentView: View {

    @State var selected = 0

    @State var textFieldValue0 = ""
    @State var textFieldValue1 = ""

    @State var editingFlag = false

    @State var message = ""

    var body: some View {
        ZStack {
            // TODO: make it Color.clear istead yellow
            Color.yellow.opacity(0.1).onTapGesture {
                UIApplication.shared.windows.first{$0.isKeyWindow }?.endEditing(true)
            }
            VStack {

                TextField("Salutation", text: $textFieldValue0, onEditingChanged: { editing in
                    self.editingFlag = editing
                }, onCommit: {
                    self.onCommit(txt: "salutation commit")
                })
                    .padding()
                    .background(Color(UIColor.secondarySystemFill))
                    .cornerRadius(4)

                TextField("Welcome message", text: $textFieldValue1, onEditingChanged: { editing in
                    self.editingFlag = editing
                }, onCommit: {
                    self.onCommit(txt: "message commit")
                })
                    .padding()
                    .background(Color(UIColor.secondarySystemFill))
                    .cornerRadius(4)

                Picker(selection: $selected, label: Text(""), content: {
                    Text("Word").tag(0)
                    Text("Phrase").tag(1)
                    Text("Sentence").tag(2)
                })
                    .pickerStyle(SegmentedPickerStyle())
                    .gesture(TapGesture().onEnded({
                        UIApplication.shared.windows.first{$0.isKeyWindow }?.endEditing(true)
                    }), including: (editingFlag) ? .all : .none)


                Button(action: {
                    self.textFieldValue0 = "Hi"
                    print("button pressed")
                    UIApplication.shared.windows.first{$0.isKeyWindow }?.endEditing(true)
                }, label: {
                    Text("Tap to change salutation")
                        .padding()
                        .background(Color.yellow)
                        .cornerRadius(10)
                })

                Text(textFieldValue0)
                Text(textFieldValue1)
                Text(message).font(.largeTitle).foregroundColor(Color.red)

            }

        }
    }

    func onCommit(txt: String) {
        print(txt)
        self.message = [self.textFieldValue0, self.textFieldValue1].joined(separator: ", ").appending("!")
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

如果您错过了 onCommit(在 TextField 外部点击时不会调用它),只需将其添加到您的 TextField onEditingChanged(它模仿在键盘上键入 Return)

TextField("Salutation", text: $textFieldValue0, onEditingChanged: { editing in
    self.editingFlag = editing
    if !editing {
        self.onCommit(txt: "salutation")
    }
 }, onCommit: {
     self.onCommit(txt: "salutation commit")
 })
     .padding()
     .background(Color(UIColor.secondarySystemFill))
     .cornerRadius(4)

【讨论】:

    【解决方案3】:

    我想进一步采用 Mark T.s Answer 并将整个函数添加到 View 的扩展中:

    extension View {
        func hideKeyboardWhenTappedAround() -> some View  {
            return self.onTapGesture {
                UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), 
                      to: nil, from: nil, for: nil)
            }
        }
    }
    
    

    然后可以这样调用:

    var body: some View {
        MyView()
          // ...
          .hideKeyboardWhenTappedAround()
          // ...
    }
    

    【讨论】:

      【解决方案4】:

      @user3441734 很聪明,可以仅在需要时启用关闭手势。您可以:

      1. 监控 UIWindow.keyboardWillShowNotification / willHide

      2. 通过在根视图设置的 EnvironmentKey 传递当前键盘状态

      针对 iOS 14.5 测试。

      将关闭手势附加到表单

      Form { }
          .dismissKeyboardOnTap()
      
      

      在根视图中设置监视器

      // Root view
          .environment(\.keyboardIsShown, keyboardIsShown)
          .onDisappear { dismantleKeyboarMonitors() }
          .onAppear { setupKeyboardMonitors() }
      
      // Monitors
      
          @State private var keyboardIsShown = false
          @State private var keyboardHideMonitor: AnyCancellable? = nil
          @State private var keyboardShownMonitor: AnyCancellable? = nil
          
          func setupKeyboardMonitors() {
              keyboardShownMonitor = NotificationCenter.default
                  .publisher(for: UIWindow.keyboardWillShowNotification)
                  .sink { _ in if !keyboardIsShown { keyboardIsShown = true } }
              
              keyboardHideMonitor = NotificationCenter.default
                  .publisher(for: UIWindow.keyboardWillHideNotification)
                  .sink { _ in if keyboardIsShown { keyboardIsShown = false } }
          }
          
          func dismantleKeyboarMonitors() {
              keyboardHideMonitor?.cancel()
              keyboardShownMonitor?.cancel()
          }
      
      

      SwiftUI 手势 + 糖

      
      struct HideKeyboardGestureModifier: ViewModifier {
          @Environment(\.keyboardIsShown) var keyboardIsShown
          
          func body(content: Content) -> some View {
              content
                  .gesture(TapGesture().onEnded {
                      UIApplication.shared.resignCurrentResponder()
                  }, including: keyboardIsShown ? .all : .none)
          }
      }
      
      extension UIApplication {
          func resignCurrentResponder() {
              sendAction(#selector(UIResponder.resignFirstResponder),
                         to: nil, from: nil, for: nil)
          }
      }
      
      extension View {
      
          /// Assigns a tap gesture that dismisses the first responder only when the keyboard is visible to the KeyboardIsShown EnvironmentKey
          func dismissKeyboardOnTap() -> some View {
              modifier(HideKeyboardGestureModifier())
          }
          
          /// Shortcut to close in a function call
          func resignCurrentResponder() {
              UIApplication.shared.resignCurrentResponder()
          }
      }
      

      环境键

      extension EnvironmentValues {
          var keyboardIsShown: Bool {
              get { return self[KeyboardIsShownEVK] }
              set { self[KeyboardIsShownEVK] = newValue }
          }
      }
      
      private struct KeyboardIsShownEVK: EnvironmentKey {
          static let defaultValue: Bool = false
      }
      

      【讨论】:

        【解决方案5】:

        您可以将.allowsHitTesting(false) 设置为您的 Picker 以忽略 VStack 上的点击

        【讨论】:

        • 这不起作用。我已经尝试使用 .allowsHitTesting 和元素的所有可能组合。甚至尝试输入ZStack,但无济于事。
        【解决方案6】:

        将此应用到根视图

        .onTapGesture {
            UIApplication.shared.endEditing()
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-02-09
          • 1970-01-01
          • 2011-10-22
          • 2017-01-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-07-24
          相关资源
          最近更新 更多