Touch Events and UIResponder

For the next three chapters, you are going to step away from Homepwner and build a new application
named TouchTracker to learn more about touch events and gestures, as well as debugging applications.
In this chapter, you will create a view that lets the user draw lines by dragging across the view

(Figure 12.1). Using multi-touch, the user will be able to draw more than one line at a time.

在下面三章中,你会创建一个TouchTracker 应用,学习touch事件和手势以及学习如果调试应用。

在这一章中,你会通过drag在view上画线。

Touch Events and UIResponder_第1张图片


Touch Events
As a subclass of UIResponder, a UIView can override four methods to handle the four distinct touch
events:
• a finger or fingers touches the screen
- (void)touchesBegan:(NSSet *)touches
withEvent:(UIEvent *)event;
• a finger or fingers moves across the screen (this message is sent repeatedly as a finger moves)
- (void)touchesMoved:(NSSet *)touches
withEvent:(UIEvent *)event;

• a finger or fingers is removed from the screen
- (void)touchesEnded:(NSSet *)touches
withEvent:(UIEvent *)event;
• a system event, like an incoming phone call, interrupts a touch before it ends
- (void)touchesCancelled:(NSSet *)touches
withEvent:(UIEvent *)event;

作为UIResponder的子类,UIView 可以重载处理四个事件相关的方法。touch start, touch end, touch move, touch cancel;


When a finger touches the screen, an instance of UITouch is created. The UIView that this finger
touched is sent the message touchesBegan:withEvent: and the UITouch is in the NSSet of touches.

当手指触摸屏幕的时候,就创建了一个UITouch实例。接着相对应的UIView 就会收到touchesBegan:withEvent: 消息,

并且所创建的UITouch会加入到一个NSSet的容器中,作为上述消息的参数。


As that finger moves around the screen, the touch object is updated to contain the current location
of the finger on the screen. Then, the same UIView that the touch began on is sent the message
touchesMoved:withEvent:. The NSSet that is passed as an argument to this method contains the same
UITouch that originally was created when the finger it represents touched the screen.

当手指移动的时候,这个UITouch对象会更新当前手指的位置信息。同样相对应的UIView 会收到touchesMoved:withEvent:. 消息,

它的参数NSSet有之前在手指触摸屏幕就创建的UITouch 对象。


When a finger is removed from the screen, the touch object is updated one last time to contain
the current location of the finger, and the view that the touch began on is sent the message
touchesEnded:withEvent:. After that method finishes executing, the UITouch object is destroyed.

当手指移开屏幕的时候,UITouch对象会更新位置到最后一次在屏幕的时候,并且会发送touchesEnded:withEvent:. 事件,

最后当这个方法执行完毕,UITouch对象被销毁。


From this information, we can draw a few conclusions about how touch objects work:

根据上述信息,你可以得到以下几点结论:
• One UITouch corresponds to one finger on the screen. This touch object lives as long as the finger
is on the screen and always contains the current position of the finger on the screen.

一个UITouch对象对应一个在屏幕中的手指。这个对象会一直存活到手指移开屏幕,并且会一直更新当前的位置信息。


• The view that the finger started on will receive every touch event message for that finger no matter
what. If the finger moves outside of the UIView’s frame that it began on, that view still receives
the touchesMoved:withEvent: and touchesEnded:withEvent: messages. Thus, if a touch begins
on a view, then that view owns the touch for the life of the touch.

收到事件开始的那个view会一直收到之后的所有信息;即使手指移到view之外,这个view还是会收到

touchesMoved:withEvent:和touchesEnded:withEvent: 消息。


• You do not have to – nor should you ever – keep a reference to a UITouch object. The application
will give you access to a touch object when it changes state.

你不应该保存UITouch对象,而是只是在相关的事件中访问它。


Every time a touch does something, like begins, moves, or ends, a touch event is added to a queue of
events that the UIApplication object manages. In practice, the queue rarely fills up, and events are
delivered immediately. The delivery of these touch events involves sending one of the UIResponder
messages to the view that owns the touch. (If your touches are sluggish, then one of your methods
is hogging the CPU, and events are waiting in line to be delivered. Chapter 14 will show you how to
catch these problems.)

touch行为对应的事件会被添加到UIApplication管理的事件队列中。在实践中,这个队列几乎不可能填满,

