UI基础 - UIResponder:视图缩放 | 视图拖动
▶ UIResponder
1 - 如果自定义的响应者是 UIview 或是 UIViewController 的子类则必须声明全部四个 UIResponder对象 的处理事件方法!这样做的原因很简单:所有视图都可以接收到完整的触摸事件流。如果自定义响应者是 UIKit 中响应者类,则不必如此
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
注:获得点击次数的最佳时机是在 touchBegan 和 touchEnd 方法中,后者最佳
2 - UITouch属性
tapCount 本次触摸点击了多少次
timestamp 本次触摸何时创建以及最后一次改变的时间
phase 本次触摸所处的阶段
locationView 触摸视图中的坐标
previousLocationView 上一次触摸的位置
3 - 限制触摸事件给特定视图
缺省情况下视图的 exclusiveTouch 属性设置为 NO,表示该视图并不限制当前窗口中的其他视图接收触摸事件
如果你设置其值 YES 则标识了该视图且只有该视图能对消息进行跟踪处理,窗口中的其他视图则无法接收到触摸事件
4 - 代码示例:实现视图的缩放、拖动效果(前提是要新建一个 UIView 的子类,并且还要重写 UIResponder对象 处理事件方法)
A. 拖动
方式一:直接计算偏移量
// - TouchView.m
1 #import "TouchView.h" 2 @interface TouchView () 3 @property(nonatomic,assign)CGPoint startPoint; 4 5 @end 6 7 @implementation TouchView 8 9 #pragma mark - overriding_methods 10 -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ 11 12 NSLog(@"touchesBegan!"); 13 self.backgroundColor = [UIColor colorWithRed:arc4random() % 256 / 255.0 green:arc4random() % 256 / 255.0 blue:arc4random() % 256 / 255.0 alpha:1.0];// 随机颜色 14 // 获取手指 15 UITouch *touch = [touches anyObject]; 16 // 获取手指触摸坐标 17 self.startPoint = [touch locationInView:self]; 18 NSLog(@"%@",NSStringFromCGPoint(self.startPoint)); 19 } 20 21 - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{ 22 NSLog(@"touchesCancelled!"); 23 } 24 25 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{ 26 NSLog(@"touchesEnded!"); 27 } 28 29 // 实现视图的拖动功能 30 -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ 31 NSLog(@"touchesMoved!"); 32 33 UITouch *touch = [touches anyObject]; 34 CGPoint currentPoint = [touch locationInView:self]; 35 36 // 获取 X 轴和 Y 轴的偏移量 37 CGFloat offSetX = currentPoint.x - self.startPoint.x; 38 CGFloat offSetY = currentPoint.y - self.startPoint.y; 39 40 // 获取中心坐标 41 CGPoint newCenter = self.center; 42 newCenter.x += offSetX; 43 newCenter.y += offSetY; 44 self.center = newCenter; 45 } 46 47 @end
方式二:使用 previousLocationInView: 计算出偏移量
// - TestView
1 #import "TestView.h" 2 @implementation TestView 3 4 #pragma mark - overriding_methods 5 -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ 6 } 7 8 - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{ 9 NSLog(@"touchesCancelled!"); 10 } 11 12 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{ 13 NSLog(@"touchesEnded!"); 14 } 15 16 // 实现视图的拖动功能 17 -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ 18 NSLog(@"touchesMoved!"); 19 20 UITouch *touch = [touches anyObject]; 21 CGPoint currentPoint = [touch locationInView:self]; 22 CGPoint prePoint = [touch previousLocationInView:self]; 23 24 CGRect viewFrame = self.frame; 25 // 获取 X 轴和 Y 轴的偏移量 26 CGFloat offSetX = currentPoint.x - prePoint.x; 27 CGFloat offSetY = currentPoint.y - prePoint.y; 28 viewFrame.origin.y = viewFrame.origin.y + offSetY; 29 viewFrame.origin.x = viewFrame.origin.x + offSetX; 30 self.frame = viewFrame; 31 32 } 33 34 @end
B. 缩放
方式一:通过捏合实现视图缩放
// - PinchView.m
1 #import "PinchView.h" 2 @interface PinchView () 3 @property(nonatomic,assign)CGFloat beginDistance; 4 5 @end 6 7 @implementation PinchView 8 9 #pragma mark - overrideMethods 10 // 实现缩放功能 11 - (id)initWithFrame:(CGRect)frame{ 12 self = [super initWithFrame:frame]; 13 if (self) { 14 15 self.multipleTouchEnabled = YES;// 开启多点触摸(默认是单指触摸) 16 } 17 return self; 18 } 19 20 // 实现缩放功能:步骤一 21 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 22 23 if (touches.count == 2) { 24 UITouch *firstTouch = [[touches allObjects] firstObject]; 25 UITouch *secondTouch = [[touches allObjects] lastObject]; 26 27 CGPoint firstPiont = [firstTouch locationInView:self]; // 拿到第一根手指点的坐标 28 CGPoint secondPiont = [secondTouch locationInView:self];// 拿到第二根手指点的坐标 29 self.beginDistance = [self distanceWithPointA:firstPiont pointB:secondPiont];// 获取两点间的距离 30 } 31 } 32 33 // 实现缩放功能:步骤二 34 -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ 35 if (touches.count == 2) { 36 UITouch *firstTouch = [[touches allObjects] firstObject]; 37 UITouch *secondTouch = [[touches allObjects] lastObject]; 38 39 CGPoint firstPiont = [firstTouch locationInView:self]; 40 CGPoint secondPiont = [secondTouch locationInView:self]; 41 CGFloat newDistance = [self distanceWithPointA:firstPiont pointB:secondPiont]; 42 43 CGFloat scale = newDistance / self.beginDistance;// 缩放系数 44 45 CGRect newBounds = self.bounds; 46 newBounds.size.width *= scale; 47 newBounds.size.height *= scale; 48 self.bounds = newBounds; 49 50 self.beginDistance = newDistance;// 计算完毕后记得要把两点间的距离初始化一次 51 } 52 } 53 54 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{ 55 56 NSLog(@"touchesEnded!"); 57 } 58 59 #pragma mark - self-defined-methods 60 - (CGFloat)distanceWithPointA:(CGPoint)pointA pointB:(CGPoint)pointB{ 61 62 // 返回两点间的距离(勾股定理) 63 return sqrt( pow(pointA.x - pointB.x, 2) + pow(pointA.y - pointB.y, 2)); 64 } 65 66 @end
方式二:单击视图缩小;双击视图放大
// - TestView.m
1 #import "TestView.h" 2 3 @implementation TestView{ 4 NSDictionary *_locationDic; // 实例变量:配合方式二使用 5 } 6 7 8 #pragma mark - overrding_methods 9 -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ 10 11 UITouch *touch = [touches anyObject]; 12 if (touch.tapCount - 2 == 0) { 13 14 // 取消所有延迟操作:这里是取消 单击 1 次时的操作 15 [NSObject cancelPreviousPerformRequestsWithTarget:self]; 16 17 // 方式二:取消某个特定的延迟方法 18 // 注意:参数要和执行的方法所传递的参数保持一致 19 if (_locationDic) { 20 [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(handleSingleTap:) object:_locationDic]; 21 } 22 23 } 24 } 25 26 - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ 27 28 UITouch *touch = [touches anyObject]; 29 30 // 单击:1 秒后执行 31 if (touch.tapCount - 1 == 0) { 32 33 _locationDic = [NSDictionary dictionaryWithObject:[NSValue valueWithCGPoint:[touch locationInView:self]] forKey:@"location"]; 34 [self performSelector:@selector(handleSingleTap:) withObject:_locationDic afterDelay:1.0]; 35 36 } 37 38 // 双击:视图扩大 39 else if (touch.tapCount - 2 == 0){ 40 41 CGRect viewFrame = self.frame; 42 // 宽高增加 20% 43 viewFrame.size.width += self.frame.size.width * 0.2; 44 viewFrame.size.height += self.frame.size.height * 0.2; 45 // 更新中心坐标 46 viewFrame.origin.x -= (self.frame.size.width * 0.2)/2.0; 47 viewFrame.origin.y -= (self.frame.size.height * 0.2)/2.0; 48 49 // 搞一个动画,效果更新然 50 [UIView beginAnimations:nil context:NULL]; 51 [UIView setAnimationDuration:1.2]; 52 self.frame = viewFrame; 53 [UIView commitAnimations]; 54 } 55 } 56 57 - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ 58 59 60 } 61 62 - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ 63 64 } 65 66 67 #pragma mark - self_defined_methods 68 // 单击:视图缩小 69 -(void)handleSingleTap:(NSDictionary *)touches{ 70 71 CGRect viewFrame = self.frame; 72 // 宽高增加 20% 73 viewFrame.size.width -= self.frame.size.width * 0.2; 74 viewFrame.size.height -= self.frame.size.height * 0.2; 75 // 更新中心坐标 76 viewFrame.origin.x += (self.frame.size.width * 0.2)/2.0; 77 viewFrame.origin.y += (self.frame.size.height * 0.2)/2.0; 78 79 80 [UIView beginAnimations:nil context:NULL]; 81 [UIView setAnimationDuration:1.2]; 82 self.frame = viewFrame; 83 [UIView commitAnimations]; 84 85 } 86 @end
// - ViewController.m
1 - (void)viewDidLoad { 2 [super viewDidLoad]; 3 self.view.backgroundColor = [UIColor cyanColor]; 4 5 TestView *tView = [[TestView alloc] initWithFrame:CGRectMake((SCREEN_WIDTH - 200)/2.0, 80, 200, 160)]; 6 tView.backgroundColor = [UIColor redColor]; 7 [self.view addSubview:tView]; 8 9 }
运行效果:初始视图 | 单击缩小 | 双击放大
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律