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

Posted

技术标签:

【中文标题】在键盘处于活动状态时呈现模态视图控制器【英文标题】:presenting a modal view controller while the keyboard is active 【发布时间】:2014-08-05 03:21:46 【问题描述】:

所以我基本上有一个表单,由几个文本字段组成。用户像往常一样输入字段。但用户也可以选择双击文本字段,它会显示一个模态视图控制器,允许用户从与该字段相关的多个选项中进行选择。

我能否以某种方式在键盘“上方”呈现模态,这样当它被解除时,键盘对于在我呈现模态之前已经是第一响应者的字段仍然处于活动状态?

现在,键盘在模态出现时消失,并在模态消失时重新出现。它对我来说看起来很笨重,而且让人分心。希望简化它,并减少屏幕上的动画数量。

【问题讨论】:

It looks clunky to me, and distracting. 这就是 ios 处理模态的方式。 请参考:***.com/questions/3372333/… 听起来好像真的没有办法做到这一点。 您为什么不选择 Rob B 的答案?太好了,他费了好大劲才回复你……你现在就可以了! 另一个使用 uiwindow 的工作示例在这里 ***.com/questions/32647890/… 【参考方案1】:

编辑:我已经为 iOS 12 和 Swift 更新了这个答案。修改后的示例项目(包含新的 Swift 和更新的 Objective-C 实现)是here。


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



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

为您的模态视图创建一个新的UIViewController 类。我打电话给我的OverlayViewController。根据需要设置相应的视图。根据您的问题,您需要回传一些选项,因此我创建了一个委托协议 OverlayViewController,并将主窗口的根视图控制器(类 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?
UITapGestureRecognizer 添加到您的UITextField
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 内置了手势识别器,所以我们需要允许多个UIGestureRecognizers 同时操作。
extension ViewController: UIGestureRecognizerDelegate 
  // Our gesture recognizer ***es 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(),但现在将其设置为 key 会关闭键盘。此外,键盘窗口现在有一个非标准的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

覆盖窗口应该会消失,您的原始窗口应该会出现,并且键盘的位置与之前相同。

【讨论】:

这是一个很好的答案。正是我想要的! @DanM 您认为这是一个很好的答案(我也是) - 那么为什么不接受它作为正确答案呢?这是我的问题吗?我只是没有看到复选标记? 糟糕!我显然以某种方式授予了赏金而没有标记为正确...感谢您指出这一点! 这适用于 iOS 11 吗?我认为它不起作用 我已经更新了答案和示例项目。如果您发现任何问题,请告诉我!【参考方案2】:

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

听起来可能很疯狂,但我记得在过渡期间键盘移动会导致类似的笨拙行为的过渡中这样做了。实现起来一点都不难。

如果您在尝试时遇到问题,也许有人会提供一些代码。我可以稍后参考我自己的工作并添加一个示例,但现在不行。

【讨论】:

我喜欢这个——似乎是一种很容易获得我想要的效果的方法。【参考方案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];

【讨论】:

您应该对此类建议使用评论。 当我的名声允许的时候,我一定会使用cmets。无论如何,感谢后见之明船长。【参考方案4】:

为了使任何文本接受字段(如UITextField or UITextView or UISearchBar)可见,应该是第一响应者,并且它们应该在视图中可见。这意味着响应视图应该位于窗口的顶层层次结构中。

如果您不需要此效果,您可以添加 ViewController.view 作为带有动画的 self.view 的子视图,而不是显示 ViewController

【讨论】:

不会让键盘保持在我呈现的任何视图之上吗? 会的。最好将视图保持在键盘上方 正确——所以没有做我希望做的事情。我想知道我是否将视图直接添加到窗口中,是否可行?【参考方案5】:

您可以访问 iOS 中的键盘框架。

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

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

你会发现对你有用的目的:

UIKeyboardBoundsUserInfoKey NSValue 对象的键,其中包含标识边界矩形的 CGRect 窗口坐标中的键盘。该值足以获得 键盘的大小。如果你想知道的起源 屏幕上的键盘(动画之前或之后)使用这些值 通过用户信息字典获取 UIKeyboardCenterBeginUserInfoKey 或 UIKeyboardCenterEndUserInfoKey 常量。

使用键盘框架的信息,您可以在那里显示模态视图。

【讨论】:

不太明白这如何帮助我用模态覆盖键盘? 如果您在窗口坐标中有键盘的框架,您可以将需要显示为模态的视图放在那里 啊,你是说直接将模态视图添加到窗口中? 如果您需要,您可以使用 UIView/UIWindow 类的“convertRect:”方法来转换您偏好的视图/窗口空间中的坐标 不,我是说你在窗口坐标中有框架,正如我在上面写的那样,你可以将它翻译成你选择的视图的坐标【参考方案6】:

只需在您的文本字段和 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;

【讨论】:

以上是关于在键盘处于活动状态时呈现模态视图控制器的主要内容,如果未能解决你的问题,请参考以下文章

应用程序激活时呈现模态视图

使模态控制器下的区域可滚动

切换回搜索栏处于活动状态时,选项卡栏视图变为空白

热点处于活动状态时无法关闭模态呈现的 AVPlayerViewController

如何使我的键盘弹出窗口更加流畅,我的视图控制器以模态方式出现?

iiview 甲板控制器在呈现模态视图时更改框架而不更改界面方向