事件的分发速度非常快。这些事件分发的时候,都会发送对应UIResponder的方法。(如果你的事件比较慢,

那么你其中的一个方法会独占cpu,而等待的事件会被堵塞,14章有介绍如果catch这些问题)


What about multiple touches? If multiple fingers do the same thing at the exact same time to the
same view, all of these touch events are delivered at once. Each touch object – one for each finger – is
included in the NSSet passed as an argument in the UIResponder messages. However, the window of
opportunity for the “exact same time” is fairly short. So, instead of one responder message with all of
the touches, there are usually multiple responder messages with one or more of the touches.

如果是多指触控,那么就会创建多个UITouch对象,并且发送的UIResponder消息中传递的NSSet参数也会包含多个

UITouch对象。


Creating the TouchTracker Application
Now let’s get started with your application. In Xcode, create a new Empty Application iPhone project
and name it TouchTracker. The class prefix should be the same as the other projects, BNR (Figure 12.2).

创建一个新的TouchTracker项目;



Touch Events and UIResponder_第2张图片



First, you will need a model object that describes a line. Create a new subclass of NSObject and name
it BNRLine. In BNRLine.h, declare two CGPoint properties:
#import <Foundation/Foundation.h>
@interface BNRLine : NSObject
@property (nonatomic) CGPoint begin;
@property (nonatomic) CGPoint end;
@end
Next, create a new NSObject subclass called BNRDrawView. In BNRDrawView.h, change the superclass to
UIView.
#import <Foundation/Foundation.h>
@interface BNRDrawView : UIView
@end
Now you need a view controller to manage an instance of BNRDrawView in TouchTracker. Create a
new NSObject subclass named BNRDrawViewController. In BNRDrawViewController.h, change the
superclass to UIViewController.
@interface BNRDrawViewController : UIViewController
In BNRDrawViewController.m, override loadView to set up an instance of BNRDrawView as
BNRDrawViewController’s view. Make sure to import the header file for BNRDrawView at the top of this
file.
#import "BNRDrawViewController.h"
#import "BNRDrawView.h"
@implementation BNRDrawViewController
- (void)loadView
{
self.view = [[BNRDrawView alloc] initWithFrame:CGRectZero];
}

@end

创建Model,view和viewController;

重载BNRDrawViewController的loadview方法,用于创建view.




In BNRAppDelegate.m, create an instance of BNRDrawViewController and set it as
the rootViewController of the window. Do not forget to import the header file for
BNRDrawViewController in this file.
#import "BNRAppDelegate.h"
#import "BNRDrawViewController.h"
@implementation BNRAppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch
BNRDrawViewController *dvc = [[BNRDrawViewController alloc] init];
self.window.rootViewController = dvc;
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];

}

在didFinishLaunchingWithOptions中创建BNRDrawViewController 实例,并设定位window的rootViewController;

Touch Events and UIResponder_第3张图片


Drawing with BNRDrawView
BNRDrawView will keep track of all of the lines that have been drawn and the line that is currently being
drawn. In BNRDrawView.m, create two instance variables in the class extension that will hold the lines in
their two states. Make sure to import BNRLine.h and implement initWithFrame:.

BNRDrawView 负责保存所有绘制过的线以及当前正在会在的数据。

#import "BNRDrawView.h"
#import "BNRLine.h"
@interface BNRDrawView ()
@property (nonatomic, strong) BNRLine *currentLine;
@property (nonatomic, strong) NSMutableArray *finishedLines;
@end
@implementation BNRDrawView
- (instancetype)initWithFrame:(CGRect)r
{
self = [super initWithFrame:r];
if (self) {
self.finishedLines = [[NSMutableArray alloc] init];
self.backgroundColor = [UIColor grayColor];
}
return self;
}
We will get to how lines are created in a moment, but in order to test that the line creation code is
written correctly, you need the BNRDrawView to be able to draw lines.

实现划线代码;


In BNRDrawView.m, implement drawRect: to draw the current and finished lines.
- (void)strokeLine:(BNRLine *)line
{
UIBezierPath *bp = [UIBezierPath bezierPath];
bp.lineWidth = 10;
bp.lineCapStyle = kCGLineCapRound;
[bp moveToPoint:line.begin];
[bp addLineToPoint:line.end];
[bp stroke];
}
- (void)drawRect:(CGRect)rect
{
// Draw finished lines in black
[[UIColor blackColor] set];
for (BNRLine *line in self.finishedLines) {
[self strokeLine:line];
}
if (self.currentLine) {
// If there is a line currently being drawn, do it in red
[[UIColor redColor] set];
[self strokeLine:self.currentLine];
}
}

