不用私有API,一行代码获取当前响应链的First Responder

前言

在iOS中,当发生事件响应时,必须知道由谁来响应事件。而UIResponder类就是专门用来响应用户的操作,处理各种事件的,包括触摸事件(Touch Events)、运动事件(Motion Events)和远程控制事件(Remote Control Events)。iOS处理事件的流程将遵循一个不同对象组成的层次结构,也就是响应者链(Responder Chain),网上目前有很多关于响应者链的介绍,这里就不再细讲。在响应者链中非常重要的一个概念就是第一响应者(First Responder),当前第一响应者负责响应事件,或将事件传递给下一响应者。

在编写iOS程序时,我们经常会遇到需要获取当前的第一响应者,例如系统弹出键盘时,我们希望得到当前输入框(也就是第一响应者)的Frame,从而调整视图避免键盘遮挡输入框。然而UIKit并没有提供官方的API专门用于该用途。本文将介绍一种非常简单的且未用到私有API的方法来获取当前第一响应者。

实现思路

常规思路

通过遍历当前UIWindow的所有子视图,从而找到当前的第一响应者。这种方法首先需要做非常多的递归调用,从而判断所有子视图,同时当前响应链上的第一响应者还有可能是子视图的ViewController,这种方法也会漏掉。

使用私有API的思路

使用苹果的私有API可以很容易地解决这个问题,然而苹果不会允许使用私有API的App上架App Store,而且私有API很有可能随时变化,所以这种方式也很不完美。

UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIView *firstResponder = [keyWindow performSelector:@selector(firstResponder)];

本文的思路

本文的思路用到的核心Public API就是

- (BOOL)sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event

苹果文档对该API的target参数的描述如下:

The object to receive the action message. If target is nil, the app sends the message to the first responder, from whence it progresses up the responder chain until it is handled.

从而可知,利用该API,只要将传入的target设置为nil,则系统会自动顺着响应链查找能够响应action的响应者。我们只需让所有UIResponder的子类都响应我们自定义的action,即可知道当前第一响应者是哪个对象。

实现方法

为实现本文的思路,我们需要为UIResponder提供一个Category(objc)或者Extension(swift)。

Objective-C

// UIResponder+WTYFirstResponder.h
#import <UIKit/UIKit.h>

@interface UIResponder (WTYFirstResponder)
//使用时只需要对UIResponder类调用该类方法即可获得当前第一响应者
+ (id)wty_currentFirstResponder;
@end

//  UIResponder+WTYFirstResponder.m
#import "UIResponder+WTYFirstResponder.h"

static __weak id wty_currentFirstResponder;

@implementation UIResponder (WTYFirstResponder)
+ (id)wty_currentFirstResponder {
    wty_currentFirstResponder = nil;
    // 通过将target设置为nil,让系统自动遍历响应链
    // 从而响应链当前第一响应者响应我们自定义的方法
    [[UIApplication sharedApplication] sendAction:@selector(wty_findFirstResponder:) 
                                               to:nil 
                                             from:nil 
                                         forEvent:nil];
    return wty_currentFirstResponder;
}
- (void)wty_findFirstResponder:(id)sender {
    // 第一响应者会响应这个方法,并且将静态变量wty_currentFirstResponder设置为自己
    wty_currentFirstResponder = self;
}
@end

使用方法

#import "UIResponder+WTYFirstResponder.h"

id firstResponder = [UIResponder wty_firstResponder];

Swift

//  UIResponder+WTYFirstResponder.swift

import UIKit

private weak var wty_currentFirstResponder: AnyObject?

extension UIResponder {

    static func wty_firstResponder() -> AnyObject? {
        wty_currentFirstResponder = nil
        // 通过将target设置为nil,让系统自动遍历响应链
        // 从而响应链当前第一响应者响应我们自定义的方法
        UIApplication.shared.sendAction(#selector(wty_findFirstResponder(_:)), to: nil, from: nil, for: nil)
        return wty_currentFirstResponder
    }
    
    func wty_findFirstResponder(_ sender: AnyObject) {
        // 第一响应者会响应这个方法,并且将静态变量wty_currentFirstResponder设置为自己
        wty_currentFirstResponder = self
    }
}

使用方法

firstResponder = UIResponder.wty_firstResponder()

思路衍生

如果只希望让第一响应者取消其第一响应者的状态,则可以做如下操作:

Objective-C

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

Swift

 UIApplication.shared.sendAction(#selector(resignFirstResponder), to: nil, from: nil, for: nil)

Github Repo

本文所用的代码可以在我的Github上找到,如果觉得好用的话请并忙点个星星。


本文个人博客地址: http://wty.im/2016/09/22/get-the-first-responder/
Github: https://github.com/wty21cn/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 一. Hit-Testing 什么是Hit-Testing?对于触摸事件, window首先会尝试将事件交给事件触...
    面糊阅读 807评论 0 50
  • 好奇触摸事件是如何从屏幕转移到APP内的?困惑于Cell怎么突然不能点击了?纠结于如何实现这个奇葩响应需求?亦或是...
    Lotheve阅读 56,210评论 51 598
  • 在iOS开发中经常会涉及到触摸事件。本想自己总结一下,但是遇到了这篇文章,感觉总结的已经很到位,特此转载。作者:L...
    WQ_UESTC阅读 5,980评论 4 26
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,044评论 25 707
  • 作为一个简书新人,来谈如何上首页似乎有些大言不惭,在这里,我只是想就这段时间看过的首页热门文章,来谈一下自己的小看...
    一之鱼阅读 360评论 2 12