1. UIResonder
對於C#裡所有的控制項(例如TextBox),都繼承於Control類。而Control類的繼承關係如
下:
複製代碼 代碼如下:
System.Object
System.MarshalByRefObject
System.ComponentModel.Component
System.Windows.Forms.Control
對於iOS裡的UI類,也有類似的繼承關係。
例如對於UITextField,繼承於UIControl;UIControl繼承於UIView,UIView繼承於UIRe
sponder,UIResponder繼承於NSObject。
具體架構可以參見:
http://developer.apple.com/library/ios/#documentation/general/conceptual/Devp
edia-CocoaApp/Responder.html
UIResponder是UIKit架構中的類(Mac OS X Cocoa對應的是AppKit架構)。
2. 第一響應對象
在應用的響應對象裡,會有一個成為第一響應對象。
第一響應對象和其他響應對象之間有什麼區別?對於普通的觸摸事件沒什麼區別。就算
我把一個按鈕設定成第一響應對象,當我點擊其他按鈕時,還是會響應其他按鈕,而不
會優先響應第一響應對象。
第一響應對象的區別在於負責處理那些和螢幕位置無關的事件,例如搖動。
蘋果官方文檔的說法是:第一響應對象是視窗中,應用程式認為最適合處理事件的對象
。
一個班只能有一個班長,應用的響應對象中,只能有一個響應對象成為第一響應對象。
3. 成為與取消第一響應對象。
要當第一響應對象,還需要有View來毛遂自薦:
複製代碼 代碼如下:
- (BOOL) canBecomeFirstResponder
{
returnYES;
}
如果缺少了這段,就算用[view becomeFirstResponder]也不能讓一個view成為第一響應
對象。。。強扭的瓜不甜?好吧不是這個原因。大多數視圖預設只關心與自己有關聯的
事件,並且(幾乎)總是有機會來處理這些事件。以UIButton為例,當使用者單擊某個UIB
utton對象時,無論當前的第一響應對象是哪個視圖,該對象都會收到指定的動作訊息。
當上第一響應對象吃力不討好麼。。。所以只能由某個UIResponder明確表示自己願意成
為第一響應對象才行。(我不知道設計上是基於什麼考慮。。。安全?)
在當上第一響應對象時,不同對象可能會有一些特殊的表現。例如UITextField當上的時
候,就會調出一塊小鍵盤。
第一響應對象也有可能被辭退。發送一個resignFirstResponder,就可以勸退。
4. 第一響應對象的任務
剛才說了第一響應對象可以處理搖動。就來看個範例吧:
複製代碼 代碼如下:
- (void) motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if(motion == UIEventSubtypeMotionShake)
{
NSLog(@"Device is beginning to shake");
[selfsetCircleColor:[UIColorredColor]];
[selfsetNeedsDisplay];
}
}
當搖動開始時觸發某些行為。
5. 擷取當前第一響應對象
源自這篇討論:http://stackoverflow.com/questions/1823317/get-the-current-firs
t-responder-without-using-a-private-api
提問的傢伙用了如下的方式來擷取
複製代碼 代碼如下:
UIView *firstResponder = [keyWindow
performSelector:@selector(firstResponder)];
結果被蘋果打回來,說用了非公開的API。。。
於是這傢伙只好苦逼地用遞迴了:
複製代碼 代碼如下:
implementationUIView (FindFirstResponder)
- (UIView *)findFirstResponder
{
if (self.isFirstResponder) {
return self;
}
for (UIView *subView in self.subviews) {
UIView *firstResponder = [subView findFirstResponder];
if (firstResponder != nil) {
return firstResponder;
}
}
return nil;
}
@end
6.View的FirstResponder的釋放問題
今天遇到一個問題,當我隱藏掉一個正在接受使用者輸入的UITextField的時候,鍵盤並不會消失,而且鍵盤仍然接受使用者輸入,再次顯示該TextField時候發現在隱藏狀態下,所有的輸入仍然傳輸到了該TextField中,於是查下官方資料找到如下解釋:
Important If you hide a view that is currently the first responder, the view does not automatically resign its first responder status. Events targeted at the first responder are still delivered to the hidden view. To prevent this from happening, you should force your view to resign the first responder status when you hide it.
意思是如果這個View是當前的第一響應者的時候,隱藏該View並不會自動放棄其第一響應者的身份,而且會繼續以第一響應者的身份接受訊息。我們可以通過在隱藏View之前,手動調用resignFirstResponder來強制該view放棄第一響應者身份。
下面請看小例子:
複製代碼 代碼如下:
SvTestFirstResponder.h
//
// SvTestFirstResponder.h
//
// Created by maple on 3/15/12.
// Copyright (c) 2012 SmileEvday. All rights reserved.
//
// 當一個view時當前響應者時,調用其hidden方法並不會自動放棄第一響應者身份,所有的訊息仍然會發送到這個view
// 可以通過在hidden前強制放棄第一響應者,恢複正常的訊息傳遞
//
#import <UIKit/UIKit.h>
@interface SvTestFirstResponder : UIView {
UITextField *_inputField;
}
@end
複製代碼 代碼如下:
SvTestFirstResponder.m
//
// SvTestFirstResponder.m
//
// Created by maple on 3/15/12.
// Copyright (c) 2012 SmileEvday. All rights reserved.
//
#import "SvTestFirstResponder.h"
@interface SvTestFirstResponder()
- (void)hiddenInputView:(id)sender;
- (void)showInputView:(id)sender;
@end
@implementation SvTestFirstResponder
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
_inputField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 200, 50)];
_inputField.center = CGPointMake(160, 50);
[_inputField setFont:[UIFont systemFontOfSize:24]];
_inputField.text = @"input you text";
_inputField.clearsOnBeginEditing = YES;
_inputField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
_inputField.borderStyle = UITextBorderStyleRoundedRect;
[self addSubview:_inputField];
_inputField.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
UIButton *hiddenBtn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
hiddenBtn.frame = CGRectMake(0, 0, 115, 40);
hiddenBtn.center = CGPointMake(80, 110);
[hiddenBtn setTitle:@"Hide TextField" forState:UIControlStateNormal];
[hiddenBtn addTarget:self action:@selector(hiddenInputView:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:hiddenBtn];
hiddenBtn.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
hiddenBtn.titleLabel.lineBreakMode = UILineBreakModeTailTruncation;
UIButton *showBtn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
showBtn.frame = CGRectMake(0, 0, 115, 40);
showBtn.center = CGPointMake(240, 110);
[showBtn setTitle:@"Show TextField" forState:UIControlStateNormal];
[showBtn addTarget:self action:@selector(showInputView:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:showBtn];
showBtn.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
showBtn.titleLabel.lineBreakMode = UILineBreakModeTailTruncation;
}
return self;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
- (void)hiddenInputView:(id)sender
{
_inputField.hidden = YES;
}
- (void)showInputView:(id)sender
{
_inputField.hidden = NO;
}
@end
這個簡單的例子中,當輸入框進入接受使用者輸入狀態的時候,點擊hide按鈕,鍵盤並不會消失而且會繼續接收使用者輸入並且將使用者輸入傳到TextField中去,後面再點擊Show按鈕的時候你會發現所有在隱藏狀態下輸入的文字都已經成功的被接收。我們可以修改hide方法如下:
複製代碼 代碼如下:
- (void)hiddenInputView:(id)sender
{
if (_inputField.isFirstResponder) {
[_inputField resignFirstResponder];
}
_inputField.hidden = YES;
}
這樣就可以在隱藏之前強制釋放第一響應者身份,這個問題比較細節,但有時候可能就是這種細節問題導致一些莫名奇妙的問題,在隱藏一些可能成為第一響應者的view之前添加強制釋放第一響應者身份,可能會幫我們避免一些奇怪的問題,而且也幾乎不會有什麼開銷,何樂而不為呢。