画出current和finish线段。


Turning Touches into Lines
A line is defined by two points. Your BNRLine stores these points as properties named begin and end.
When a touch begins, you will create a line and set both begin and end to the point where the touch
began. When the touch moves, you will update end. When the touch ends, you will have your complete
line.
In BNRDrawView.m, implement touchesBegan:withEvent: to create a new line.
- (void)touchesBegan:(NSSet *)touches
withEvent:(UIEvent *)event
{
UITouch *t = [touches anyObject];
// Get location of the touch in view's coordinate system
CGPoint location = [t locationInView:self]; //获取对应坐标系的坐标点,这里是相对于自己
self.currentLine = [[BNRLine alloc] init];
self.currentLine.begin = location;
self.currentLine.end = location;
[self setNeedsDisplay];
}


Then, in BNRDrawView.m, implement touchesMoved:withEvent: so that it updates the end of the
currentLine.
- (void)touchesMoved:(NSSet *)touches
withEvent:(UIEvent *)event
{
UITouch *t = [touches anyObject];
CGPoint location = [t locationInView:self];
self.currentLine.end = location;
[self setNeedsDisplay];
}

//在移动的时候更新end;


Finally, in BNRDrawView.m, add the currentLine to the finishedLines when the touch ends.
- (void)touchesEnded:(NSSet *)touches
withEvent:(UIEvent *)event
{
[self.finishedLines addObject:self.currentLine];
self.currentLine = nil;
[self setNeedsDisplay];
}

在结束的时候将画的线加入到finishedLines容器中,并设置currentLine为nil;


Build and run the application and draw some lines on the screen. While you are drawing, the lines will
appear in red and once finished, they will appear in black.

编译并运行,就可以看到需要的结果了。


Handling multiple touches
When drawing lines, you may have noticed that having more than one finger on the screen does not do
anything – that is, you can only draw one line at a time. Let’s update BNRDrawView so that you can draw
as many lines as you can fit fingers on the screen.

更新BNRDrawView的代码,以实现画多条线。


By default, a view will only accept one touch at a time. If one finger has already triggered
touchesBegan:withEvent: but has not finished – and therefore has not triggered
touchesEnded:withEvent: – subsequent touches are ignored. In this context, “ignore” means that the
BNRDrawView will not be sent touchesBegan:withEvent: or any other UIResponder messages related
to the extra touches.

在默认的情况下,view并不会处理多指事件。当触发了touchesBegan:withEvent:,但还没触发touchesEnded:withEvent: 之前,

所有touchesBegan:withEvent:事件都不会被处理。


In BNRDrawView.m, enable BNRDrawView instances to accept multiple touches.
- (instancetype)initWithFrame:(CGRect)r
{
self = [super initWithFrame:r];
if (self) {
self.finishedLines = [[NSMutableArray alloc] init];
self.backgroundColor = [UIColor grayColor];
self.multipleTouchEnabled = YES;
}
return self;
}

打开multipleTouchEnabled;


Now that BNRDrawView will accept multiple touches, each time a finger touches the screen, moves, or
is removed from the screen, the view will receive the appropriate UIResponder message. However, this
now presents a problem: your UIResponder code assumes there will only be one touch active and one
line being drawn at a time.

这样你就可以处理多点触摸了,但是现在还有个问题,你的UIResponder 对象现在假定只有一个touch对象,

并且一次只有一条线被绘制。


Notice, first, that each touch handling method you have already implemented sends the message
anyObject to the NSSet of touches it receives. In a single-touch view, there will only ever be one
object in the set, so asking for any object will always give you the touch that triggered the event. In a
multiple touch view, that set could contain more than one touch.

注意到事件处理方法中的NSSet在单点触摸中只有一个UITouch对象,但如果是多点触控,那就会有多个UITouch对象。


Then, notice that there is only one property (currentLine) that hangs on to a line in progress.
Obviously, you will need to hold as many lines as there are touches currently on the screen. While you
could create a few more properties, like currentLine1 and currentLine2, you would have to go to
considerable lengths to manage which instance variable corresponds to which touch.

