在UITextField之外的任何地方触摸键盘

时间:2012-06-30 12:36:48

标签: ios cocoa-touch uitextfield

我正在开发一款iPad应用,其中包含大量UIViewControllersUITableViewsaccessoryViewsUITextFields的单元格等等。许多{ {1}}出现在导航层次结构中。

UIViewControllers出现在很多不同的地方,包括UITextFields UITableViewCell

我想设计一种有效的策略,在用户触摸当前正在编辑的accessoryViews之外时解除键盘。我已经搜索了键盘解雇技术,但还没有找到解释一般键盘解雇策略如何工作的答案。

例如,我喜欢这种方法,其中以下代码被添加到任何ViewController:

UITextField

...但是这种技术并不涉及例如在显示的- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@"* * * * * * * * *ViewControllerBase touchesBegan"); [self.view endEditing:YES]; // dismiss the keyboard [super touchesBegan:touches withEvent:event]; } 内发生触摸的情况。所以,当触摸UITableView等等时,我需要添加一些代码来调用endEditing。这意味着我的应用程序会随处散布大量代码以解除其他各种键盘感动UITableView

我想我可以尝试识别触摸需要截取的所有不同位置以及键盘被解雇,但在我看来,可能有一个更好的设计模式用于处理iOS键盘解雇事件。

任何人都可以在这个问题上分享他们的经验,并推荐一种特定的技术,用于在整个应用程序中一般性地处理键盘解雇吗?

非常感谢

8 个答案:

答案 0 :(得分:26)

您的视图层次结构位于UIWindow内。 UIWindow负责将触摸事件转发到其sendEvent:方法中的正确视图。让我们创建UIWindow的子类来覆盖sendEvent:

@interface MyWindow : UIWindow
@end

窗口将需要对当前第一响应者的引用(如果有)。您可能还决定使用UITextView,因此我们会观察来自文字字段和文字视图的通知。

@implementation MyWindow {
    UIView *currentFirstResponder_;
}

- (void)startObservingFirstResponder {
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(observeBeginEditing:) name:UITextFieldTextDidBeginEditingNotification object:nil];
    [center addObserver:self selector:@selector(observeEndEditing:) name:UITextFieldTextDidEndEditingNotification object:nil];
    [center addObserver:self selector:@selector(observeBeginEditing:) name:UITextViewTextDidBeginEditingNotification object:nil];
    [center addObserver:self selector:@selector(observeEndEditing:) name:UITextViewTextDidEndEditingNotification object:nil];
}

- (void)stopObservingFirstResponder {
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center removeObserver:self name:UITextFieldTextDidBeginEditingNotification object:nil];
    [center removeObserver:self name:UITextFieldTextDidEndEditingNotification object:nil];
    [center removeObserver:self name:UITextViewTextDidBeginEditingNotification object:nil];
    [center removeObserver:self name:UITextViewTextDidEndEditingNotification object:nil];
}

- (void)observeBeginEditing:(NSNotification *)note {
    currentFirstResponder_ = note.object;
}

- (void)observeEndEditing:(NSNotification *)note {
    if (currentFirstResponder_ == note.object) {
        currentFirstResponder_ = nil;
    }
}

窗口将在初始化时开始观察通知,并在取消分配时停止:

- (id)initWithCoder:(NSCoder *)aDecoder {
    if ((self = [super initWithCoder:aDecoder])) {
        [self commonInit];
    }
    return self;
}

- (id)initWithFrame:(CGRect)frame {
    if ((self = [super initWithFrame:frame])) {
        [self commonInit];
    }
    return self;
}

- (void)commonInit {
    [self startObservingFirstResponder];
}

- (void)dealloc {
    [self stopObservingFirstResponder];
}

我们将覆盖sendEvent:根据事件“调整”第一个响应者,然后调用super sendEvent:来正常发送事件。

- (void)sendEvent:(UIEvent *)event {
    [self adjustFirstResponderForEvent:event];
    [super sendEvent:event];
}

如果没有第一响应者,我们不需要对第一响应者做任何事情。如果有第一响应者,并且它包含触摸,我们不想强迫它辞职。 (请记住,可以同时进行多次触摸!)如果有第一个响应者,并且新触摸出现在可以成为第一个响应者的另一个视图中,系统将自动正确处理,因此我们也想忽略该情况。但是如果有第一个响应者,并且它不包含任何触摸,并且在不能成为第一响应者的视图中出现新的触摸,我们希望让第一个响应者辞职。 / p>

- (void)adjustFirstResponderForEvent:(UIEvent *)event {
    if (currentFirstResponder_
        && ![self eventContainsTouchInFirstResponder:event]
        && [self eventContainsNewTouchInNonresponder:event]) {
        [currentFirstResponder_ resignFirstResponder];
    }
}

报告事件是否在第一响应者中包含触摸很容易:

- (BOOL)eventContainsTouchInFirstResponder:(UIEvent *)event {
    for (UITouch *touch in [event touchesForWindow:self]) {
        if (touch.view == currentFirstResponder_)
            return YES;
    }
    return NO;
}

报告一个事件是否包含无法成为第一响应者的视图中的新触摸几乎一样容易:

- (BOOL)eventContainsNewTouchInNonresponder:(UIEvent *)event {
    for (UITouch *touch in [event touchesForWindow:self]) {
        if (touch.phase == UITouchPhaseBegan && ![touch.view canBecomeFirstResponder])
            return YES;
    }
    return NO;
}

