当键盘处于活动状态时呈现模态视图控制器

11

我有一个表单,包含多个文本字段。用户像往常一样输入字段。但是用户还可以选择双击文本字段,这会呈现一个模态视图控制器,允许用户从与该字段相关的多个选项中进行选择。

我是否可以以某种方式将模态视图“覆盖”在键盘上,这样当它被解除时,键盘仍然处于活动状态,以供之前已经是第一响应者的字段使用?

目前,当模态框出现时,键盘会关闭,并在模态框消失时重新出现。我认为这看起来很笨拙和分散注意力。我希望能简化它,并减少屏幕上的动画数量。


这对我来说看起来很笨重,而且分散注意力。嗯,这就是iOS处理模态的方式。 - Andrew
请参考这里:https://dev59.com/sXA75IYBdhLWcg3wS3BA - Vinaykrishnan
好像真的没有办法做到这一点。 - DanM
为什么你没有选择 Rob B 的答案呢?他的回答非常出色,而且他也花了很多心思来回应你……你可以现在就这样做! - David H
另一个使用UIWindow的工作示例在这里:https://dev59.com/EFwY5IYBdhLWcg3waXO5 - user1709076
6个回答

17

编辑:我已经更新了这个答案,以适应iOS 12和Swift。修订后的示例项目(包含新的Swift和更新的Objective-C实现)在这里


您可以创建一个新的UIWindow,并在隐藏键盘窗口的同时将其放置在默认窗口上方。


重叠新的UIWindow在现有窗口上的动画示例


我在Github上有一个示例项目here,但是基本过程如下。

  • 为您的模态视图创建一个新的UIViewController类。我称之为OverlayViewController。按您的要求设置相应的视图。根据您的问题,您需要传回一些选项,因此我制作了一个委托协议OverlayViewControllerDelegate,并将主窗口的根视图控制器(类ViewController)设置为我们的委托。
protocol OverlayViewControllerDelegate: class {
  func optionChosen(option: YourOptionsEnum)
}
  • 为我们最初的视图控制器添加一些支持属性。
class ViewController: UIViewController {
  /// The text field that responds to a double-tap.
  @IBOutlet private weak var firstField: UITextField!
  /// A simple label that shows we received a message back from the overlay.
  @IBOutlet private weak var label: UILabel!
  /// The window that will appear over our existing one.
  private var overlayWindow: UIWindow?
  • 在你的UITextField上添加一个UITapGestureRecognizer
override func viewDidLoad() {
  super.viewDidLoad()

  // Set up gesture recognizer
  let doubleTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap))
  doubleTapRecognizer.numberOfTapsRequired = 2
  doubleTapRecognizer.delegate = self

  firstField.addGestureRecognizer(doubleTapRecognizer)

  firstField.becomeFirstResponder()
}
  • UITextField 具有内置的手势识别器,因此我们需要允许多个 UIGestureRecognizer 同时操作。
extension ViewController: UIGestureRecognizerDelegate {
  // Our gesture recognizer clashes with UITextField's.
  // Need to allow both to work simultaneously.
  func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
                         shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
  }
}
  • 这是有趣的部分。当手势识别器被触发时,创建新的UIWindow,将你的OverlayViewController指定为根视图控制器并显示它。请注意,我们将窗口级别设置为UIWindowLevelAlert,以使其出现在前面。但是,尽管警报窗口级别,键盘仍将在前方,因此我们还必须手动隐藏它的窗口。

重要的是不要将新的UIWindow设置为关键窗口或更改第一响应者从UITextField,否则会关闭键盘。

以前(iOS 10之前?)我们可以通过overlayWindow.makeKeyAndVisible()来实现,但现在将其设置为关键会关闭键盘。此外,键盘的窗口现在具有非标准的UIWindow.Level值,位于每个公开定义值的前面。我通过在层次结构中找到键盘的窗口并隐藏它来解决了这个问题。

@objc func handleDoubleTap() {
    // Prepare the overlay window
    guard let overlayFrame = view?.window?.frame else { return }
    overlayWindow = UIWindow(frame: overlayFrame)
    overlayWindow?.windowLevel = .alert
    let overlayVC = OverlayViewController.init(nibName: "OverlayViewController", bundle: nil)
    overlayWindow?.rootViewController = overlayVC
    overlayVC.delegate = self

    // The keyboard's window always appears to be the last in the hierarchy.
    let keyboardWindow = UIApplication.shared.windows.last
    keyboardWindow?.isHidden = true
}
  • 叠加窗口现在是原始窗口。用户现在可以选择您建立的叠加视图中的任何选项。当您的用户选择一个选项后,您的代理应该采取您打算执行的任何操作,然后关闭叠加窗口并再次显示键盘。
func optionChosen(option: YourOptionsEnum) {
  // Your code goes here. Take action based on the option chosen.
  // ...

  // Dismiss the overlay and show the keyboard
  overlayWindow = nil;
  UIApplication.shared.windows.last?.isHidden = false
}
  • 覆盖窗口应该消失,原始窗口应该出现在相同的位置上,键盘也应该和之前一样。

