简体   繁体   English

防止或检测从iOS 8键盘传递的事件

[英]Prevent or detect events passed on from iOS 8 keyboard

In our iOS 8 app, the search screen, which is similar to the search screen of the App Store app, no longer works reliably. 在我们的iOS 8应用程序中,搜索屏幕与App Store应用程序的搜索屏幕类似,不再可靠。 When a user taps a key, the keyboard is sometimes closed or even an action executed. 当用户轻敲键时,键盘有时会关闭甚至执行操作。

Part of the reason is that the tap event is passed on to lower layers, which is close the keyboard (smoke screen), navigate to a search result (UITableView with search result) or execute the search (UITableView with search term suggestions). 部分原因是tap事件被传递到较低层,即关闭键盘(烟雾屏幕),导航到搜索结果(带搜索结果的UITableView)或执行搜索(带搜索术语建议的UITableView)。

For some unknown reason, it properly works as long as the user stays in the app. 由于某些未知原因,只要用户留在应用程序中,它就能正常工作。 However, if he/she goes to a different app and then returns, the events are passed on. 但是,如果他/她转到另一个应用程序然后返回,则会传递事件。 This behavior affects all iOS 8 version (8.0.x, 8.1). 此行为会影响所有iOS 8版本(8.0.x,8.1)。

How can we prevent the keyboard from passing on tap events or how can we detect such an event (eg from tableView:didSelectRowAtIndexPath: )? 我们如何防止键盘传递点击事件或我们如何检测这样的事件(例如来自tableView:didSelectRowAtIndexPath:

The question " Keyboard intermittently disappears when editing using IOS 8 " seems to refer to the same problem though I can't figure out how to apply that ugly hack to my situation. 使用IOS 8编辑时键盘间歇性消失 ”的问题似乎引用了同样的问题,虽然我无法弄清楚如何将这种丑陋的黑客应用于我的情况。


I've just found a similar post in Apple's developer forum. 我刚刚在Apple的开发者论坛上找到了类似的帖子 Unfortunately, it has no answers and has been archived in the mean time: 不幸的是,它没有答案,并在同一时间存档:

I have overriden -hitTest:withEvent: on a view on my view hierarchy where I check if it was touched and will forward the touch to its subviews and fire a selector to dismiss the keyboard. 我在视图层次结构上的视图上覆盖-hitTest:withEvent:我检查它是否被触摸并将触摸转发到其子视图并触发选择器以关闭键盘。

On iOS 7 (and, more strangely, when the app is launched on iOS 8) this works perfectly and -hitTest:withEvent: will never be called if the view is behind the keyboard and the user taps on the keyboard. 在iOS 7上(更奇怪的是,当应用程序在iOS 8上启动时)这非常有效,并且-hitTest:withEvent:如果视图位于键盘后面且用户点击键盘,则永远不会调用它。

But on iOS 8, if the user sends the app to the background and brings it back to the foreground, tapping anything on the keyboard will trigger -hitTest:withEvent: as if the view was above the keyboard on the view hierarchy. 但是在iOS 8上,如果用户将应用程序发送到后台并将其带回前台,则点击键盘上的任何内容都会触发-hitTest:withEvent:就好像视图位于视图层次结构上的键盘上方一样。 I've used Reveal.app to verify that it is not above the keyboard, it is behind as expected. 我已经使用Reveal.app验证它不在键盘上方,它按预期落后。

Anyone got any ideas of what could be happening? 任何人都对可能发生的事情有任何想法? I've created a sample project and attached it to a radar for Apple as this looks like a bug on iOS 8 for not working the same way consistently. 我已经创建了一个示例项目并将其附加到Apple的雷达上,因为这看起来像iOS 8上的一个错误,因为它不能以相同的方式一致地工作。

Update 更新

My search screen contains two views (on top of each other): a background view visible when no search results are available and a table view visible if search results are available. 我的搜索屏幕包含两个视图(彼此叠加):没有搜索结果时可见的背景视图,如果搜索结果可用,则可以看到表格视图。 On top of these, I dynamically add two additional views if the search bar becomes active: a smoke class view that can be tapped to end the search text entry and a table view that displays search text suggestions. 除此之外,如果搜索栏变为活动状态,我会动态添加两个额外的视图:可以点击以结束搜索文本条目的烟雾类视图和显示搜索文本建议的表视图。 All four views are directly contained in the view controller's main view and cover the full area. 所有四个视图都直接包含在视图控制器的主视图中,并覆盖整个区域。

The interesting thing now is that the keyboard forwards event the two dynamically added views but not to the two lower views that are always there. 现在有趣的是,键盘转发事件是两个动态添加的视图,但不是两个始终存在的低视图。

I believe it's a bug that tap events are passed on to views underneath the keyboard. 我认为将点击事件传递给键盘下方的视图是一个错误。 As I've figured out in the mean time that only the dynamically added views are affected and not the ones that are part of the Interface Builder file, I've now come up with a work around: if the keyboard appears, I shrink these two view so they do not extend underneath the keyboard. 正如我在同一时间发现只有动态添加的视图受到影响而不是那些属于Interface Builder文件的视图一样,我现在想出一个解决方法:如果出现键盘,我会缩小这些两个视图,因此它们不会在键盘下方延伸。 And I grow them again when the keyboard disappears. 当键盘消失时,我再次长大它们。

So this is the code. 所以这就是代码。 The upper part up to (and excluding) [[NSNotificationCenter defaultCenter] has existed before. 上一部分(不包括) [[NSNotificationCenter defaultCenter]之前已存在。 The rest is the workaround. 其余的是解决方法。

- (BOOL) searchBarShouldBeginEditing: (UISearchBar*) searchBar {

    // calculate from for entire area
    CGRect frame = ...  // omitted

    // add smoke screen
    _smokeScreen = [UIButton buttonWithType: UIButtonTypeCustom];
    _smokeScreen.frame = frame;
    _smokeScreen.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    _smokeScreen.backgroundColor = [UIColor colorWithWhite: 0.0f alpha: 0.5f];
    [_smokeScreen addTarget: self action: @selector(smokeScreenPressed:) forControlEvents: UIControlEventTouchDown];
    [self.view addSubview: _smokeScreen];

    // add table view for search term suggestions
    _suggestionTableView = [[SearchControllerTableView alloc] initWithFrame:frame style:UITableViewStylePlain];
    _suggestionTableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    _suggestionTableView.dataSource = self;
    _suggestionTableView.delegate = self;
    _suggestionTableView.searchController = self;
    _suggestionTableView.hidden = YES;
    [self.view addSubview:_suggestionTableView];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidShow:)
                                                 name:UIKeyboardDidShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:)
                                                 name:UIKeyboardWillHideNotification object:nil];

    return YES;
}


