ios开发过中,我们常常需要使用一些控件,来搭建我们的界面。有时系统为我们提供的控件并不能满足我们的需求,这时我们需要自己封装一些控件。因此我们就需要了解一些控件基类的特性,方便我们封装操作。比如说,UIKit提供了一组控件:UISwitch开关、UIButton按钮、UISegmentedControl分段控件、UISlider滑块、UITextField文本字段控件、
UIPageControl分页控件等,这些控件的基类均是UIControl。了解UIControl对我们封装类似UIButton,UISwitch等等触发类控件有很大的帮助
Target-Action机制, 相较于UIControl的父类UIView来讲是UIControl及其派生子类的一个共同的,重要的特性。
Target-action是一种设计模式,直译过来就是”目标-行为”。当我们通过代码为一个按钮添加一个点击事件时,通常是如下处理:
[button addTarget:self action:@selector(tapButton:) forControlEvents:UIControlEventTouchUpInside];也就是说,当按钮的点击事件发生时,会将消息发送到target(此处即为self对象),并由target对象的tapButton:方法来处理相应的事件。
其基本过程可以用下图来描述:
实现方法:
@interface lxcontrol : UIView @property (nonatomic,assign) id target;//在哪里找到方法 @property (nonatomic,assign) SEL action;//要执行的方法名 -(void)addTarget:(id)target action:(SEL)action; @end
#import "lxcontrol.h" @implementation lxcontrol #pragma mark --触发方法 -(void)addTarget:(id)target action:(SEL)action{ _target = target; _action = action; } #pragma mark --让外部传入的对象 去执行外部出入的方法 -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [self.target performSelector:self.action withObject:@"123"]; } @end
@property(nonatomic,getter=isEnabled) BOOL enabled;
控件默认是启用的。要禁用控件,可以将enabled属性设置为NO,这将导致控件忽略任何触摸事件。被禁用后,控件还可以用不同的方式显示自己,比如变成灰色不可用。虽然是由控件的子类完成的,这个属性却存在于UIControl中。
@property(nonatomic,getter=isSelected) BOOL selected;当用户选中控件时,UIControl类会将其selected属性设置为YES。子类有时使用这个属性来让控件选择自身,或者来表现不同的行为方式。
@property(nonatomic) UIControlContentVerticalAlignment contentVerticalAlignment;控件如何在垂直方向上布置自身的内容。默认是将内容顶端对其,对于文本字段,可能会改成UIControlContentVerticalAlignmentCenter。对于这个字段,可以使用下列诸值:
@property(nonatomic) UIControlContentHorizontalAlignment contentHorizontalAlignment;水平方向
UIControl类提供了一个标准机制,来进行事件登记和接收。这令你可以指定你的控件在发生特定事件时,通知代理类的一个方法。如果要注册一个事件,可以使用addTarget方法:
[ myControl addTarget: myDelegate action:@selector(myActionmethod:) forControlEvents:UIControlEventValueChanged ];事件可以用逻辑OR合并在一起,因此可以再一次单独的addTarget调用中指定多个事件。下列事件为基类UIControl所支持,除非另有说明,也适用于所有控件
UIControlEventTouchDown //单点触摸按下事件:用户点触屏幕,或者又有新手指落下的时候。 UIControlEventTouchDownRepeat //多点触摸按下事件,点触计数大于1:用户按下第二、三、或第四根手指的时候。 UIControlEventTouchDragInside //当一次触摸在控件窗口内拖动时。 UIControlEventTouchDragOutside //当一次触摸在控件窗口之外拖动时。 UIControlEventTouchDragEnter //当一次触摸从控件窗口之外拖动到内部时。 UIControlEventTouchDragExit //当一次触摸从控件窗口内部拖动到外部时。 UIControlEventTouchUpInside //所有在控件之内触摸抬起事件。 UIControlEventTouchUpOutside //所有在控件之外触摸抬起事件(点触必须开始与控件内部才会发送通知)。 UIControlEventTouchCancel //所有触摸取消事件,即一次触摸因为放上了太多手指而被取消,或者被上锁或者电话呼叫打断。 UIControlEventTouchChanged //当控件的值发生改变时,发送通知。用于滑块、分段控件、以及其他取值的控件。你可以配置滑块控件何时发送通知,在滑块被放下时发送,或者在被拖动时发送。 UIControlEventEditingDidBegin //当文本控件中开始编辑时发送通知。 UIControlEventEditingChanged //当文本控件中的文本被改变时发送通知。 UIControlEventEditingDidEnd //当文本控件中编辑结束时发送通知。 UIControlEventEditingDidOnExit //当文本控件内通过按下回车键(或等价行为)结束编辑时,发送通知。 UIControlEventAllEditingEvents //通知所有关于文本编辑的事件。 UIControlEventAllEvents
- (void)removeTarget:(nullable id)target action:(nullable SEL)action forControlEvents:(UIControlEvents)controlEvents;要删除一个或多个事件的相应动作,可以使用UIControl类的removeTarget方法。使用nil值就可以将给定事件目标的所有动作删除:
- (NSSet *)allTargets;要取得关于一个控件所有指定动作的列表,可以使用allTargets方法。这个方法返回一个NSSet,其中包含事件的完整列表:
- (nullable NSArray<NSString *> *)actionsForTarget:(nullable id)target forControlEvent:(UIControlEvents)controlEvent;另外,你还可以用actionsForTarget方法,来获取针对某一特定事件目标的全部动作列表:
- (void)sendAction:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event;如果设计了一个自定义控件类,可以使用sendActionsForControlEvent方法,为基本的UIControl事件或自己的自定义事件发送通知。例如,如果你的控件值正在发生变化,就可以
如果是想提供自定义的跟踪行为,则可以重写以下几个方法:
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event - (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event - (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event - (void)cancelTrackingWithEvent:(UIEvent *)event这四个方法分别对应的时跟踪开始、移动、结束、取消四种状态。看起来是不是很熟悉?这跟UIResponse提供的四个事件跟踪方法是不是挺像的?我们来看看UIResponse的四个方法:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event我们可以看到,上面两组方法的参数基本相同,只不过UIControl的是针对单点触摸,而UIResponse可能是多点触摸。另外,返回值也是大同小异。由于UIControl本身是视图,所以它实际上也继承了UIResponse的这四个方法。如果测试一下,我们会发现在针对控件的触摸事件发生时,这两组方法都会被调用,而且互不干涉。
我们的实例中,做了个小小的处理,将外部添加的Target-Action放在控件内部来处理事件,因此,我们的代码实现如下:
//lxControl.h - (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event { // 将事件传递到对象本身来处理 [super sendAction:@selector(handleAction:) to:self forEvent:event]; } - (void)handleAction:(id)sender { NSLog(@"handle Action"); } // ViewController.m - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; lxControl.h *control = [[lxControl.h alloc] initWithFrame:CGRectMake(100, 100, 100, 100)]; [control addTarget:self action:@selector(tapImageControl:) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:control] } - (void)tapImageControl:(id)sender { NSLog(@"sender = %@", sender); }
打印结果:
由于我们重写了sendAction:to:forEvent:方法,所以最后处理事件的Selector是ImageControl的handleAction:方法,而不是ViewController的tapImageControl:方法。
另外,sendAction:to:forEvent:实际上也被UIControl的另一个方法所调用,即sendActionsForControlEvents:。这个方法的作用是发送与指定类型相关的所有行为消息。我们可以在任意位置(包括控件内部和外部)调用控件的这个方法来发送参数controlEvents指定的消息。在我们的示例中,在ViewController.m中作了如下测试:
- (void)viewDidLoad { [control addTarget:self action:@selector(tapImageControl:) forControlEvents:UIControlEventTouchUpInside]; [control sendActionsForControlEvents:UIControlEventTouchUpInside]; }可以看到在未点击控件的情况下,触发了UIControlEventTouchUpInside事件,并打印了handle Action日志。
@interface UIControlTargetAction : NSObject { SEL _action; BOOL _cancelled; unsigned int _eventMask; id _target; } @property (nonatomic) BOOL cancelled; - (void).cxx_destruct; - (BOOL)cancelled; - (void)setCancelled:(BOOL)arg1; @end可以看到UIControlTargetAction对象维护了一个Target-Action所必须的三要素,即target,action及对应的事件eventMask。
When you call this method, target is not retained.另外,如果我们以同一组target-action和event多次调用addTarget:action:forControlEvents:方法,在_targetActions中并不会重复添加UIControlTargetAction对象。