@end

实施此课程后,您需要更改自己的应用,而不是使用UIWindow

如果您要在UIWindow中创建application:didFinishLaunchingWithOptions:,则需要#import "MyWindow.h"顶部的AppDelegate.m,然后更改application:didFinishLaunchingWithOptions:以创建MyWindow代替UIWindow

如果您在笔尖中创建UIWindow,则需要将窗口的自定义类设置为笔尖中的MyWindow

答案 1 :(得分:23)

这是一种更容易,更有效的方法。这适用于视图控制器中的任何UITextField。你甚至可以将它添加到你的基本视图控制器(如果你有一个),它将像一个魅力。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    UITouch *touch = [[event allTouches] anyObject];

    if (![[touch view] isKindOfClass:[UITextField class]]) {
        [self.view endEditing:YES];
    }
    [super touchesBegan:touches withEvent:event];
}

答案 2 :(得分:3)

我通常使用以下

首先:

在您的实施文件中加入以下扩展名,findFirstResponder将帮助您找到第一个谐振器

@implementation UIView (FindFirstResponder)
- (UIView*)findFirstResponder
{
    if (self.isFirstResponder) {
        return self;     
    }
    for (UIView *subView in self.subviews) {
        UIView *responder = [subView findFirstResponder];
        if (responder)
            return responder;
    }

    return nil;
}
@end

然后在您的视图控制器viewDidLoad中添加以下内容

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(keyboardDidShow:) 
                   name:UIKeyboardDidShowNotification 
                 object:nil];

    [center addObserver:self selector:@selector(keyboardDidHide:) 
                   name:UIKeyboardWillHideNotification 
                 object:nil];
    // Do any additional setup after loading the view, typically from a nib.
}

通知功能将如下

- (void) keyboardDidShow:(NSNotification*)notification
{    
    UIButton *button = [[UIButton alloc] init];

    CGRect rect = self.view.bounds;


    button.frame = rect;
    button.backgroundColor = [UIColor blackColor];
    button.tag = 111;
    UIView *currentResponer = [self.view findFirstResponder];
    [button addTarget:currentResponer action:@selector(resignFirstResponder) forControlEvents:UIControlEventTouchUpInside];

    [self.view insertSubview:button belowSubview:currentResponer];
}

- (void) keyboardDidHide:(NSNotification*)notification
{
    [[self.view viewWithTag:111] removeFromSuperview];
}

当键盘显示时,我在当前第一响应者下方添加UIButton,此按钮操作将隐藏键盘,

这里的一个限制是UITextField必须在self.view但是您可以根据需要调整此技术并进行一些修改,希望它可以帮助您

答案 3 :(得分:2)

我其实非常喜欢奥马尔的技术 - 它运作得非常漂亮 - 但我在案例中添加了几行。

- (void)keyboardDidShow:(NSNotification*)notification
{
    UIButton *button = [[UIButton alloc] init];

    CGRect rect = self.view.bounds;

    button.frame = rect;
    button.backgroundColor = [UIColor blackColor];

    [button setAlpha:0.5f];
    button.tag = 111;
    UIView *currentResponder = [self.view findFirstResponder];

    [self.view bringSubviewToFront:currentResponder];

    if (currentResponder == _descriptionTextView) [_descriptionTextView setTextColor:[UIColor whiteColor]];

    [button addTarget:currentResponder action:@selector(resignFirstResponder) forControlEvents:UIControlEventTouchUpInside];

    [self.view insertSubview:button belowSubview:currentResponder];
}

我在他的解决方案中添加了三行(见上文):

  1. 将按钮的alpha设置为0.6,这样我就可以看到我的一些 它背后的viewController。
  2. 将当前的应答者带到前面, 所以我的其他观点都不在按钮前面。
  3. 切换我的UITextView的颜色,使文字更清晰(我的 UITextView具有清晰的背景,因此文本没有显示 清楚)。
  4. 无论如何,只是微小的变化,但希望这些有所帮助。谢谢奥马尔。

答案 4 :(得分:1)

为了辞职当前的第一响应者,我尝试使用endEditing,但之后设置了第一个响应者有一些问题。然后我使用递归方法一段时间在UIView类别方法中找到第一个响应者:

- (UIView *)getFirstResponder {

    if (self.isFirstResponder) {
        return self;
    }

    for (UIView *subView in self.subviews) {
        UIView *firstResponder = [subView getFirstResponder];

        if (firstResponder != nil) {
            return firstResponder;
        }
    }

    return nil;
}

一旦回复了响应者,我就可以在上面调用resignFirstResponder。

就在今天,我的一位朋友在工作中向我展示了一种更好的方式:http://overooped.com/post/28510603744/i-had-completely-forgotten-about-nil-targeted

请致电:

[[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder) to:nil from:nil forEvent:nil];

很棒的一个班轮!

答案 5 :(得分:1)

在Swift 3中

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

    self.view.endEditing(true)
}

答案 6 :(得分:0)

可能我不理解你想要什么,但- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch方法可以帮助你解雇键盘

答案 7 :(得分:0)

添加&#34; textFieldDidChange&#34;文本字段控件的通知方法。

  

[textField addTarget:self action:@selector(textFieldDidChange :) forControlEvents:UIControlEventEditingDidEnd];

适合我