现在你只有一个currentLine属性用于处理单点触控,但很明显,你现在还多个current对象用于处理多点触控。

特别是要把current对象和touch事件联系起来。


Instead of the multiple property approach, you can use an NSMutableDictionary to hang on to each
BNRLine in progress. The key to store the line in the dictionary will be derived from the UITouch object
that the line corresponds to. As more touch events occur, you can use the same algorithm to derive
the key from the UITouch that triggered the event and use it to look up the appropriate BNRLine in the
dictionary.

所以你需要一个NSMutableDictionary 对象,并且是以UITouch 为key, BNRLine 为value,这样就可以通过

touch对象来获取BNRLine。



In BNRDrawView.m, add a new instance variable to replace the currentLine and instantiate it in
initWithFrame:.

@property (nonatomic, strong) NSMutableDictionary *linesInProgress;
@property (nonatomic, strong) NSMutableArray *finishedLines;
@end
@implementation BNRDrawView
- (instancetype)initWithFrame:(CGRect)r
{
self = [super initWithFrame:r];
if (self) {
self.linesInProgress = [[NSMutableDictionary alloc] init];
self.finishedLines = [[NSMutableArray alloc] init];
self.backgroundColor = [UIColor grayColor];
self.multipleTouchEnabled = YES;
}
return self;
}

创建一个NSMutableDictionary 类型对象。 


Now you need to update the UIResponder methods to add lines that are currently being drawn to this
dictionary. In BNRDrawView.m, update the code in touchesBegan:withEvent:.
- (void)touchesBegan:(NSSet *)touches
withEvent:(UIEvent *)event
{
// Let's put in a log statement to see the order of events
NSLog(@"%@", NSStringFromSelector(_cmd));
for (UITouch *t in touches) {
CGPoint location = [t locationInView:self];
BNRLine *line = [[BNRLine alloc] init];
line.begin = location;
line.end = location;
NSValue *key = [NSValue valueWithNonretainedObject:t];
self.linesInProgress[key] = line;
}

/** delete the code

UITouch *t = [touches anyObject];
CGPoint location = [t locationInView:self];
self.currentLine = [[BNRLine alloc] init];
self.currentLine.begin = location;
self.currentLine.end = location;

**/


[self setNeedsDisplay];
}

在touchesBegan中初始化NSMutableDictionary 


First, notice that you use fast enumeration to loop over all of the touches that began, because it is
possible that more than one touch can begin at the same time. (Although typically touches begin at

different times and BNRDrawView will receive multiple touchesBegan:withEvent: messages containing
each touch.)

要注意到,你现在使用(for in)快速遍历的方法处理多点触摸,因为有可能其中会有很多个UITouch对象。


Next, notice the use of valueWithNonretainedObject: to derive the key to store the BNRLine. This
method creates an NSValue instance that holds on to the address of the UITouch object that will be
associated with this line. Since a UITouch is created when a touch begins, updated throughout its
lifetime, and destroyed when the touch ends, the address of that object will be constant through each
touch event message.

因为在UITouch的整个生命周期中,这个对象会一直存在,所以使用它的地址来和一个BNRLine对象绑定。

Touch Events and UIResponder_第4张图片

Update touchesMoved:withEvent: in BNRDrawView.m so that it can look up the right BNRLine.

更新touchesMoved:withEvent:以处理多点触摸;


- (void)touchesMoved:(NSSet *)touches
withEvent:(UIEvent *)event
{
// Let's put in a log statement to see the order of events
NSLog(@"%@", NSStringFromSelector(_cmd));
for (UITouch *t in touches) {
NSValue *key = [NSValue valueWithNonretainedObject:t];
BNRLine *line = self.linesInProgress[key];
line.end = [t locationInView:self];
}


[self setNeedsDisplay];
}

通过valueWithNonretainedObject:t来找到对应的BNRItem对象,并更新数据。


Then, update touchesEnded:withEvent: to move any finished lines into the _finishedLines array.


- (void)touchesEnded:(NSSet *)touches
withEvent:(UIEvent *)event
{
// Let's put in a log statement to see the order of events
NSLog(@"%@", NSStringFromSelector(_cmd));
for (UITouch *t in touches) {
NSValue *key = [NSValue valueWithNonretainedObject:t];
BNRLine *line = self.linesInProgress[key];
[self.finishedLines addObject:line];
[self.linesInProgress removeObjectForKey:key];
}
[self.finishedLines addObject:self.currentLine];
self.currentLine nil;
[self setNeedsDisplay];
}

