27

我正在开发一个 iPad 应用程序,该应用程序具有大量UIViewControllers, UITableViews(带有accessoryViewsof的单元格UITextFields)等。其中许多UIViewControllers出现在导航层次结构中。

有许多不同的地方UITextFields出现,包括 as UITableViewCell accessoryViews

我想设计一种有效的策略来在用户触摸UITextField当前正在编辑的外部时关闭键盘。我搜索了键盘关闭技术,但尚未找到解释一般键盘关闭策略如何工作的答案。

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

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"* * * * * * * * *ViewControllerBase touchesBegan");

    [self.view endEditing:YES]; // dismiss the keyboard

    [super touchesBegan:touches withEvent:event];
}

...但是这种技术不能处理例如触摸发生在UITableView正在显示的 a 内的情况。因此,我需要添加一些代码以在触摸endEditinga 时调用UITableView等等。这意味着我的应用程序将大量撒上大量代码,以在UIElements触摸其他各种键盘时关闭键盘。

我想我可以尝试找出需要拦截触摸和关闭键盘的所有不同位置,但在我看来,可能有更好的设计模式来处理 iOS 键盘关闭事件。

任何人都可以分享他们在这件事上的经验,并推荐一种特定的技术来通用处理整个应用程序中的键盘关闭吗?

非常感谢

4

8 回答 8

26

您的视图层次结构位于UIWindow. UIWindow负责在其方法中将触摸事件转发到正确的视图sendEvent:。让我们创建一个UIWindowto override的子类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:以根据事件“调整”第一响应者,然后调用 supersendEvent:以正常发送事件。

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

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

- (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.

如果您正在创建UIWindowin application:didFinishLaunchingWithOptions:,则需要#import "MyWindow.h"在您的顶部AppDelegate.m,然后更改application:didFinishLaunchingWithOptions:为创建 aMyWindow而不是 a UIWindow

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

于 2012-06-30T18:39:13.660 回答
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];
}
于 2013-08-21T07:30:59.327 回答
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但是您可以通过一些修改来适应这种技术以满足您的需要,希望它可以帮助您

于 2012-06-30T12:59:02.780 回答
2

我实际上真的很喜欢 Omar 的技术——它工作得很好——但我在我的例子中添加了几行。

- (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. 将 currentResponder 放在前面,这样我的其他视图都不会出现在按钮前面。
  3. 切换我的 UITextView 的颜色,使文本更清晰(我的 UITextView 具有清晰的背景,因此文本显示不清晰)。

无论如何,只有微小的变化,但希望这些有所帮助。谢谢奥马尔。

于 2013-01-08T13:41:46.713 回答
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];

很棒的一个班轮!

于 2013-11-07T16:01:58.350 回答
1

在斯威夫特 3

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

    self.view.endEditing(true)
}
于 2017-07-13T07:22:49.413 回答
0

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

于 2012-06-30T13:30:15.710 回答
0

向文本字段控件添加“textFieldDidChange”通知方法。

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

为我工作

于 2014-07-16T18:45:06.500 回答