- (void) keyboardDidShow:(NSNotification *) notification
{
    // shrink the smoke screen area and the table view because otherwise they'll receive tap events from the keyboard
    CGRect screenRect = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    CGRect windowRect = [self.view.window convertRect:screenRect fromWindow:nil];
    CGRect viewRect = [self.view convertRect:windowRect fromView:nil];

    [self setBottom: viewRect.origin.y];
}


- (void) keyboardWillHide:(NSNotification *) notification
{
    // grow the views again
    [self setBottom: self.view.frame.size.height];
}


- (void) setBottom: (CGFloat)y
{
    CGRect frame = _suggestionTableView.frame;
    frame.size.height = y - frame.origin.y;
    _suggestionTableView.frame = frame;

    frame = _smokeScreen.frame;
    frame.size.height = y - frame.origin.y;
    _smokeScreen.frame = frame;
}

在调用搜索视图时,您可以尝试使用视图属性userInteractionEnabledexclusiveTouch

I come across this problem with Xamarin.iOS , this is the solution for Xamarin.iOS use C# . 我用Xamarin.iOS遇到了这个问题,这是Xamarin.iOS使用C#的解决方案。

In WillMoveToSuperview method, observe keyboard show/hide event and store the keyboard frame in variable KeyboardEndFrame : WillMoveToSuperview方法中,观察键盘显示/隐藏事件并将键盘框架存储在变量KeyboardEndFrame

 if (newsuper != null)
 {
     var wrThis = new WeakReference<XInterceptTouchView> (this);
     // add notification observe
     keyboardDidShow = UIKeyboard.Notifications.ObserveDidShow ((sender, e) =>
     {
         XInterceptTouchView interceptTouchView;
         if (wrThis.TryGetTarget (out interceptTouchView))
         {
             interceptTouchView.KeyboardEndFrame = e.FrameEnd;
         }
     });
     keyboardDidHide = UIKeyboard.Notifications.ObserveDidHide ((sender, e) =>
     {
         XInterceptTouchView interceptTouchView;
         if (wrThis.TryGetTarget (out interceptTouchView))
         {
             interceptTouchView.KeyboardEndFrame = e.FrameEnd;
         }
     });
 }
 else
 {
     // remove notification observe
     keyboardDidShow?.Dispose ();
     keyboardDidHide?.Dispose ();
 }