更新touchesEnded代码,从linesInProgress 中删除对应的UITouch对象。


Finally, update drawRect: to draw each line in _linesInProgress.
// Draw finished lines in black
[[UIColor blackColor] set];
for (BNRLine *line in self.finishedLines) {
[self strokeLine:line];
}
[[UIColor redColor] set];
for (NSValue *key in self.linesInProgress) {
[self strokeLine:self.linesInProgress[key]];
}

}


Build and run the application and start drawing lines with multiple fingers. (You can simulate multiple
fingers on the simulator by holding down the option key as you drag.)

这样你就可以处理多点触摸事件了。

You may be wondering: why not use the UITouch itself as the key? Why go through the hoop of
creating an NSValue? Objects used as keys in an NSDictionary must conform to the NSCopying
protocol, which allows them to be copied by sending the message copy. UITouch instances do not
conform to this protocol because it does not make sense for them to be copied. Thus, the NSValue
instances hold the address of the UITouch so that equal NSValue instances can be later created with the
same UITouch.

你可能会想为什么不直接使用UITouch 来作为key,而是绕了一圈创建一个NSValue对象。因为NSDictionary 的key一定要

实现NSCopying协议,但是UITouch对象并没有,因为它不知道自己是否能够被复制。


Also, you should know that when a UIResponder message like touchesMoved:withEvent: is sent to a
view, only the touches that have moved will be in the NSSet of touches. Thus, it is possible for three
touches to be on a view, but only one touch inside the set of touches passed into one of these methods
if the other two did not move. Additionally, once a UITouch begins on a view, all touch event messages
are sent to that same view over the touch’s lifetime, even if that touch moves off of the view it began
on.

另外在touchesMoved:withEvent: 有可能有一个UITouch对象,也有可能有多个,取决于是否触发move;

还有只有收到begin的view才能处理之后的事件,即使手指移除了view之外。(被grab了)


The last thing left for the basics of TouchTracker is to handle what happens when a touch is cancelled.
A touch can be cancelled when an application is interrupted by the operating system (for example, a
phone call comes in) when a touch is currently on the screen. When a touch is cancelled, any state it set
up should be reverted. In this case, you should remove any lines in progress.

此外还需要处理cancel事件,它有可能在你画线的时候,有个电话打进来。这时需要把UITouch从容器中移除。


In BNRDrawView.m, implement touchesCancelled:withEvent:.
- (void)touchesCancelled:(NSSet *)touches
withEvent:(UIEvent *)event
{
// Let's put in a log statement to see the order of events
NSLog(@"%@", NSStringFromSelector(_cmd));
for (UITouch *t in touches) {
NSValue *key = [NSValue valueWithNonretainedObject:t];
[self.linesInProgress removeObjectForKey:key];
}
[self setNeedsDisplay];
}

Bronze Challenge: Saving and Loading
Save the lines when the application terminates. Reload them when the application resumes.

在应用终止的时候保存线段,并在回来的时候重新加载线段。


Silver Challenge: Colors
Make it so the angle at which a line is drawn dictates its color once it has been added to
_finishedLines.



Gold Challenge: Circles
Use two fingers to draw circles. Try having each finger represent one corner of the bounding box
around the circle. You can simulate two fingers on the simulator by holding down the Option button.
(Hint: This is much easier if you track touches that are working on a circle in a separate dictionary.)

画弧线


For the More Curious: The Responder Chain
In Chapter 7, we talked briefly about UIResponder and the first responder. A UIResponder can receive
touch events. UIView is one example of a UIResponder subclass, but there are many others, including
UIViewController, UIApplication, and UIWindow. You are probably thinking, “But you can’t touch
a UIViewController. It’s not an on-screen object.” You are right – you cannot send a touch event
directly to a UIViewController, but view controllers can receive events through the responder chain.

虽然不能直接发送touch事件到UIViewController,但UIViewController仍旧可以通过responder链收到事件。


Every UIResponder has a pointer called nextResponder, and together these objects make up the
responder chain (Figure 12.5). A touch event starts at the view that was touched. The nextResponder
of a view is typically its UIViewController (if it has one) or its superview (if it does not). The
nextResponder of a view controller is typically its view’s superview. The top-most superview is the
window. The window’s nextResponder is the singleton instance of UIApplication.