2
这是一个非常好的答案。恰好满足了我所寻找的! - DanM
@DanM 你认为这是一个很好的答案(我也是),那为什么不把它作为正确答案接受呢?这是我的问题吗?我只是没有看到勾选标记吗? - David H
哎呀!看来我不知怎么的在没有标记为正确的情况下颁发了赏金...谢谢你指出来! - DanM
这个在iOS 11上能用吗?我看它好像不起作用。 - tdios
我已经更新了答案和示例项目。如果您发现任何问题,请告诉我! - Rob Bajorek
厉害的方法,Rob。你或者其他人知道如何在从模态窗口中调用时使其工作吗?基本上,我的UITextField已经在一个模态窗口上(modalPresentationStyle = .overFullScreen),我想要保持键盘打开。我尝试了各种组合但是无法使其工作。我可以让keyboardWindow消失,但是无论如何overlayWindow都不会出现。 - Jan

3
@Rob Bajorek的回答非常出色。对于iOS 9、10,有一些小的变化。
现在使用的代码是:
[self.overlayWindow setWindowLevel:UIWindowLevelAlert];
[self.overlayWindow makeKeyAndVisible];

请放置以下代码:

NSArray *windows = [[UIApplication sharedApplication] windows];
UIWindow *lastWindow = (UIWindow *)[windows lastObject];
[self.overlayWindow setWindowLevel:lastWindow.windowLevel + 1];
[self.overlayWindow setHidden:NO];

您应该为这种建议使用注释。 - always-a-learner
1
当我的声誉允许时,我一定会使用评论。无论如何,感谢后见之明队长。 - Harman

3

我现在无法尝试这个,但已经为其他目的实现了类似的功能。在呈现模态控制器的操作中,我假设手势识别器或委托方法,首先截取屏幕截图并将其放置在当前子视图上方的imageView中。稍后返回时,只需删除imageView即可。

听起来可能很疯狂,但我记得曾经在转换过程中键盘移动导致类似的笨拙行为。实现起来并不难。

如果你尝试有困难,也许有人会提供一些代码。我可以稍后引用自己的工作并添加一个例子,但现在不行。


我喜欢这个——看起来是实现我想要的效果相当简单的一种方式。 - DanM

1
为了使键盘能够显示任何文本输入字段,如UITextField、UITextView或UISearchBar,它们应该是第一响应者,并且它们应该在视图中可见。这意味着响应的视图应该在窗口的顶层级别层次结构中。
如果不需要这种效果,可以将ViewController.view作为子视图添加到self.view中并进行动画,而不是展示一个ViewController

这样做不会让键盘停留在我正在展示的任何视图的上方吗? - DanM
是的,它会。最好保持你的视线在键盘上方。 - Rajesh
那么这并没有做我所希望的事情。我想知道如果我直接将视图添加到窗口中,是否会起作用? - DanM

1

您可以访问iOS键盘框架。

您需要实现代码来监听键盘通知(如UIKeyboardWillShowNotification和UIKeyboardWillChangeFrameNotification)。通知将向您发送有关键盘框架的信息。

请查看“键盘通知用户信息键”在windows reference中的描述。

对于您的目的,您会发现以下内容很有用:

UIKeyboardBoundsUserInfoKey用于标识NSValue对象的键,该对象包含一个CGRect,用于在窗口坐标中标识键盘的边界矩形。此值足以获取键盘的大小。如果要获取键盘在屏幕上的原点(动画前或后),请使用通过UIKeyboardCenterBeginUserInfoKey或UIKeyboardCenterEndUserInfoKey常量从用户信息字典中获取的值。

有了键盘框架的信息,您就可以显示模态视图。


我不太明白那如何帮助我在模态框下覆盖键盘? - DanM
如果您在窗口坐标中有键盘的框架,您可以在那里放置需要显示为模态的视图。 - Luca Bartoletti
啊,你的意思是直接将模态视图添加到窗口中? - DanM
如果需要,您可以使用UIView/UIWindow类的“convertRect:”方法将坐标转换为您喜欢的视图/窗口空间。 - Luca Bartoletti
如果您需要建议,您可以编写一个自定义控制器,能够提供该类型的模态视图。如果您将视图附加到窗口上,则无法免费旋转。 - Luca Bartoletti
显示剩余3条评论

1

只需在您的文本字段中添加一个轻拍手势和一个UITextfield *flagTextfield;

UITapGestureRecognizer* doubleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(DoubleTapMethod:)];
doubleTap.numberOfTapsRequired = 2;
[self.txtTest addGestureRecognizer:doubleTap];


-(void)DoubleTapMethod:(UITapGestureRecognizer *)gesture
{

     [flagTextfield resignFirstResponder];
     NSLog(@"DoubleTap detected");
     //Set your logic on double tap of Textfield...
     //presents a modal view controller
}

- (void)textFieldDidBeginEditing:(UITextField *)textField{

        flagTextfield = textfield;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接