如何使用 SwiftUI
针对以下情况隐藏 keyboard
?
情况 1
我有一个TextField
,需要在用户点击“返回”按钮时隐藏 keyboard
。
情况 2
我有一个TextField
,需要在用户点击外部区域时隐藏 keyboard
。
我可以通过使用 SwiftUI
来实现这个功能吗?
注意:
我没有问关于 UITextField
的问题。 我想通过使用 SwifUI.TextField
来完成此操作。
如何使用 SwiftUI
针对以下情况隐藏 keyboard
?
情况 1
我有一个TextField
,需要在用户点击“返回”按钮时隐藏 keyboard
。
情况 2
我有一个TextField
,需要在用户点击外部区域时隐藏 keyboard
。
我可以通过使用 SwiftUI
来实现这个功能吗?
注意:
我没有问关于 UITextField
的问题。 我想通过使用 SwifUI.TextField
来完成此操作。
@Mikhail的答案非常有效,只是它无法支持在TextView中拖动选择文本的问题 - 点击所选文本时键盘将关闭。我扩展了他的解决方案,为AnyGesture提供更好的文本编辑用户体验。(来自如何检查UITextRangeView?的答案)
有没有优化while循环的建议?
class AnyGestureRecognizer: UIGestureRecognizer {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
if let touchedView = touches.first?.view, touchedView is UIControl {
state = .cancelled
} else if let touchedView = touches.first?.view as? UITextView, touchedView.isEditable {
state = .cancelled
} else {
// Check if it is a subview of editable UITextView
if var touchedView = touches.first?.view {
while let superview = touchedView.superview {
if let view = superview as? UITextView, view.isEditable {
state = .cancelled
return
} else {
touchedView = superview
}
}
}
state = .began
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
state = .ended
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
state = .cancelled
}
}
真正的SwiftUI解决方案
@State var dismissKeyboardToggle = false
var body: some View {
if dismissKeyboardToggle {
textfield
} else {
textfield
}
Button("Hide Keyboard") {
dismissKeyboardToggle.toggle()
}
}
这将完美无缺地工作
.gesture(
DragGesture().onEnded { value in
self.dismissKeyboard()
})
然而,这可能会阻止滑动操作。我通过在 CoporateIdentity 视图内部的背景视图中添加事件来避免这种情况:
struct CIView: View {
var displayView: AnyView
var body: some View {
ZStack{
Color("Background").edgesIgnoringSafeArea(.all)
.gesture(
DragGesture().onEnded { value in
self.dismissKeyboard()
})
displayView
}
.foregroundColor(Color("Foreground"))
}
private func dismissKeyboard() {
UIApplication.shared.dismissKeyboard()
}
}
这个视图可以像这样使用:
CIView(displayView: AnyView(YourView()))
我正在尝试在SwiftUIForms中实现单击隐藏键盘,同时Picker也应该能够通过单击工作。
我搜索了很多以找到一个适当的解决方案,但没有一个适用于我。所以我自己制作了一个扩展,它非常有效。
在您的SwiftUI表单视图中使用:
var body: some View {
.onAppear { KeyboardManager.shared.setCurrentView(UIApplication.topViewController()?.view)
}
}
enum KeyboardNotificationType {
case show
case hide
}
typealias KeyBoardSizeBlock = ((CGSize?, UIView?, KeyboardNotificationType) -> Void)
class KeyboardManager: NSObject {
static let shared = KeyboardManager()
private weak var view: UIView?
var didReceiveKeyboardEvent: KeyBoardSizeBlock?
@objc public var shouldResignOnTouchOutside = true {
didSet {
resignFirstResponderGesture.isEnabled = shouldResignOnTouchOutside
}
}
@objc lazy public var resignFirstResponderGesture: UITapGestureRecognizer = {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissCurrentKeyboard))
tap.cancelsTouchesInView = false
tap.delegate = self
return tap
}()
private override init() {
super.init()
self.setup()
}
func setCurrentView(_ view: UIView?) {
self.view = view
resignFirstResponderGesture.isEnabled = true
if let view = self.view {
view.addGestureRecognizer(resignFirstResponderGesture)
}
}
private func setup() {
registerForKeyboardWillShowNotification()
registerForKeyboardWillHideNotification()
}
private func topViewHasCurrenView() -> Bool {
if view == nil { return false }
let currentView = UIApplication.topViewController()?.view
if currentView == view { return true }
for subview in UIApplication.topViewController()?.view.subviews ?? [] where subview == view {
return true
}
return false
}
@objc func dismissCurrentKeyboard() {
view?.endEditing(true)
}
func removeKeyboardObserver(_ observer: Any) {
NotificationCenter.default.removeObserver(observer)
}
private func findFirstResponderInViewHierarchy(_ view: UIView) -> UIView? {
for subView in view.subviews {
if subView.isFirstResponder {
return subView
} else {
let result = findFirstResponderInViewHierarchy(subView)
if result != nil {
return result
}
}
}
return nil
}
deinit {
removeKeyboardObserver(self)
}
}
// MARK: - Keyboard Notifications
extension KeyboardManager {
private func registerForKeyboardWillShowNotification() {
_ = NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidShowNotification, object: nil, queue: nil, using: { [weak self] notification -> Void in
guard let `self` = self else { return }
guard let userInfo = notification.userInfo else { return }
guard var kbRect = (userInfo[UIResponder.keyboardFrameEndUserInfoKey]! as AnyObject).cgRectValue else { return }
kbRect.size.height -= self.view?.safeAreaInsets.bottom ?? 0.0
var mainResponder: UIView?
guard self.topViewHasCurrenView() else { return }
if let scrollView = self.view as? UIScrollView {
let contentInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: kbRect.size.height, right: 0.0)
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
guard let firstResponder = self.findFirstResponderInViewHierarchy(scrollView) else {
return
}
mainResponder = firstResponder
var aRect = scrollView.frame
aRect.size.height -= kbRect.size.height
if (!aRect.contains(firstResponder.frame.origin) ) {
scrollView.scrollRectToVisible(firstResponder.frame, animated: true)
}
} else if let tableView = self.view as? UITableView {
guard let firstResponder = self.findFirstResponderInViewHierarchy(tableView),
let pointInTable = firstResponder.superview?.convert(firstResponder.frame.origin, to: tableView) else {
return
}
mainResponder = firstResponder
var contentOffset = tableView.contentOffset
contentOffset.y = (pointInTable.y - (firstResponder.inputAccessoryView?.frame.size.height ?? 0)) - 10
tableView.setContentOffset(contentOffset, animated: true)
} else if let view = self.view {
guard let firstResponder = self.findFirstResponderInViewHierarchy(view) else {
return
}
mainResponder = firstResponder
var aRect = view.frame
aRect.size.height -= kbRect.size.height
if (!aRect.contains(firstResponder.frame.origin) ) {
UIView.animate(withDuration: 0.1) {
view.transform = CGAffineTransform(translationX: 0, y: -kbRect.size.height)
}
}
}
if let block = self.didReceiveKeyboardEvent {
block(kbRect.size, mainResponder, .show)
}
})
}
private func registerForKeyboardWillHideNotification() {
_ = NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: nil, using: { [weak self] notification -> Void in
guard let `self` = self else { return }
guard let userInfo = notification.userInfo else { return }
guard let kbRect = (userInfo[UIResponder.keyboardFrameEndUserInfoKey]! as AnyObject).cgRectValue else { return }
let contentInsets = UIEdgeInsets.zero
guard self.topViewHasCurrenView() else { return }
if let scrollView = self.view as? UIScrollView {
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
} else if let tableView = self.view as? UITableView {
tableView.contentInset = contentInsets
tableView.scrollIndicatorInsets = contentInsets
tableView.contentOffset = CGPoint(x: 0, y: 0)
} else if let view = self.view {
view.transform = CGAffineTransform(translationX: 0, y: 0)
}
if let block = self.didReceiveKeyboardEvent {
block(kbRect.size, nil, .hide)
}
})
}
}
//MARK: - UIGestureRecognizerDelegate
extension KeyboardManager: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view is UIControl ||
touch.view is UINavigationBar { return false }
return true
}
}
你可以简单地做到这一点,
Form {
}
.scrollDismissesKeyboard(.interactively)
SwiftUI于2020年6月发布,搭配Xcode 12和iOS 14版本新增了hideKeyboardOnTap()修饰符。这应该可以解决您的第二个问题。 对于您的第一个问题,Xcode 12和iOS 14已经免费提供了解决方案:TextField的默认键盘会在按下Return按钮时自动隐藏。