事件機制效果圖
效果圖 Gif.gif
AppDelegate.h
#import "AppDelegate.h"
#import "ButtonHitTestViewController.h"
#import "ButtonPointInsideViewController.h"
#import "ButtonSubViewOutsideViewController.h"
#import "KeyboardViewController.h"
#import "TestWindow.h"
@interface AppDelegate ()<UITableViewDataSource, UITableViewDelegate>
{
UITableViewController *_tableVC;
}
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
_tableVC = [[UITableViewController alloc] initWithStyle:UITableViewStylePlain];
_tableVC.tableView.delegate = self;
_tableVC.tableView.dataSource = self;
_tableVC.edgesForExtendedLayout = UIRectEdgeNone;
_tableVC.navigationItem.title = @"Touch2 Demo";
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:_tableVC];
TestWindow *testWindow = [[TestWindow alloc] init];
self.window = testWindow;
self.window.rootViewController = nav;
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
#pragma mark - table
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 4;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}
NSString *title;
switch (indexPath.row) {
case 0:
title = @"Button - hitTest";
break;
case 1:
title = @"Button - pointInside";
break;
case 2:
title = @"Button - subView outside";
break;
case 3:
title = @"keyboard";
break;
default:
break;
}
cell.textLabel.text = title;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
UIViewController *targetVC;
switch (indexPath.row) {
case 0:
targetVC = (UIViewController *)[[ButtonHitTestViewController alloc] init];
break;
case 1:
targetVC = (UIViewController *)[[ButtonPointInsideViewController alloc] init];
break;
case 2:
targetVC = (UIViewController *)[[ButtonSubViewOutsideViewController alloc] init];
break;
case 3:
targetVC = (UIViewController *)[[KeyboardViewController alloc] init];
break;
default:
break;
}
if (targetVC)
{
[_tableVC.navigationController pushViewController:targetVC animated:YES];
}
}
@end
ButtonHitTestView.m
#import "ButtonHitTestView.h"
static const CGFloat buttonExtraPadding = 20;
@interface ButtonHitTestView()
{
UIView *_buttonBgView;
UIButton *_button;
}
@end
@implementation ButtonHitTestView
- (instancetype)init {
if (self = [super init]) {
self.backgroundColor = [UIColor lightGrayColor];
_buttonBgView = [[UIView alloc] init];
_buttonBgView.layer.borderColor = [UIColor redColor].CGColor;
_buttonBgView.layer.borderWidth = 1;
[self addSubview:_buttonBgView];
_button = [[UIButton alloc] init];
[_button setBackgroundColor:[UIColor orangeColor]];
[_button setTitle:@"button" forState:UIControlStateNormal];
[_button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_button];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
_button.frame = CGRectMake(50, 50, 100, 30);
_buttonBgView.frame = CGRectMake(_button.frame.origin.x - buttonExtraPadding, _button.frame.origin.y - buttonExtraPadding, _button.frame.size.width + buttonExtraPadding * 2, _button.frame.size.height + buttonExtraPadding * 2);
}
- (void)buttonClicked:(id)sender {
//彈窗 alertView:
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"確定么?!" message:@"按鈕 已經被點擊" preferredStyle:UIAlertControllerStyleAlert];
//alertView 下面的確定按鈕:
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]];
//彈出 alertVC:
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alert animated:YES completion:nil];
}
#pragma mark - 重寫 hitTest 方法 - 擴展按鈕范圍
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
//給出一個我想要的擴大后的按鈕的范圍,這里用到了 CGRectGetMinX , CGRectGetMinY , CGRectGetWidth , CGRectGetHeight 等函數方法簡化了代碼:
CGRect targetRect = CGRectMake(CGRectGetMinX(_button.frame) - buttonExtraPadding, CGRectGetMinY(_button.frame) - buttonExtraPadding, CGRectGetWidth(_button.frame) + buttonExtraPadding * 2, CGRectGetHeight(_button.frame) + buttonExtraPadding * 2);
//這個方法就相當于上面的代碼 , 擴大自身2倍的 dx 和2倍的 dy 同時還能讓左上角點 orign 偏移:
targetRect = CGRectInset(_button.frame, - buttonExtraPadding, - buttonExtraPadding);
//判斷我當前點擊的目標范圍在不在這個 targetRect 范圍內:
if (CGRectContainsPoint(targetRect, point)) {
//如果在點擊范圍內,那么它就相當于點擊了_ button 這個 view 視圖: , ( UIButton --> UIControl --> UIView )
return _button;
}
//如果不是的話我就交給原有的 super 方法去執行原有的 hitTest 方法邏輯:
return [super hitTest:point withEvent:event];
}
@end
ButtonHitTestViewController.m
#import "ButtonHitTestViewController.h"
#import "ButtonHitTestView.h"
@implementation ButtonHitTestViewController
- (instancetype)init
{
self = [super init];
if (self)
{
self.navigationItem.title = @"Button HitTest";
self.edgesForExtendedLayout = UIRectEdgeNone;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
ButtonHitTestView *view = [[ButtonHitTestView alloc] init];
[self.view addSubview:view];
view.frame = CGRectMake(50, 50, 200, 200);
}
@end
ButtonPointInsideView.m
#import "ButtonPointInsideView.h"
#import "TestButton.h"
const static CGFloat buttonExtraPadding = 20;
@interface ButtonPointInsideView() {
UIView *_buttonBgView;
TestButton *_button;
}
@end
@implementation ButtonPointInsideView
#pragma mark - init
- (instancetype)init {
self = [super init];
if (self) {
self.backgroundColor = [UIColor lightGrayColor];
_buttonBgView = [[UIView alloc] init];
_buttonBgView.layer.borderColor = [UIColor redColor].CGColor;
_buttonBgView.layer.borderWidth = 1;
[self addSubview:_buttonBgView];
_button = [[TestButton alloc] init];
[_button setBackgroundColor:[UIColor orangeColor]];
[_button setTitle:@"button" forState:UIControlStateNormal];
[_button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_button];
}
return self;
}
#pragma mark - layoutSubviews
- (void)layoutSubviews {
[super layoutSubviews];
_button.frame = CGRectMake(50, 50, 100, 30);
_buttonBgView.frame = CGRectMake(
_button.frame.origin.x - buttonExtraPadding,
_button.frame.origin.y - buttonExtraPadding,
_button.frame.size.width + buttonExtraPadding*2,
_button.frame.size.height + buttonExtraPadding*2
);
}
#pragma mark - buttonClicked:
- (void)buttonClicked:(id)sender {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:@"button clicked" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]];
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alert animated:YES completion:nil];
}
@end
ButtonPointInsideViewController.m
#import "ButtonPointInsideViewController.h"
#import "ButtonPointInsideView.h"
@implementation ButtonPointInsideViewController
- (instancetype)init
{
self = [super init];
if (self)
{
self.navigationItem.title = @"Button PointInside";
self.edgesForExtendedLayout = UIRectEdgeNone;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
ButtonPointInsideView *view = [[ButtonPointInsideView alloc] init];
[self.view addSubview:view];
view.frame = CGRectMake(50, 50, 200, 200);
}
@end
TestButton.h
#import "TestButton.h"
@implementation TestButton
//判斷當前點擊的這個點是否在試圖范圍內的方法?!
//#pragma mark - 重寫pointInside: withEvent: 方法
//- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
//{
// CGFloat buttonExtraPadding = 20;
//
// //way1
//// CGRect targetRect = CGRectInset(self.bounds, - buttonExtraPadding, - buttonExtraPadding);
//// if (CGRectContainsPoint(targetRect, point))
//// {
//// return YES;
//// }
//
// //way2
// CGPoint convertPoint = [self convertPoint:point toView:self.superview];
// CGRect targetRect = CGRectInset(self.frame, - buttonExtraPadding, - buttonExtraPadding);
// if (CGRectContainsPoint(targetRect, convertPoint))
// {
// return YES;
// }
// return [super pointInside:point withEvent:event];
//
//}
//用 bounds 的方法:
//#pragma mark - 方法1
//- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
// CGFloat buttonExtraPadding = 20;
// CGRect targetRect = CGRectInset(self.bounds, - buttonExtraPadding, - buttonExtraPadding);
// if (CGRectContainsPoint(targetRect, point)) {
// return YES;
// }
// return [super pointInside:point withEvent:event];
//}
//還可以用 frame 的方式去判斷, frame 與 bounds 的區別, frame 是按鈕在父控件的坐標位置
#pragma mark - 方法2
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
CGFloat buttonExtraPadding = 20;
//將當前坐標系轉換到父視圖的坐標系上:
CGPoint convertPoint = [self convertPoint:point toView:self.superview];
CGRect targetRect = CGRectInset(self.frame, -buttonExtraPadding, -buttonExtraPadding);
if (CGRectContainsPoint(targetRect, point)) {
return YES;
}
return [super pointInside:point withEvent:event];
}
@end
ButtonSubViewOutsideView.h
#import "ButtonSubViewOutsideView.h"
const static CGFloat buttonExtraPadding = 20;
@interface ButtonSubViewOutsideView()
{
UIButton *_button;
}
@end
@implementation ButtonSubViewOutsideView
- (instancetype)init {
self = [super init];
if (self) {
self.backgroundColor = [UIColor lightGrayColor];
_button = [[UIButton alloc] init];
[_button setBackgroundColor:[UIColor orangeColor]];
[_button setTitle:@"button" forState:UIControlStateNormal];
[_button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_button];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
_button.frame = CGRectMake(50, - buttonExtraPadding, 100, 30);
}
#pragma mark - 按鈕點擊方法
- (void)buttonClicked:(id)sender {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:@"button clicked" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]];
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alert animated:YES completion:nil];
}
//點擊超出父視圖的按鈕位置的時候首先在父視圖調用 pointInset 方法,發現點擊位置不在父視圖范圍內,則返回 NO, 所以超出父視圖范圍不會響應按鈕點擊方法 , 重寫 pointInset 方法 , 不是對 button 的 pointInset 方法修改,而是修改父視圖的 pointInset 方法 , 讓父視圖的感知范圍擴大
//#pragma mark - 重寫pointInside方法判斷點擊位置是否在父視圖范圍內
//- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
// if ([_button pointInside:[self convertPoint:point toView:_button] withEvent:event]) {
// return YES;
// }
// return [super pointInside:point withEvent:event];
//}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
//當我點擊的這個位置在 button 的范圍內的時候就讓父視圖的 pointInset 返回 YES 即可!
//轉換到 button 的坐標系上,這里不能直接寫 point , 因為直接寫是 button 的位置相對于父視圖的位置,現在是要轉換到 button 的位置!!!
if ([_button pointInside:[self convertPoint:point toView:_button] withEvent:event]) {
return YES;
}
return [super pointInside:point withEvent:event];
}
@end
ButtonSubViewOutsideViewController.h
#import "ButtonSubViewOutsideViewController.h"
#import "ButtonSubViewOutsideView.h"
@implementation ButtonSubViewOutsideViewController
- (instancetype)init
{
self = [super init];
if (self)
{
self.navigationItem.title = @"Subview OutSide";
self.edgesForExtendedLayout = UIRectEdgeNone;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
ButtonSubViewOutsideView *view = [[ButtonSubViewOutsideView alloc] init];
[self.view addSubview:view];
view.frame = CGRectMake(50, 50, 200, 200);
}
@end
TestWindow.m
#import "TestWindow.h"
@implementation TestWindow
- (void)sendEvent:(UIEvent *)event {
BOOL needEnd = YES;
//遍歷所有的 touches , 去除文本框里的位置, 文本框里的位置為第一響應位置!!!
for (UITouch *touch in event.allTouches) {
if ([touch.view isFirstResponder]) {
needEnd = NO;
break;
}
}
if (needEnd)
{
[self endEditing:YES];
}
[super sendEvent:event];
}
@end
KeyboardViewController.h
#import "KeyboardViewController.h"
#import "KeyboardView.h"
@implementation KeyboardViewController
- (instancetype)init
{
self = [super init];
if (self)
{
self.navigationItem.title = @"Keyboard";
self.edgesForExtendedLayout = UIRectEdgeNone;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
KeyboardView *view = [[KeyboardView alloc] init];
[self.view addSubview:view];
view.frame = CGRectMake(50, 50, 200, 200);
}
@end
KeyboardView.h
#import "KeyboardView.h"
@interface KeyboardView() {
UITextField *_textField;
}
@end
@implementation KeyboardView
- (instancetype)init
{
self = [super init];
if (self) {
[self setBackgroundColor:[UIColor lightGrayColor]];
_textField = [[UITextField alloc] init];
[_textField setBackgroundColor:[UIColor whiteColor]];
[self addSubview:_textField];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
_textField.frame = CGRectMake(20, 50, 150, 30);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//way1
//[_textField resignFirstResponder];
//way2 UIView,收起鍵盤 分類方法 , 結束編輯
//[self endEditing:YES];
//way3 方法父視圖的范圍,讓 window 去做 endEditing;
//[[UIApplication sharedApplication].keyWindow endEditing:YES];
//way4
//用 UIApplication 的事件機制 , 通過 sendAction 沿著時間的響應鏈進行傳遞:
//找到當前的第一響應 firstResponder , 然后調用該第一響應的 resignFirstResponder 方法:
[[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder) to:nil from:nil forEvent:nil];
}
@end