首先是触摸的view先收到事件,接着是它的UIViewController,最后是UIApplication(因为它也是UIResponder 子类),

所有的UIResponder 对象构成了一条链。


Touch Events and UIResponder_第5张图片

(这个和android是有区别的,android是window先收到事件,最后是最底层的view收到)


How does a UIResponder not handle an event? It forwards the same message to its nextResponder.
That is what the default implementation of methods like touchesBegan:withEvent: do. So if a method
is not overridden, its next responder will attempt to handle the touch event. If the application (the last
object in the responder chain) does not handle the event, then it is discarded.

如果一个对象没有处理事件,那就会使用默认的方法处理,也就是直接传送给它的nextResponder对象。

如果最后一个UIResponder 没有处理事件,那么这个事件就被取消了。


You can explicitly send a message to a next responder, too. Let’s say there is a view that tracks touches,
but if a double tap occurs, its next responder should handle it. The code would look like this:

你也可以显示得指定UIResponder 对象处理事件,比如你可以在有2个触摸点的时候,把这个事件交给它的nextResponder

对象来处理。


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if (touch.tapCount == 2) {
[[self nextResponder] touchesBegan:touches withEvent:event];
return;
}
... Go on to handle touches that are not double taps
}


For the More Curious: UIControl
The class UIControl is the superclass for several classes in Cocoa Touch, including UIButton and
UISlider. You have seen how to set the targets and actions for these controls. Now we can take a
closer look at how UIControl overrides the same UIResponder methods you implemented in this
chapter.

UIControl是cocoa Touch中很多类的基类,包括UIButton和UISlider。你可以在UIResponder方法中

发送一个UIControl  action.


In UIControl, each possible control event is associated with a constant. Buttons, for example, typically
send action messages on the UIControlEventTouchUpInside control event. A target registered for this
control event will only receive its action message if the user touches the control and then lifts the finger
off the screen inside the frame of the control. Essentially, it is a tap.

UIControl中,每个control event都对应一个常量,比如UIControlEventTouchUpInside ;

比如你可以在UIButton中发送一个UIControlEventTouchUpInside 类型的action消息,而

在用户点击button,其对应的target就会收到这个消息;


For a button, however, you can have actions on other event types. For example, you might trigger a
method if the user removes the finger inside or outside the frame. Assigning the target and action
programmatically would look like this:

在button中,你还可以注册action-target;比如你可以注册某个target在用户进入或者移出button区域的时候收到action消息;


[rButton addTarget:tempController
action:@selector(resetTemperature:)
forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchUpOutside];

以上注册action-target;


Now consider how UIControl handles UIControlEventTouchUpInside.
// Not the exact code. There is a bit more going on!
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
// Reference to the touch that is ending
UITouch *touch = [touches anyObject];
// Location of that point in this control's coordinate system
CGPoint touchLocation = [touch locationInView:self];
// Is that point still in my viewing bounds?
if (CGRectContainsPoint(self.bounds, touchLocation))
{
// Send out action messages to all targets registered for this event!
[self sendActionsForControlEvents:UIControlEventTouchUpInside];
} else {
// The touch ended outside the bounds, different control event
[self sendActionsForControlEvents:UIControlEventTouchUpOutside];
}
}

以上代码在touchesEnded:中发送对应的action;



So how do these actions get sent to the right target? At the end of the UIResponder method
implementations, the control sends the message sendActionsForControlEvents: to itself. This
method looks at all of the target-action pairs the control has, and if any of them are registered for the
control event passed as the argument, those targets are sent an action message.

action如果才能发送到对应的target? 在UIResponder 所有对应的方法中,会给自身发送一个endActionsForControlEvents: 

消息,这个方法会查询所有的 target-action对,一旦其中和对应的control消息对应,那么这个target就会收到action消息;


However, a control never sends a message directly to its targets. Instead, it routes these messages
through the UIApplication object. Why not have controls send the action messages directly to the
targets? Controls can also have nil-targeted actions. If a UIControl’s target is nil, the UIApplication
finds the first responder of its UIWindow and sends the action message to it.

但是,一个control不能直接发送消息到target,而是要通过UIApplication来派发;因为control也可以有action对应一个nil的target,

这时这个action消息就会发送给UIWindow的first responder对象。



























你可能感兴趣的:(Touch Events and UIResponder)