UIView(控件)
功能一:界面显示
1. 屏幕上显示的所有UI元素都叫做控件,也有人叫做视图、组件;按钮(UIButton)、文本(UILabel)都是控件;
2. 所有的控件最终都继承自UIView
3. UI控件能够展示的原因是因为有一个layer层功能二:事件响应
1. UIView继承自UIResponder。UIResponder继承自NSObject
2. 大部分UI开头的类都是继承自UIResponder,UIResponder具有事件响应的能力,这是它们能够处理事件的原因注意
1. UI控件没有显示原因总结:
1. 没有尺寸位置;
2. 尺寸位置超出视图;
3. 没有颜色或图片等显示要素;
4. 没添加到当前窗口或视图中;
5. 透明(alpha = 0 || opapue = 1);
6. 其他控件或窗口挡住了;
7. 按钮、segmentedControl等直接设置属性,而没有分状态设置;
2. 大部分UI控件通过alloc.init创建都没有尺寸,颜色等;开关,菊花控件、toolBar控件、导航条控件、标签条控件等除外
UIKit中可能用得上的UI控件:
UIkit坐标系(UI控件位置、尺寸的基础)
- UIKit坐标系:
- 坐标系的原点(0,0)在屏幕的左上角
- x值向右正向延伸
- y值向下正向延伸
UIView位置、尺寸属性
-
frame:控件矩形框在父控件中的位置和尺寸
1. 以父控件的左上角为坐标原点view.frame = CGRectMake(0, 0, 100, 100);
-
bonuds:控件矩形框的尺寸及在内容层中的位置
1. 修改bounds的尺寸是以中心点为基准伸缩的
2. 自己左上角'内容层原点'为坐标原点,内容层是无限大的
3. bonuds的x,y修改,实际上是影响可视化区域在内容层中的位置。
4. 可视化区域,参照父控件,位置是不变。所以修改x,y,可以视作是内容层反向移动,有如下规律:
1. bounds x > 0,表示需要看右边内容,可视区域相对内容层原点往右边走,我们看到的效果,内容层原点往左移动
2. bounds y > 0,表示需要看下边内容,可视区域相对内容层原点往下边走,我们看到效果,内容层原点往上移动view.bounds = CGRectMake(0, 0, 100, 100);
- center:控件中心点的位置
1. 以父控件的左上角为坐标原点
2. 注意:在设置center时,先确定尺寸,否则尺寸不确定,center定位不准,布局控件时应:
1. 先设置frame后设置center.
2. 或通过设置bonuds,这样则不需要区分先后view.center = CGPointMake(100, 100);
UIView位置、尺寸属性的修改
以frame的修改为例(位置,尺寸的修改可参照frame的修改)
- 不能直接修改->OC对象的结构体属性的成员
// imageView.frame.size.width = 100; // 不允许
-
方案一:通过CGRectMake函数(常用)
imageView.frame = CGRectMake(100, 100, 200, 200);
-
方案二:利用临时结构体变量(常用)
- 使用场景:针对结构体中某个变量的修改
- C结构体的修改方式
CGRect tempFrame = imageView.frame; tempFrame.origin.x = 100; imageView.frame = tempFrame;
-
方案三:使用大括号{}形式(不常用)
- C结构体的修改方式
imageView.frame = redView.frame = (CGRect){(100, 100), (100, 100)};;
- C结构体的修改方式
-
小结:开发中经常需要设置或获取UIView尺寸属性,建议增加UIView(Rect)的分类,提高效率,步骤如下;
1. 创建Rect分类文件(UIView的分类),在.h文件通过@property声明x,y,width,height,centerX,centerY,top,bottom,leading,trailing属性;
1. 分类不能定义属性,这里使用@property仅是声明set、get方法,不会生成下划线成员属性并实现set、get方法
2. 实现set方法;
3. 实现get方法;
4. 建议UIView尺寸属性名都带上类前缀,如zq_x、zq_top等,避免以后会与原生或其他框架冲突.h文件 @interface UIView (Rect) /** 生成set、get方法声明 */ @property(nonatomic,assign) CGFloat zq_x; @property(nonatomic,assign) CGFloat zq_y; @property(nonatomic,assign) CGFloat zq_width; @property(nonatomic,assign) CGFloat zq_height; @property(nonatomic,assign) CGFloat zq_centerX; @property(nonatomic,assign) CGFloat zq_centerY; @property(nonatomic,assign) CGFloat zq_top; @property(nonatomic,assign) CGFloat zq_leading;//左 @property(nonatomic,assign) CGFloat zq_bottom; @property(nonatomic,assign) CGFloat zq_trailing;//右
.m文件(仅以代表举例) #pragma mark - x set、get方法实现 - (void)setZq_x:(CGFloat)zq_x { CGRect frame = self.frame; frame.origin.x = zq_x; self.frame = frame; } - (CGFloat)zq_x { return self.frame.origin.x; } #pragma mark - leading set、get方法实现 - (void)setZq_leading:(CGFloat)zq_leading { self.zq_x = zq_leading; } - (CGFloat)zq_leading { return self.zq_x; } #pragma mark - bottom set、get方法实现 - (void)setZq_bottom:(CGFloat)zq_bottom { self.zq_y = zq_bottom - self.zq_height; } - (CGFloat)zq_bottom { return self.zq_y + self.zq_height; }
UIView的transform(形变)属性
- 控件的形变包括平移、缩放、旋转等属性,类型是CGAffineTransform
- 常用形变方式举例,效果以Scale(伸缩)为例说明,假设宽高都是100:
-
方式一:CGAffineTransformMakeXXX:每次从零开始形变,每次都是相对于最开始的状态形变
- 效果说明:重复执行代码,每次宽高都是从(100,100)->(50,50)
self.view.transform = CGAffineTransformMakeTranslation(100, 100); self.view.transform = CGAffineTransformMakeScale(0.5, 0.5); self.view.transform = CGAffineTransformMakeRotation(M_PI);
- 效果说明:重复执行代码,每次宽高都是从(100,100)->(50,50)
-
方式二:CGAffineTransformXXX:接着上次的状态形变
- 效果说明:重复执行代码,宽高是从(100,100)->(50,50)->(25,25)->...
self.view.transform = CGAffineTransformTranslate(self.view.transform, 100, 100); self.view.transform = CGAffineTransformScale(self.view.transform, 0.5, 0.5); self.view.transform = CGAffineTransformRotate(self.view.transform, M_PI);
- 效果说明:重复执行代码,宽高是从(100,100)->(50,50)->(25,25)->...
-
方式三:清空形变
- 效果说明:宽高从(?,?)-> (100, 100)
self.view.transform = CGAffineTransformIdentity;
- 效果说明:宽高从(?,?)-> (100, 100)
-
矩形的坐标系转换
- 坐标系:
- UIKit坐标系 :以屏幕的左上角为坐标原点
- view1坐标系 : 以view1的左上角为坐标原点
- view2坐标系 : 以view2的左上角为坐标原点
- 转换过程:
- 判断旧坐标系,确定旧坐标系原点;
- 根据rect判断矩形在坐标系中的位置,可借助UIKit坐标系中转;
- 确定新坐标系,确定旧坐标系原点,判断矩形在坐标系中的位置
- 必须注意:rect不是控件,与控件无关系!只代表一块区域
以convertRect: toView:
为例;// 0.基础数据:UIKit坐标系下,view1,view2的坐标 view1.frame = CGRectMake(100, 100, 100, 100); view2.frame = CGRectMake(200, 200, 100, 100); ------------------------------------------------- // 1.从零点矩形看坐标转换 // 不代表UIKit坐标系左上角的点! CGRect zeroRect = CGRectZero; // {(0,0),(0,0)} CGRect newRect1 = [view1 convertRect:zeroRect toView:view2]; // view1坐标系-> view2坐标系 // UIKit坐标系下,矩形位置为{(100,100),(0,0)},view2坐标系左上角为{(200,200),(0,0)} // newRect1{(-100, -100), (0, 0)} CGRect newRect2 = [view2 convertRect:zeroRect toView:view1]; // view2坐标系-> view1坐标系 // UIKit坐标系下,矩形位置为{(200,200),(0,0)},view1坐标系左上角为{(100,100),(0,0)} // newRect2:{(100,100),(0,0)} ------------------------------------------------- // 2.rect={(100, 100, 100, 100)}看坐标转换 // 不代表UIKit坐标系中view1控件! rect1 = redView.frame = CGRectMake(100, 100, 100, 100); CGRect newRect3 = [view1 convertRect:rect1 toView:view2]; // view1坐标系-> view2坐标系 // UIKit坐标系下,矩形位置为{(200,200),(100,100)},view2坐标系左上角为{(200,100),(200,100)} // newRect3 = {(0, 0), (100, 100)} CGRect newRect4 = [view2 convertRect:zeroRect toView:view1]; // view2坐标系-> view1坐标系 // UIKit坐标系下,矩形位置为{(300,300),(100,100)},view1坐标系左上角为{(100,100),(100,100)} // newRect6 = {(200, 200), (100, 100)}
- 矩形的坐标转换另一个方法的原理与上述一致,只是fromView才是转换前坐标系,不再多论。
// 结果一致 CGRect newRect = [view1 convertRect:rect1 fromView:view2]; CGRect newRect = [view2 convertRect:rect1 toView:view1];
- 点得坐标系转换可参照上述原理,使用方法是:
CGPoint point = CGPointMake(100, 100); // 结果一致 [view1 convertPoint:point toView:view2]; [view2 convertRect:point fromView:view1];
常用坐标系转换 -> 获得控件在主窗口(屏幕)中的区域(rect)
- 坐标系转换:控件的坐标系 -> 主窗口的坐标系
1. 控件的坐标系:
1. 父控件坐标系 :以父控件的左上角为坐标原点(常用)
2. 自身坐标系 :以控件自身内容层的左上角为坐标原点(常用)
2. 主窗口的坐标系:即UIKit坐标系
- 注意:在控件的坐标系下,rect描述的必须是控件所在区域;存在规律:
1. 若以自身左上角为转换前坐标系原点,rect为self.bounds;
2. 若以父控件左上角为转换前坐标系原点,rect为self.frame - 获取写法概况如下:
// 这里为使显示简洁,定义KEY_WINDOW为主窗口 #define KEY_WINDOW ([UIApplication sharedApplication].keyWindow)
- 写法一(推荐):
// convertRect: toView: // 第一个参数(这里是KEY_WINDOW)不能设置为nil // redView.bounds的x、y设置不影响转换结果 CGRect newRect = [redView convertRect:redView.bounds toView:KEY_WINDOW]; CGRect newRect = [redView.superview convertRect:redView.frame toView:KEY_WINDOW];
- 等价于:
// 规律:交换写法一中第一与第三参数,由toView->fromView // redView.bounds的x、y设置不影响转换结果 // convertRect: fromView: CGRect newRect = [KEY_WINDOW convertRect:redView.bounds fromView:redView]; CGRect newRect = [KEY_WINDOW convertRect:redView.frame fromView:redView.superview];
- 等价于(假设redView或其父控件加入的是主窗口):
// redView.bounds的x、y设置不影响转换结果 CGRect newRect = [redView convertRect:redView.bounds toView:nil]; CGRect newRect = [redView.superview convertRect:redView.frame toView:nil]; // 最后一个参数可以设置(toView:nil)->表示以自身所在的窗口(self.window)的左上角为坐标系原点; // 尽管可以省略,开发中建议标明转换的坐标系,而不是设置为nil,这是因为: // self.window大部分情况下是keyWindow;但如果控件是加入到其他窗口中(如状态栏等),则不是keyWindow // 若是比较两矩形间、点与矩形的关系,此时需要保证转换后的坐标系必须是相同的坐标系;
- 等价于:
// 通过 @protocol UICoordinateSpace <NSObject> 中的方法 // CoordinateSpace:坐标空间(系) CGRect newRect = [redView convertRect:redView.bounds toCoordinateSpace:KEY_WINDOW]; CGRect newRect = [KEY_WINDOW convertRect:redView.bounds fromCoordinateSpace:redView]; CGRect newRect = [redView.superview convertRect:redView.frame toCoordinateSpace:KEY_WINDOW]; CGRect newRect = [KEY_WINDOW convertRect:redView.frame fromCoordinateSpace:redView.superview];
-
不等价于
// 正确转换(写法一): CGRect newRect = [redView convertRect:redView.bounds toView:KEY_WINDOW]; CGRect newRect = [redView.superview convertRect:redView.frame toView:KEY_WINDOW]; // 伪转换 -> 描述的区域不是redView CGRect newRect = [redView convertRect:redView.frame toView:KEY_WINDOW]; CGRect newRect = [redView.superview convertRect:redView.bounds toView:KEY_WINDOW]; // 正确转换(写法二): CGRect newRect = [KEY_WINDOW convertRect:redView.bounds fromView:redView]; CGRect newRect = [KEY_WINDOW convertRect:redView.frame fromView:redView.superview]; // 伪转换 -> 描述的区域不是redView CGRect newRect = [KEY_WINDOW convertRect:redView.frame fromView:redView]; CGRect newRect = [KEY_WINDOW convertRect:redView.bounds fromView:redView.superview]; // 伪转换 -> 逻辑错误 // 是获取redView坐标系下区域为rect的矩形在KEY_WINDOW坐标系下的区域newRect,而不是相反 CGRect newRect = [KEY_WINDOW convertRect:redView.bounds toView:redView]; CGRect newRect = [KEY_WINDOW convertRect:redView.frame toView:redView.superview]; CGRect newRect = [KEY_WINDOW convertRect:redView.frame toView:redView]; CGRect newRect = [KEY_WINDOW convertRect:redView.bounds toView:redView.superview];
- 写法一(推荐):
坐标系转换应用 -> 矩形区域(Rect)、点(Point)比较
- 将
不同坐标系
下的区域(点)转换成同一个坐标系
下的区域(点)
时,可以进行区域间关系
判断及区域与点
的关系判断 - 判断rect1是否包含了rect2
bool CGRectContainsRect(CGRect rect1, CGRect rect2)
- 判断rect1和rect2是否有重叠
bool CGRectIntersectsRect(CGRect rect1, CGRect rect2)
- 判断point是不是在rect上
bool CGRectContainsPoint(CGRect rect1, CGRect rect2)