In HitTest method, get the keyboard window and process touch event with the window: HitTest方法中,获取键盘窗口并使用窗口处理触摸事件:

if (KeyboardEndFrame.Contains (point))
{
    IntPtr handle = ObjCRuntime.Class.GetHandle ("UITextEffectsWindow");
    if (handle != IntPtr.Zero)
    {
        var keyboardWindow = UIApplication.SharedApplication.Windows.FirstOrDefault (w => w.IsKindOfClass (new ObjCRuntime.Class ("UITextEffectsWindow")));
        if (keyboardWindow != null)
            return keyboardWindow.HitTest (point, uievent);
    }
}

var hitTestView = base.HitTest (point, uievent);
this.EndEditing (true);
return hitTestView;

I found that keyboard touches trigger hitTest if I show the keyboard, send the application to the background, and come back. 如果我显示键盘,将应用程序发送到后台并返回,我发现键盘触摸会触发hitTest This shouldn't happen so it looks like an iOS bug. 这不应该发生,所以它看起来像一个iOS错误。 I see two solutions: 我看到两个解决方案:

#1 Send the touch away #1发送触摸

Solution #1: hitTest shouldn't be called for touches on the keyboard, so check against the keyboard frame and send the touch away. 解决方法#1:不应该为键盘上的触摸调用hitTest ,因此请检查键盘框架并发送触摸。

var keyboardFrame: CGRect?

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
{
    if let keyboardFrame = keyboardFrame {
        if keyboardFrame.contains(point){
            return super.hitTest(CGPoint(x:-1,y:-1), with: event)
        }
    }
    return super.hitTest(point, with: event)
}

deinit {
    NotificationCenter.default.removeObserver(self)
}

func subscribeKeyboard(){
    NotificationCenter.default.addObserver(self, selector: #selector(MyView.keyboardDidShow(_:)), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(MyView.keyboardWillHide(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}

func keyboardDidShow(_ notification: NSNotification) {
    keyboardFrame = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
}

func keyboardWillHide(_ notification: NSNotification) {
    keyboardFrame = nil
}

#2 Pull the keyboard before resigning #2在辞职前拉动键盘

Solution 2#: Pull the keyboard before resigning active. 解决方案2#:在重新启动活动之前拉动键盘。 Two flaws: you are touching the delegate to fix something elsewhere, and the user is surprised to find the keyboard down when the app comes back. 两个缺陷:你正在触摸代表在其他地方修复某些东西,当用户回来时,用户很惊讶地发现键盘已关闭。

func applicationWillResignActive(_ application: UIApplication) {
    UIApplication.shared.sendAction(#selector(self.resignFirstResponder), to: nil, from: nil, for: nil)
}

Or if you want this done for a particular view controller only: 或者,如果您只想为特定视图控制器执行此操作:

if let controller = UIApplication.shared.keyWindow?.topmostViewController {
    if controller is MyViewController {
        NSLog("Pulling the keyboard to prevent touches from falling through after coming back from the background.")
        UIApplication.shared.sendAction(#selector(self.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

where topmostViewController is: topmostViewController是:

extension UIViewController {
   static var topmostViewController: UIViewController? {
       return UIApplication.shared.keyWindow?.topmostViewController
   }
   var topmostViewController: UIViewController? {
       return presentedViewController?.topmostViewController ?? self
   }
}
extension UINavigationController {
   override var topmostViewController: UIViewController? {
       return visibleViewController?.topmostViewController
   }
}
extension UITabBarController {
   override var topmostViewController: UIViewController? {
       return selectedViewController?.topmostViewController
   }
}
extension UIWindow {
   var topmostViewController: UIViewController? {
       return rootViewController?.topmostViewController
   }
}

Leonardo Cardoso wrote the extensions above. Leonardo Cardoso写了上面的扩展。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM