iOS - UIView
前言
NS_CLASS_AVAILABLE_IOS(2_0) @interface UIView : UIResponder <NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusEnvironment>
@available(iOS 2.0, *) public class UIView : UIResponder, NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusEnvironment
苹果将控件的共同属性都抽取到父类 UIView 中,所有的控件最终都继承自 UIView。UIView 最纯洁、最干净的控件,拥有尺寸、位置、背景色等基本属性。
view:翻译过来是页面的意思,iOS 有层的概念。
1、View 的创建
UIView 创建出来默认是透明的,在 iOS6 的时候是白色的。
Objective-C
// 实例化 view 对象,并设置 view 大小 UIView *view = [[UIView alloc] initWithFrame:CGRectMake(10, 20, 200, 100)]; // 将 view 加到 window 上显示出来 [self.view addSubview:view];
Swift
// 实例化 view 对象,并设置 view 大小 let view:UIView = UIView(frame: CGRectMake(10, 20, 200, 100)) // 将 view 加到 window 上显示出来 self.view.addSubview(view)
2、View 的设置
Objective-C
// 设置 frame /* frame:控件矩形框在父控件中的位置和尺寸,以父控件的左上角为坐标原点 */ view.frame = CGRectMake(10, 20, 200, 100); // 设置 bounds /* bounds:控件矩形框的位置和尺寸,以自己左上角为坐标原点,所以 bounds 的 x、y 一般为 0 */ view.bounds = CGRectMake(0, 0, 200, 100); // 设置中心位置 /* 控件中点的位置,以父控件的左上角为坐标原点 */ view.center = self.view.center; // 设置背景颜色 view.backgroundColor = [UIColor greenColor]; // 设置背景颜色半透明 view.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5]; // 设置视图透明度 /* 范围:0.0 ~ 1.0 ,0.0 透明,1.0 不透明(默认) 视图上的文字等内容的透明度也同时被改变 */ view.alpha = 1.0; // 设置 tag 值 /* 用于区分不同的 view ,所有继承 view 的类对象都可以设置 tag 值 */ view.tag = 100; // 设置用户交互属性 /* YES:打开用户交互属性,NO:关闭用户交互属性 */ view.userInteractionEnabled = YES; // 设置圆脚边框 /* cornerRadius:圆角半径 masksToBounds:子图层是否剪切图层边界,默认为 NO */ view.layer.cornerRadius = 20; view.layer.masksToBounds = YES; // 设置边框 /* borderWidth:边框粗细 borderColor:边框颜色 */ view.layer.borderWidth = 5; view.layer.borderColor = [[UIColor blueColor] CGColor]; // 不允许子视图的范围超过父视图的范围 /* 不允许 view 的子视图的范围超过 view 的范围,在父视图上设置 */ view.clipsToBounds = NO; // 获得自己的所有子控件对象 /* 数组元素的顺序决定着子控件的显示层级顺序(下标越大的,越显示在上面) */ NSArray *subviews = self.view.subviews; // 获得自己的父控件对象 UIView *superview = self.view.superview; // 从父视图中移除 [view removeFromSuperview]; // 根据一个 tag 标识找出对应的控件 UIView *view = [self.view viewWithTag:100];
Swift
// 设置 frame /* 控件矩形框在父控件中的位置和尺寸,以父控件的左上角为坐标原点 */ view.frame = CGRectMake(10, 20, 200, 100) // 设置 bounds /* 控件矩形框的位置和尺寸,以自己左上角为坐标原点,所以 bounds 的 x、y 一般为 0 */ view.bounds = CGRectMake(0, 0, 200, 100) // 设置中心位置 /* 控件中点的位置,以父控件的左上角为坐标原点 */ view.center = self.view.center // 设置背景颜色 view.backgroundColor = UIColor.greenColor() // 设置背景颜色半透明 view.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.5) // 设置视图透明度 /* 范围:0.0 ~ 1.0 ,0.0 透明,1.0 不透明(默认) 视图上的文字等内容的透明度也同时被改变 */ view.alpha = 1.0 // 设置 tag 值 /* 用于区分不同的 view ,所有继承 view 的类对象都可以设置 tag 值 */ view.tag = 100 // 设置用户交互属性 /* true:打开用户交互属性,false:关闭用户交互属性 */ view.userInteractionEnabled = true // 设置圆脚边框 /* cornerRadius:圆角半径 masksToBounds:子图层是否剪切图层边界,默认为 false */ view.layer.cornerRadius = 20 view.layer.masksToBounds = true // 设置边框 /* borderWidth:边框粗细 borderColor:边框颜色 */ view.layer.borderWidth = 5 view.layer.borderColor = UIColor.blueColor().CGColor // 子视图的范围不允许超过父视图的范围 /* 不允许 view 的子视图的范围超过 view 的范围,在父视图上设置 */ view.clipsToBounds = true // 获得自己的所有子控件对象 /* 数组元素的顺序决定着子控件的显示层级顺序(下标越大的,越显示在上面) */ let subviews:Array = self.view.subviews // 获得自己的父控件对象 let superview:UIView? = self.view.superview // 从父视图中移除 view.removeFromSuperview() // 根据一个 tag 标识找出对应的控件 let view:UIView? = self.view.viewWithTag(100)
3、View 的层次设置
在 iOS 中后添加的 View 在上层。
Objective-C
// 放到最上层 /* 将 view1 放到最上层,层次关系 view2.view3.view1 */ [self.view bringSubviewToFront:view1]; // 放倒最下面 /* 将 view3 放倒最下面,层次关系 view3.view1.view2 */ [self.view sendSubviewToBack:view3]; // 位置进行交换 /* 将 view1 和 view3 位置进行交换,层次关系 view3.view2.view1 */ [self.view exchangeSubviewAtIndex:0 withSubviewAtIndex:2]; // 放在上面 /* 将 view1 放在 view3 上面,层次关系 view2.view3.view1 */ [self.view insertSubview:view1 aboveSubview:view3]; // 放在下面 /* 将 view3 放在 view2 下面,层次关系 view1.view3.view2 */ [self.view insertSubview:view3 belowSubview:view2]; // 放在 n 位置 /* 将 view1 放在1的位置,层次关系 view2.view1.view3 */ [self.view insertSubview:view1 atIndex:1];
Swift
// 放到最上层 /* 将 view1 放到最上层,层次关系 view2.view3.view1 */ self.view.bringSubviewToFront(view1) // 放倒最下面 /* 将 view3 放倒最下面,层次关系 view3.view1.view2 */ self.view.sendSubviewToBack(view3) // 位置进行交换 /* 将 view1 和 view3 位置进行交换,层次关系 view3.view2.view1 */ self.view.exchangeSubviewAtIndex(0, withSubviewAtIndex: 2) // 放在上面 /* 将 view1 放在 view3 上面,层次关系 view2.view3.view1 */ self.view.insertSubview(view1, aboveSubview: view3) // 放在下面 /* 将 view3 放在 view2 下面,层次关系 view1.view3.view2 */ self.view.insertSubview(view3, belowSubview: view2) // 放在 n 位置 /* 将 view1 放在1的位置,层次关系 view2.view1.view3 */ self.view.insertSubview(view1, atIndex: 1)
4、View 的旋转与缩放设置
Objective-C
单一形变
// 控件的形变属性 @property(nonatomic) CGAffineTransform transform; // 旋转 /* (CGFloat angle) 旋转 45 度,需要输入的参数为弧度,45/180 * M_PI,1 度 = PI/180 弧度 */ self.testView.transform = CGAffineTransformMakeRotation(0.25 * M_PI); [self.testView.layer setAffineTransform: CGAffineTransformMakeRotation(0.25 * M_PI)]; // 缩放 /* (CGFloat sx, CGFloat sy) (1, 2) 宽度和高度的放大倍数 */ self.testView.transform = CGAffineTransformMakeScale(1, 2); [self.testView.layer setAffineTransform: CGAffineTransformMakeScale(1, 2)]; // 平移 /* (CGFloat tx, CGFloat ty) (100, 100) 水平和垂直方向的移动距离 */ self.testView.transform = CGAffineTransformMakeTranslation(100, 100); [self.testView.layer setAffineTransform: CGAffineTransformMakeTranslation(100, 100)];
叠加形变
// 旋转 + 缩放 CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(0.25 * M_PI); self.testView.transform = CGAffineTransformScale(rotationTransform, 2, 2); // 旋转 + 平移 CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(0.25 * M_PI); self.testView.transform = CGAffineTransformTranslate(rotationTransform, 200, 100); // 缩放 + 平移 CGAffineTransform scaleTransform = CGAffineTransformMakeScale(2, 2); self.testView.transform = CGAffineTransformTranslate(scaleTransform, 100, 100); // 旋转 + 缩放 + 平移 CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(0.25 * M_PI); CGAffineTransform rotationScaleTransform = CGAffineTransformScale(rotationTransform, 2, 2); self.testView.transform = CGAffineTransformTranslate(rotationScaleTransform, 200, 100);
累加形变
// 连续旋转 self.testView.transform = CGAffineTransformRotate(self.testView.transform, 0.25 * M_PI); // 连续缩放 self.testView.transform = CGAffineTransformScale(self.testView.transform, 2, 2); // 连续平移 self.testView.transform = CGAffineTransformTranslate(self.testView.transform, 100, 100);
还原形变
// 还原所有形变 self.testView.transform = CGAffineTransformIdentity; [self.testView.layer setAffineTransform: CGAffineTransformIdentity];
Swift
单一形变
// 控件的形变属性 public var transform: CGAffineTransform // 旋转 /* (angle: CGFloat) 旋转 45 度,需要输入的参数为弧度,45/180 * M_PI,1 度 = PI/180 弧度 */ self.testView.transform = CGAffineTransformMakeRotation(0.25 * CGFloat(M_PI)) self.testView.layer.setAffineTransform(CGAffineTransformMakeRotation(0.25 * CGFloat(M_PI))) // 缩放 /* (sx: CGFloat, _ sy: CGFloat) (1, 2) 宽度和高度的放大倍数 */ self.testView.transform = CGAffineTransformMakeScale(1, 2) self.testView.layer.setAffineTransform(CGAffineTransformMakeScale(1, 2)) // 平移 /* (tx: CGFloat, _ ty: CGFloat) 水平和垂直方向的移动距离 */ self.testView.transform = CGAffineTransformMakeTranslation(100, 100) self.testView.layer.setAffineTransform(CGAffineTransformMakeTranslation(100, 100))
叠加形变
// 旋转 + 缩放 let rotationTransform:CGAffineTransform = CGAffineTransformMakeRotation(0.25 * CGFloat(M_PI)) self.testView.transform = CGAffineTransformScale(rotationTransform, 2, 2) // 旋转 + 平移 let rotationTransform:CGAffineTransform = CGAffineTransformMakeRotation(0.25 * CGFloat(M_PI)) self.testView.transform = CGAffineTransformTranslate(rotationTransform, 200, 100) // 缩放 + 平移 let scaleTransform:CGAffineTransform = CGAffineTransformMakeScale(2, 2) self.testView.transform = CGAffineTransformTranslate(scaleTransform, 100, 100) // 旋转 + 缩放 + 平移 let rotationTransform:CGAffineTransform = CGAffineTransformMakeRotation(0.25 * CGFloat(M_PI)) let rotationScaleTransform:CGAffineTransform = CGAffineTransformScale(rotationTransform, 2, 2) self.testView.transform = CGAffineTransformTranslate(rotationScaleTransform, 200, 100)
累加形变
// 连续旋转 self.testView.transform = CGAffineTransformRotate(self.testView.transform, 0.25 * CGFloat(M_PI)) // 连续缩放 self.testView.transform = CGAffineTransformScale(self.testView.transform, 2, 2) // 连续平移 self.testView.transform = CGAffineTransformTranslate(self.testView.transform, 100, 100)
还原形变
// 还原所有形变 self.testView.transform = CGAffineTransformIdentity self.testView.layer.setAffineTransform(CGAffineTransformIdentity)
5、View 的跟随模式设置
Objective-C
// 父视图设置 /* 父视图允许子视图跟随 default is YES */ fatherView.autoresizesSubviews = YES; // 子视图设置 /* UIViewAutoresizingNone = 0, // 不跟随 UIViewAutoresizingFlexibleLeftMargin = 1 << 0, // 左边距 随父视图变化 UIViewAutoresizingFlexibleRightMargin = 1 << 2, // 右边距 随父视图变化 UIViewAutoresizingFlexibleTopMargin = 1 << 3, // 上边距 随父视图变化 UIViewAutoresizingFlexibleBottomMargin = 1 << 5 // 下边距 随父视图变化 UIViewAutoresizingFlexibleWidth = 1 << 1, // 宽度 随父视图变化 UIViewAutoresizingFlexibleHeight = 1 << 4, // 高度 随父视图变化 */ sonView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
Swift
// 父视图设置 /* 父视图允许子视图跟随 default is true */ fatherView.autoresizesSubviews = true // 子视图设置 /* None // 不跟随 FlexibleLeftMargin // 左边距 随父视图变化 FlexibleRightMargin // 右边距 随父视图变化 FlexibleTopMargin // 上边距 随父视图变化 FlexibleBottomMargin // 下边距 随父视图变化 FlexibleWidth // 宽度 随父视图变化 FlexibleHeight // 高度 随父视图变化 */ sonView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
6、View 的动画设置
6.1 block 方式
设置控件位置、尺寸、透明度等的代码,放在 animateWithDuration: block 中,将自动以动画的方式改变。
Objective-C
// 移动时间 2 秒 [UIView animateWithDuration:2 animations:^{ // 改变控件的位置和尺寸,改变后的位置或大小 view.frame = CGRectMake([UIScreen mainScreen].bounds.size.width - 60, 20, 50, 50); } completion:^(BOOL finished) { // 上一个设置完成后 [UIView animateWithDuration:2 animations:^{ // 改变控件的位置和尺寸,改变后的位置或大小 view.frame = CGRectMake(10, [UIScreen mainScreen].bounds.size.height - 110, 100, 100); }]; }];
Swift
// 移动时间 2 秒 UIView.animateWithDuration(2, animations: { // 改变控件的位置和尺寸,改变后的位置或大小 view.frame = CGRectMake(UIScreen.mainScreen().bounds.size.width - 60, 20, 50, 50) }) { (finished:Bool) in // 上一个设置完成后 UIView.animateWithDuration(2, animations: { // 改变控件的位置和尺寸,改变后的位置或大小 view.frame = CGRectMake(10, UIScreen.mainScreen().bounds.size.height - 110, 100, 100) }) }
6.2 动画块方式
设置控件位置、尺寸、透明度等的代码,放在 beginAnimations: 和 commitAnimations 之间,将自动以动画的方式改变。
Objective-C
// 开始一个动画块 [UIView beginAnimations:nil context:nil]; // 动画设置 // 设置动画时间 /* default = 0.2 */ [UIView setAnimationDuration:2.0]; // 设置延时 /* 设置指定的时间后开始执行动画,default = 0.0 */ [UIView setAnimationDelay:1.0]; // 设置动画开始执行时间 /* default = now ([NSDate date]) */ [UIView setAnimationStartDate:[NSDate dateWithTimeIntervalSinceNow:10]]; // 设置动画执行节奏 /* UIViewAnimationCurveEaseInOut, // slow at beginning and end 开始喝结束慢速,默认 UIViewAnimationCurveEaseIn, // slow at beginning 开始慢速 UIViewAnimationCurveEaseOut, // slow at end 结束慢速 UIViewAnimationCurveLinear // 匀速 */ [UIView setAnimationCurve:UIViewAnimationCurveEaseOut]; // 设置重复次数 /* default = 0.0. May be fractional */ [UIView setAnimationRepeatCount:CGFLOAT_MAX]; // 设置是否自动返回 /* default = NO. used if repeat count is non-zero */ [UIView setAnimationRepeatAutoreverses:YES]; // 设置是否从当前状态开始动画 /* default = NO */ [UIView setAnimationBeginsFromCurrentState:YES]; // 设置代理 /* default = nil */ [UIView setAnimationDelegate:self]; // 设置动画开始时执行的代理方法,自定义方法 /* default = NULL */ [UIView setAnimationWillStartSelector:@selector(startAnimations)]; // 设置动画结束时执行的代理方法,自定义方法 /* default = NULL */ [UIView setAnimationDidStopSelector:@selector(stopAnimations)]; // 动画之行后效果 // 设置透明度,改变后的透明度 view.alpha = 0.0; // 改变控件的位置和尺寸,改变后的位置或大小 view.center = CGPointMake(250, 250); view.frame = CGRectMake(100, 180, 50, 50); // 结束一个动画块 [UIView commitAnimations];
Swift
// 开始一个动画块 UIView.beginAnimations(nil, context: nil) // 动画设置 // 设置动画时间 /* default = 0.2 */ UIView.setAnimationDuration(2.0) // 设置延时 /* 设置指定的时间后开始执行动画,default = 0.0 */ UIView.setAnimationDelay(1.0) // 设置动画开始执行时间 /* default = now ([NSDate date]) */ UIView.setAnimationStartDate(NSDate(timeIntervalSinceNow: 10)) // 设置动画执行节奏 /* EaseInOut, // slow at beginning and end 开始喝结束慢速,默认 EaseIn, // slow at beginning 开始慢速 EaseOut, // slow at end 结束慢速 Linear // 匀速 */ UIView.setAnimationCurve(.EaseOut) // 设置重复次数 /* default = 0.0. May be fractional */ UIView.setAnimationRepeatCount(9999999) // 设置是否自动返回 /* default = NO. used if repeat count is non-zero */ UIView.setAnimationRepeatAutoreverses(true) // 设置是否从当前状态开始动画 /* default = NO */ UIView.setAnimationBeginsFromCurrentState(true) // 设置代理 /* default = nil */ UIView.setAnimationDelegate(self) // 设置动画开始时执行的代理方法,自定义方法 /* default = NULL */ UIView.setAnimationWillStartSelector(#selector(UiView.startAnimations)) // 设置动画结束时执行的代理方法,自定义方法 /* default = NULL */ UIView.setAnimationDidStopSelector(#selector(UiView.stopAnimations)) // 动画之行后效果 // 设置透明度,改变后的透明度 view.alpha = 0.0 // 改变控件的位置和尺寸,改变后的位置或大小 view.center = CGPointMake(250, 250) view.frame = CGRectMake(100, 180, 50, 50) // 结束一个动画块 UIView.commitAnimations()
7、frame 与 NSValue 的相互转换
Objective-C
// Frame 转 NSValue NSValue *freamValue = [NSValue valueWithCGRect:frame]; // NSValue 转 Frame frame = [freamValue CGRectValue];
Swift
// Frame 转 NSValue let freamValue:NSValue = NSValue(CGRect: frame) // NSValue 转 Frame frame = freamValue.CGRectValue()
8、view 的封装(自定义控件)
如果一个 view 内部的子控件比较多,一般会考虑自定义一个 view,把它内部子控件的创建屏蔽起来,不让外界关心。外界可以传入对应的模型数据给 view,view 拿到模型数据后给内部的子控件设置对应的数据。
- 一个控件有 2 种创建方式:
- 通过纯代码创建
- 初始化时一定会调用 initWithFrame: 方法。想做一些初始化操作,应该在这个方法中执行。
- 系统中 init 方法执行时会自动调用 initWithFrame: 方法。
- 通过 xib\storyboard 创建
- 初始化时不会调用 initWithFrame: 方法,只会调用 initWithCoder: 方法。
- 初始化完毕后会调用 awakeFromNib 方法。想做一些初始化操作,应该在这个方法中执行。
- 有时候希望在控件初始化时做一些初始化操作,比如添加子控件、设置基本属性,这时需要根据控件的创建方式,来选择在 initWithFrame:、initWithCoder:、awakeFromNib 的哪个方法中操作。
- 通过纯代码创建
8.1 纯代码自定义控件
- 1> 在 initWithFrame: 方法中添加子控件,提供便利构造方法(类方法)。
- 或者不使用 initWithFrame: 方法,而是使用子控件的懒加载方式,添加子控件。
- 2> 在 layoutSubviews 方法中设置子控件的 frame(一定要调用 super 的 layoutSubviews)。
- 控件的尺寸发生改变时会自动调用 layoutSubviews 方法,设置子控件的布局。
3> 增加模型属性,在模型属性 setter 方法中设置数据到子控件上。
Objective-C
ShopView.h
#import <UIKit/UIKit.h> /// 数据模型 @class ShopModel; @interface ShopView : UIView /// 商品模型 @property (nonatomic, strong) ShopModel *shop; /// 便利的构造方法 + (instancetype)shopView; @end
ShopView.m
在 initWithFrame: 方法中添加子控件
#import "ShopView.h" #import "ShopModel.h" @interface ShopView() /// 图片控件 @property (nonatomic, strong) UIImageView *iconView; /// 名字控件 @property (nonatomic, strong) UILabel *nameLabel; @end @implementation ShopView /// 便利的构造方法 + (instancetype)shopView { return [[self alloc] init]; } /** * 控件初始化方法 * init 方法内部会自动调用 initWithFrame: 方法 */ - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { self.backgroundColor = [UIColor orangeColor]; // 添加图片 UIImageView *iconView = [[UIImageView alloc] init]; iconView.backgroundColor = [UIColor blueColor]; [self addSubview:iconView]; self.iconView = iconView; // 添加文字 UILabel *nameLabel = [[UILabel alloc] init]; nameLabel.font = [UIFont systemFontOfSize:11]; nameLabel.textAlignment = NSTextAlignmentCenter; nameLabel.backgroundColor = [UIColor redColor]; [self addSubview:nameLabel]; self.nameLabel = nameLabel; } return self; } /** * 布局子控件 * 这个方法专门用来布局子控件,一般在这里设置子控件的 frame * 当控件本身的尺寸发生改变的时候,系统会自动调用这个方法 */ - (void)layoutSubviews{ // 一定要调用 super 的 layoutSubviews [super layoutSubviews]; CGFloat shopW = self.frame.size.width; CGFloat shopH = self.frame.size.height; self.iconView.frame = CGRectMake(0, 0, shopW, shopW); self.nameLabel.frame = CGRectMake(0, shopW, shopW, shopH - shopW); } /// 数据模型 setter 方法 - (void)setShop:(ShopModel *)shop { _shop = shop; self.nameLabel.text = shop.name; self.iconView.image = [UIImage imageNamed:shop.icon]; } @end
使用子控件的懒加载方法添加子控件
#import "ShopView.h" #import "ShopModel.h" @interface ShopView() /// 图片控件 @property (nonatomic, strong) UIImageView *iconView; /// 名字控件 @property (nonatomic, strong) UILabel *nameLabel; @end @implementation ShopView /// 便利的构造方法 + (instancetype)shopView { return [[self alloc] init]; } /// 子控件懒加载方法(getter 方法) - (UIImageView *)iconView { if (_iconView == nil) { UIImageView *iconView = [[UIImageView alloc] init]; iconView.backgroundColor = [UIColor blueColor]; [self addSubview:iconView]; _iconView = iconView; } return _iconView; } /// 子控件懒加载方法(getter 方法) - (UILabel *)nameLabel { if (_nameLabel == nil) { UILabel *nameLabel = [[UILabel alloc] init]; nameLabel.font = [UIFont systemFontOfSize:11]; nameLabel.textAlignment = NSTextAlignmentCenter; nameLabel.backgroundColor = [UIColor redColor]; [self addSubview:nameLabel]; _nameLabel = nameLabel; } return _nameLabel; } /** * 布局子控件 * 这个方法专门用来布局子控件,一般在这里设置子控件的 frame * 当控件本身的尺寸发生改变的时候,系统会自动调用这个方法 */ - (void)layoutSubviews{ // 一定要调用 super 的 layoutSubviews [super layoutSubviews]; CGFloat shopW = self.frame.size.width; CGFloat shopH = self.frame.size.height; self.iconView.frame = CGRectMake(0, 0, shopW, shopW); self.nameLabel.frame = CGRectMake(0, shopW, shopW, shopH - shopW); } /// 数据模型 setter 方法 - (void)setShop:(ShopModel *)shop { _shop = shop; self.nameLabel.text = shop.name; self.iconView.image = [UIImage imageNamed:shop.icon]; } @end
8.2 XIB 自定义控件
新建自定义控件类。
新建 xib 文件(文件名建议和自定义控件类的类名一致)。
修改 xib 中控件绑定的类名。
在类扩展中增加子控件的属性,关联 xib 中的子控件。封装 xib 的加载过程,增加模型属性,在模型属性 setter 方法中设置数据到子控件上。
Objective-C
ShopView.h
#import <UIKit/UIKit.h> @class ShopModel; @interface ShopView : UIView /** 模型数据 */ @property (nonatomic, strong) ShopModel *shop; + (instancetype)shopView; + (instancetype)shopViewWithShopModel:(ShopModel *)shop; @end
ShopView.m
#import "ShopView.h" #import "ShopModel.h" @interface ShopView() /** 图标 */ @property (weak, nonatomic) IBOutlet UIImageView *iconView; /** 名字 */ @property (weak, nonatomic) IBOutlet UILabel *nameLabel; @end @implementation ShopView + (instancetype)shopView { return [self shopViewWithShop:nil]; } + (instancetype)shopViewWithShop:(ShopModel *)shop { // 加载 xib 自定义控件 ShopView *shopView = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self) owner:nil options:nil] lastObject]; shopView.shop = shop; return shopView; } /// 数据模型 setter 方法 - (void)setShop:(ShopModel *)shop { _shop = shop; // 设置子控件的数据 self.iconView.image = [UIImage imageNamed:shop.icon]; self.nameLabel.text = shop.name; } @end
9、九宫格计算思路
Objective-C
// 设置列数 int cols = 3; // 设置每一个单元格的尺寸 CGFloat shopW = 50; CGFloat shopH = 70; // 设置每一列和每一行之间的间距 /* self.shopsView.frame.size.width 为父视图的宽度 */ CGFloat colMargin = (self.shopsView.frame.size.width - cols * shopW) / (cols - 1); CGFloat rowMargin = 10; // 计算单元格的索引 /* 计算将要添加的单元格的索引 */ NSUInteger index = self.shopsView.subviews.count; // 计算单元格的 x 坐标值 NSUInteger col = index % cols; CGFloat shopX = col * (shopW + colMargin); // 计算单元格的 y 坐标值 NSUInteger row = index / cols; CGFloat shopY = row * (shopH + rowMargin); // 创建一个单元格视图控件 UIView *shopView = [[UIView alloc] init]; // 设置单元格的 frame shopView.frame = CGRectMake(shopX, shopY, shopW, shopH); // 设置单元格视图控件的内容 shopView.shop = self.shops[index]; // 将单元格添加到父视图上 [self.shopsView addSubview:shopView];
10、UI 控件 weak 引用
我们知道,从 Storyboard 往编译器拖出来的 UI 控件的属性是 weak 的,那么,如果有一些 UI 控件我们要用代码的方式来创建,那么它应该用 weak 还是 strong 呢?为什么?
@property (weak, nonatomic) IBOutlet UIButton *myButton;
这是一道有意思的问题。简单来说,这道题并没有标准答案,但是答案背后的解释却非常有价值,能够看出一个人对于引用计数,对于 view 的生命周期的理解是否到位。
我们就能看到一些理解非常不到位的解释,例如说:Storyboard 拖线使用 weak 是为了规避出现循环引用的问题。
这个理解是错误的,Storyboard 拖出来的控件即使是 strong 的,也不会有循环引用问题。UI 控件用默认用 weak,根源还是苹果希望只有这些 UI 控件的父 View 来强引用它们,而 ViewController 只需要强引用 ViewController.view 成员,则可以间接持有所有的 UI 控件。这样有一个好处是:在以前,当系统收到 Memory Warning 时,会触发 ViewController 的 viewDidUnload 方法,这样的弱引用方式,可以让整个 view 整体都得到释放,也更方便重建时整体重新构造。但是首先 viewDidUnload 方法在 iOS 6 开始就被废弃掉了,苹果用了更简单有效地方式来解决内存警告时的视图资源释放。总之就是,除非你特殊地操作 view 成员,ViewController.view 的生命期和 ViewController 是一样的了。
所以在这种情况下,其实 UI 控件是不是 weak 其实关系并不大。当 UI 控件是 weak 时,它的引用计数是 1,持有它的是它的 superview,当 UI 控件是 strong 时,它的引用计数是 2,持有它的有两个地方,一个是它的 superview,另一个是这个 strong 的指针。UI 控件并不会持有别的对象,所以,不管是手写代码还是 Storyboard,UI 控件是 strong 都不会有循环引用的。
那么回到我们的最初的问题,自己写的 view 成员,应该用 weak 还是 strong?我个人觉得应该用 strong,因为用 weak 并没有什么特别的优势,其实 weak 变量会有额外的系统维护开销的,如果你没有使用它的特别的理由,那么用 strong 的话应该更好。另外如果你要做 Lazy 加载,那么你也只能选择用 strong。当然,如果你非要用 weak,其实也没什么问题,只需要注意在赋值前,先把这个对象用 addSubView 加到父 view 上,否则可能刚刚创建完,它就被释放了。
11、简述对 UIView、UIWindow 和 CALayer 的理解
UIView 对象定义了屏幕上的一个矩形区域,用于构建用户界面和响应用户触屏事件。一个 UIView 的实例可以包含和管理若干个子 UIView。UIView 的直接父类为 UIResponder 类,可以响应用户事件。
UIWindow 对象是所有 UIView 的根,管理和协调应用程序的显示。UIWindow 类是 UIView 的子类,可以看作是特殊的 UIView。一般应用程序只有一个 UIWindow 对象,即使有多个 UIWindow 对象,也只有一个 UIWindow 可以接受到用户的触屏事件。
UIViewController 对象负责管理所有 UIView 的层次结构,并处理用户的触摸事件。
UIScreen 可以获取设备屏幕的大小。
CALayer 直接从 NSObject 继承,因为缺少了 UIResponder 类,所以 CALayer 不能响应任何用户事件。QuartzCore 是 iOS 中提供图像绘制的基础库,并且 CALayer 是定义该框架中。CALayer 定义了 position、size、transform、animations 等基本属性。UIView 是基于 CALayer 的高层封装。UIView 相比 CALayer 最大区别是 UIView 可以响应用户事件,而 CALayer 不可以。UIView 侧重于对显示内容的管理,CALayer 侧重于对内容的绘制。UIView 和 CALayer 是相互依赖的关系。UIView 依赖与 calayer 提供的内容,CALayer 依赖 UIView 提供的容器来显示绘制的内容。归根到底 CALayer 是这一切的基础,如果没有 CALayer,UIView 自身也不会存在,UIView 是一个特殊的 CALayer 实现,添加了响应事件的能力。UIView 来自 CALayer,高于 CALayer,是 CALayer 高层实现与封装。UIView 的所有特性来源于 CALayer 支持。