UIKit 继承关系图
UIView
// 背景色
.backgroundColor
// 隐藏
.hidden
// 透明度
.alpha
// 不透明
.opaque
.tintColor
// 子视图是否能超出界面
.clipsToBounds
.transform
// 标签
.tag
- viewWithTag:
.multipleTouchEnabled
// 所属父 view
.superview
- removeFromSuperview
// 子 view
.subviews
- addSubview:
//
- didAddSubview:
- willRemoveSubview:
- willMoveToSuperview:
- didMoveToSuperview
- willMoveToWindow:
- didMoveToWindow
// 一般情况下,使用 - drawRect 时,会把 .opaque 设置为 NO
- drawRect:
// 通知矩形区域需要重新绘制
- setNeedsDisplay
- addGestureRecognizer:
- removeGestureRecognizer:
+ animateWithDuration:delay:options:animations:completion:
+ animateWithDuration:animations:completion:
+ animateWithDuration:animations:
+ animateKeyframesWithDuration:delay:options:animations:completion:
+ addKeyframeWithRelativeStartTime:relativeDuration:animations:
+ animateWithDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion:
.motionEffects
- addMotionEffect:
- removeMotionEffect:
- hitTest:withEvent:
- endEditing:
.canBecomeFocused
.focused
自定义 UIView( with xib file)
// 实现,前调 super
- awakeFromNib
设置自定义 UIView 高宽和取消顶部状态栏
在对应的 xib 文件中,选择对应 UIView ,点击 Attributes inspector — Simulated Metrics 中设置
Size — Freeform
Status Bar — None
Top Bar — None
Bottom Bar — None
关联 xib 视图界面与类文件
在对应的 xib 文件中,选择对应 UIView ,点击 Identity inspector — Custom Class 中设置
Class - 为对应的 Class 文件
xib 文件与 Class 关联的两种形式的区别
可以注意到,在 xib 文件中的 File’s Owner 亦可关联 Class,这种方式也是可以的
两者的区别在于:
- File’s Owner: 除了可以关联 UIView 类,也可以关联 UIViewController 。即关联 View 与 ViewController root view 的关系
- UIView Class:自定义 UIView 对象。即只能是指向 UIView
//返回的是 NSArray 数组,获取指定 xib 文件中的视图列表
//通常在 xib 文件中,只有一个根视图,所以用 .firstObject 来获取这个根视图
// owner 指 File’s Owner 指定的 Custom Class
[[NSBundle mainBundle] loadNibNamed:[nibName] owner:nil options:nil];
// 或者 UINib
//[[UINib nibWithNibName:@"TempView" bundle:nil] instantiateWithOwner:nil options:nil];
使用 File’s Owner 和 UIView Class 在指定 Custom Class 时,上述方法使用的区别
// File’s Owner:
// owner 必须指向当前这个 newView 的实例,也就是这个方法的执行必须这个自定义的 Custom View 中进行定义,也就是说我们要自定义一个初始化方法(initXXX),来返回这个 Custom View
TempView *newView = [[[NSBundle mainBundle] loadNibNamed:[nibName] owner:[newView instance] options:nil] firstObject];
// UIView Class:
// 可以在任意地方通过 nibName 来获取当前 xib 文件中的视图列表
TempView *tempView = [[[NSBundle mainBundle] loadNibNamed:[nibName] owner:nil options:nil] firstObject];
CALayer
UIView 有属性 .layer ,指向一个 CALayer 对象
我们可以自定义属性 .layerClass 的 getter 来返回一个自定义的 CALayer 子类
CALayer 是什么
- 图层
- 屏幕上显示和动画
- UIView 背后的TA
// 当我们设置
view1.backgroundColor = ...
view1.frame = ...
view2.layer.backgroundColor = ...
view2.layer.frame = ...
// 是一样的
CALayer 可以做什么
- 显示(阴影、圆角、边框、遮罩、变换……)
- 动画
CALayer vs UIView
- CALayer 是 UIView 的内部实现细节
- Similar hierarchy
Layer 和 UIView 有相同的层级树,也就是说,当 view1 为 view2 的子视图时,view1.layer 也为 view2.layer 的子 layer - CALayer 不处理事件 。CALayer 继承于 NSObject,UIView 继承于 UIResponder
选择:
- UIView 能满足绝大部分简单的绘制需求、动画需求
- CALayer 提供更多的灵活性
自定义 Layer
- 自定义 CALayer 子类,实现 - drawInContext: 方法,根据参数的 CGContextRef 上下文绘制
- 在自定义的 UIView 子类中实现 <CALayerDelegate> 协议方法 - drawLayer:inContext:
当实现了 - drawLayer:inContext: 方法时,- drawRect: 方法失效
假如在 UIView 的 - drawRect: 方法中主动调用 [self.layer drawInContext:ctx],因为 self.layer 并没有实现自己的 - drawInContext: 方法,所以 self.layer 会向它的 delegate 也就是 UIView 有没有实现 - drawLayer:inContext: 方法,如果 UIView 没有实现 - drawLayer:inContext: 方法,就会回到 - drawRect: 方法,即引起死循环 - CALayer 有属性 .delegate ,可传入实现 <CALayerDelegate> 协议方法 - drawLayer:inContext: 的对象
CALayer 显示的实现
CALayer 的坐标系也是以左上角为原点,也有属性 .frame 和 .bounds ,中心是 .position
.sublayers
- addSublayer:
// 背景色,CGColorRef
.backgroundColor
// 边框颜色
.borderColor
// 边框宽度
.borderWidth
// 圆角
.cornerRadius
// 子 layer 是否能超出父 layer 范围
.masksToBounds
/*
锚点,如图钉和一面白纸,用图钉把白纸钉在墙上,这张纸可以围绕着图钉旋转,图钉在这张纸上的位置就是 anchorPoint , anchorPoint 范围为 0 - 1 ,表示这个点在这张白纸上的相对位置,默认为(0.5,0.5),也就是中心(position)位置,position 为 layer 的中 anchorPoint 在 super layer 中的位置坐标,position 的计算公式:
layer.position.x = layer.frame.origin.x + layer.anchorPoint.x * layer.bounds.size.width;
layer.position.y = layer.frame.origin.y + layer.anchorPoint.y * layer.bounds.size.height;
*/
.anchorPoint
CALayer *layer1 = [[CALayer alloc] init];
layer1.backgroundColor = [UIColor orangeColor].CGColor;
layer1.frame = CGRectMake(100, 100, 150, 150);
layer1.borderColor = [UIColor redColor].CGColor;
layer1.borderWidth = 2;
layer1.cornerRadius = 10;
layer1.masksToBounds = YES;
layer1.anchorPoint = CGPointMake(0, 0);
CALayer *layer2 = [[CALayer alloc] init];
layer2.backgroundColor = [UIColor blueColor].CGColor;
layer2.frame = CGRectMake(100, 100, 150, 150);
layer2.borderColor = [UIColor redColor].CGColor;
layer2.borderWidth = 2;
layer2.cornerRadius = 10;
layer2.masksToBounds = YES;
layer2.anchorPoint = CGPointMake(0.5, 0.5);
NSLog(@"layer1 position %@", NSStringFromCGPoint(layer1.position));
NSLog(@"layer1 frame %@", NSStringFromCGRect(layer1.frame));
NSLog(@"layer2 position %@", NSStringFromCGPoint(layer2.position));
NSLog(@"layer2 frame %@", NSStringFromCGRect(layer2.frame));
[self.view.layer addSublayer:layer1];
[self.view.layer addSublayer:layer2];
// 打印结果为:
layer1 position {175, 175}
layer1 frame {{175, 175}, {150, 150}}
layer2 position {175, 175}
layer2 frame {{100, 100}, {150, 150}}
CALayer *layer1 = [[CALayer alloc] init];
layer1.backgroundColor = [UIColor orangeColor].CGColor;
layer1.frame = CGRectMake(100, 100, 150, 150);
layer1.borderColor = [UIColor redColor].CGColor;
layer1.borderWidth = 2;
layer1.cornerRadius = 10;
layer1.masksToBounds = YES;
layer1.anchorPoint = CGPointMake(0, 0);
CALayer *layer2 = [[CALayer alloc] init];
layer2.backgroundColor = [UIColor blueColor].CGColor;
layer2.frame = CGRectMake(100, 100, 150, 150);
layer2.borderColor = [UIColor redColor].CGColor;
layer2.borderWidth = 2;
layer2.cornerRadius = 10;
layer2.masksToBounds = YES;
layer2.anchorPoint = CGPointMake(0.5, 0.5);
NSLog(@"layer1 position %@", NSStringFromCGPoint(layer1.position));
NSLog(@"layer1 frame %@", NSStringFromCGRect(layer1.frame));
NSLog(@"layer2 position %@", NSStringFromCGPoint(layer2.position));
NSLog(@"layer2 frame %@", NSStringFromCGRect(layer2.frame));
//
layer1.position = CGPointMake(100, 100);
NSLog(@"layer1 position %@", NSStringFromCGPoint(layer1.position));
NSLog(@"layer1 frame %@", NSStringFromCGRect(layer1.frame));
[self.view.layer addSublayer:layer1];
[self.view.layer addSublayer:layer2];
// 打印结果为:
layer1 position {175, 175}
layer1 frame {{175, 175}, {150, 150}}
layer2 position {175, 175}
layer2 frame {{100, 100}, {150, 150}}
layer1 position {100, 100}
layer1 frame {{100, 100}, {150, 150}}
CALayer *layer1 = [[CALayer alloc] init];
layer1.backgroundColor = [UIColor orangeColor].CGColor;
layer1.frame = CGRectMake(100, 100, 150, 150);
layer1.borderColor = [UIColor redColor].CGColor;
layer1.borderWidth = 2;
layer1.cornerRadius = 10;
layer1.masksToBounds = YES;
layer1.anchorPoint = CGPointMake(0, 0);
CALayer *layer2 = [[CALayer alloc] init];
layer2.backgroundColor = [UIColor blueColor].CGColor;
layer2.frame = CGRectMake(100, 100, 150, 150);
layer2.borderColor = [UIColor redColor].CGColor;
layer2.borderWidth = 2;
layer2.cornerRadius = 10;
layer2.masksToBounds = YES;
layer2.anchorPoint = CGPointMake(0.5, 0.5);
NSLog(@"layer1 position %@", NSStringFromCGPoint(layer1.position));
NSLog(@"layer1 frame %@", NSStringFromCGRect(layer1.frame));
NSLog(@"layer2 position %@", NSStringFromCGPoint(layer2.position));
NSLog(@"layer2 frame %@", NSStringFromCGRect(layer2.frame));
//
layer1.frame = CGRectMake(100, 100, 150, 150);
NSLog(@"layer1 position %@", NSStringFromCGPoint(layer1.position));
NSLog(@"layer1 frame %@", NSStringFromCGRect(layer1.frame));
[self.view.layer addSublayer:layer1];
[self.view.layer addSublayer:layer2];
// 打印结果为:
layer1 position {175, 175}
layer1 frame {{175, 175}, {150, 150}}
layer2 position {175, 175}
layer2 frame {{100, 100}, {150, 150}}
layer1 position {100, 100}
layer1 frame {{100, 100}, {150, 150}}
// CGImageRef ,显示 Image 内容
.contents
// 标记 layer 的 contents 需要更新
- setNeedsDisplay
// 对 layer 的 contents 的转换,CATransform3D 结构体
.transform
UIView.transform vs CALayer.transform
区别:2D vs 3D
- CALayer.transform属性是是个CATransform3D类型的数据,默认值为CATransform3DIdentity
- CGAffineTransform 是用于2D层面的, 操作的是UIView或者其他 2D Core Graphics 元素。
- CATransform3D 是 Core Animation 的结构体,是用来做更复杂的关于 CALayer 的 3D 操作。
- CATransform3D 定义了一个三维变换(4x4的矩阵),用于图层的偏移、旋转,缩放,歪斜和透视等效果。
- 需要了解相关的3D变换方法:CATransform3DMakeTranslation,CATransform3DMakeScale,CATransform3DMakeRotation,以及CATransform3DTranslate,CATransform3DScale,CATransform3DRotate。
- CATransform3D.m34 表示透视效果,但需要和 CATransform3DRotate 配合使用才有效果。也就是前提是z方向上有变化(即沿x轴或者y轴旋转之后)。
不规则图片( Mask Layer )的遮罩
// layer 中有一个mask属性,它本身也是一个CALayer对象,我们将layer本身称为content layer,将mask的layer称为mask layer
.mask
遮罩的原理,就是将 alpha channel 去定义 content layer 的显示区域
Alpha | Visibility |
---|---|
1 | 可见 |
0-1 | 半透明 |
0 | 不可见 |
mask layer 虽然是 CALayer 对象,但只有透明度信息是有用的,它本身的颜色信息是会被忽略的
mask layer 本身是没有 super layer 的
[UIColor clearColor] 的 Alpha 为 0
UIImageView *imageView = [[UIImageView alloc] init];
imageView.image = [UIImage imageNamed:@"chatImage.jpg"];
imageView.contentMode = UIViewContentModeScaleAspectFill;
imageView.frame = CGRectMake(50, 50, 200, 250);
[self.view addSubview:imageView];
UIImageView *imageViewMask = [[UIImageView alloc] init];
imageViewMask.image = [[UIImage imageNamed:@"imageMask.png"] stretchableImageWithLeftCapWidth:18 topCapHeight:16];
imageViewMask.frame = imageView.bounds;
imageViewMask.alpha = 1;
imageView.layer.mask = imageViewMask.layer;
CAGradientLayer 渐变
- CAGradientLayer 的坐标系以左上角为 (0,0) ,右下角为 (1,1)
- 渐变颜色 -- 至少2个色值
- 可以有透明色
CAGradientLayer *layer = [CAGradientLayer layer];
layer.frame = CGRectMake(100, 100, 150, 150);
[layer setColors:@[
(id)[UIColor yellowColor].CGColor,
(id)[UIColor greenColor].CGColor,
(id)[UIColor blueColor].CGColor
]];
//渐变分割线,默认等分
[layer setLocations:@[@0.25, @0.5, @0.75]];
// 设置起始点和终点,如下代码即为水平方向从左往右的变化
[layer setStartPoint:CGPointMake(0, 0)];
[layer setEndPoint:CGPointMake(1, 0)];
[self.view.layer addSublayer:layer];
//这里从0-0.25都是纯黄色,从0.75到1都是纯蓝色,
CAGradientLayer 作为 mask layer
CAGradientLayer 通常用作 mask layer
//镜像效果
UIImageView *imageView = [[UIImageView alloc] init];
imageView.image = [UIImage imageNamed:@"nature.jpg"];
imageView.contentMode = UIViewContentModeScaleAspectFill;
imageView.frame = CGRectMake(100, 100, 150, 100);
[self.view addSubview:imageView];
UIImageView *mirrorImageView = [[UIImageView alloc] init];
mirrorImageView.image = imageView.image;
mirrorImageView.contentMode = UIViewContentModeScaleAspectFill;
//将y为-1实现翻转效果
mirrorImageView.transform = CGAffineTransformMakeScale(1, -1);
mirrorImageView.bounds = imageView.bounds;
mirrorImageView.center = CGPointMake(imageView.center.x, imageView.center.y + imageView.bounds.size.height);
[self.view addSubview:mirrorImageView];
//遮罩
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = CGRectMake(0, 0, imageView.frame.size.width, imageView.frame.size.height);
[gradientLayer setColors:@[(id)[UIColor clearColor].CGColor,
(id)[UIColor colorWithWhite:0 alpha:0.4].CGColor]];
//虽然mirrorImageView翻转了,但是startPoint和endPoint还是按原图来算
gradientLayer.startPoint = CGPointMake(0, 0.7);
gradientLayer.endPoint = CGPointMake(0, 1);
mirrorImageView.layer.mask = gradientLayer;
CAShapeLayer
用来绘制形状,用来画各种形状,如圆形,曲线……
// 绘制形状的路径,CGPathRef
.path
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
// Core Graphics
// 指定绘制路径
// 创建路径对象
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, nil, 50, 200);
// 添加曲线
CGPathAddCurveToPoint(path, nil, 100, 100, 250, 300, 300, 200);
shapeLayer.path = path;
CGPathRelease(path);
[self.view.layer addSublayer:shapeLayer];
或者
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.strokeColor = [UIColor orangeColor].CGColor;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
CGPoint startPoint = CGPointMake(50, 200);
CGPoint endPoint = CGPointMake(300, 200);
CGPoint controlPoint1 = CGPointMake(100, 100);
CGPoint controlPoint2 = CGPointMake(250, 300);
// UIBezierPath 贝塞尔曲线
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:startPoint];
[path addCurveToPoint:endPoint controlPoint1:controlPoint1 controlPoint2:controlPoint2];
shapeLayer.path = path.CGPath;
[self.view.layer addSublayer:shapeLayer];
CAShapeLayer 作为 mask layer
UIView *bgView = [[UIView alloc] init];
bgView.backgroundColor = [UIColor clearColor];
bgView.frame = CGRectMake(50, 100, 300, 200);
[self.view addSubview:bgView];
UIImageView *imageView = [[UIImageView alloc] init];
imageView.image = [UIImage imageNamed:@"nature.jpg"];
imageView.contentMode = UIViewContentModeScaleAspectFill;
imageView.frame = bgView.bounds;
[bgView addSubview:imageView];
UIBezierPath *maskPath = [UIBezierPath bezierPath];
[maskPath moveToPoint:CGPointMake(0, 0)];
CGFloat curveHeight = 40;
CGFloat curveBeginHeight = imageView.frame.size.height - curveHeight;
[maskPath addLineToPoint:CGPointMake(0, curveBeginHeight)];
CGPoint curveEndPoint = CGPointMake(imageView.frame.size.width, imageView.frame.size.height - curveHeight);
CGPoint controlPoint = CGPointMake(imageView.frame.size.width / 2, imageView.frame.size.height + 20);
//
[maskPath addQuadCurveToPoint:curveEndPoint controlPoint:controlPoint];
[maskPath addLineToPoint:CGPointMake(imageView.frame.size.width, 0)];
//
[maskPath closePath];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = imageView.bounds;
maskLayer.path = maskPath.CGPath;
bgView.layer.mask = maskLayer;
CAShapeLayer Animation
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.frame = CGRectMake(100, 100, 100, 100);
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:shapeLayer.bounds];
shapeLayer.path = path.CGPath;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.lineWidth = 2.0f;
shapeLayer.strokeColor = [UIColor orangeColor].CGColor;
shapeLayer.strokeEnd = 0;
[self.view.layer addSublayer:shapeLayer];
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
pathAnimation.duration = 3.0f;
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
//
[shapeLayer addAnimation:pathAnimation forKey:@"strokeEndAnimation"];
CATextLayer
与 UILabel 相比,CATextLayer 有更好的性能表现,因为 CATextLayer 更底层
CATextLayer *textLayer = [CATextLayer layer];
textLayer.contentsScale = [UIScreen mainScreen].scale;
// 文字的颜色,只有当不是富文本时才会生效
textLayer.foregroundColor = [UIColor blackColor].CGColor;
textLayer.backgroundColor = [UIColor orangeColor].CGColor;
// 换行
textLayer.wrapped = YES;
// 对齐方式
textLayer.alignmentMode = kCAAlignmentLeft;
//font
UIFont *font = [UIFont systemFontOfSize:12];
CGFontRef fontRef = CGFontCreateWithFontName((__bridge CFStringRef)font.fontName);
textLayer.font = fontRef;
textLayer.fontSize = font.pointSize;
//这里注意 Release
CGFontRelease(fontRef);
textLayer.frame = CGRectMake(50, 50, 200, 200);
NSString *text = @"哇哈哈哈哈哈哈哈哈哇哈哈哈哈哈哈哈哈哇哈哈哈哈哈哈哈哈哇哈哈哈哈哈哈哈哈哇哈哈哈哈哈哈哈哈哇哈哈哈哈哈哈哈哈哇哈哈哈哈哈哈哈哈哇哈哈哈哈哈哈哈哈";
// 富文本
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:text];
[string addAttribute:(NSString *)kCTForegroundColorAttributeName
value:(__bridge id)[UIColor yellowColor].CGColor
range:NSMakeRange(1, 2)];
[string addAttribute:(NSString *)kCTFontAttributeName
value:[UIFont fontWithName:@"Arial" size:20]
range:NSMakeRange(1, 2)];
NSDictionary *attrs = @{(__bridge id)kCTUnderlineStyleAttributeName:@(kCTUnderlineStyleSingle),
(__bridge id)kCTForegroundColorAttributeName:(__bridge id)[UIColor blueColor].CGColor};
[string setAttributes:attrs range:NSMakeRange(text.length - 5, 4)];
textLayer.string = string;
[self.view.layer addSublayer:textLayer];
UILabel
.text
.highlightedTextColor
.highlighted
.attributedText
.font
.textColor
.textAlignment
.shadowColor
.shadowOffset
.adjustsFontSizeToFitWidth
.minimumScaleFactor
.numberOfLines
.preferredMaxLayoutWidth
UILabel 高度的计算
- [UIView class] 的 - sizeThatFits:
- [NSString class] 的 - boundingRectWithSize:options:attributes:context:
//options -- NSStringDrawingOptions 枚举
//attributes -- 主要告知绘制区域需要绘制文本的字符大小
//context -- 设置字符间距 - 基于 AutoLayout 的 - systemLayoutSizeFittingSize:
使用这个方法的前提条件是展示这个控件的约束必须完美,也就是必须完整地约束上下左右四个方向
使用这个方法前必须设置 .preferredMaxLayoutWidth 用来约束换行操作,当内容超过约束区域时就会自动换行,并且更新约束布局,preferredMaxLayoutWidth 就是告诉最大的参考宽度
参数 targetSize 是 CGSize 类型,系统为我们提供了两个固定的值,分别为 UILayoutFittingCompressedSize( 在保证适当尺寸前提下,尽量压缩 CGSize 的值 ) 和 UILayoutFittingExpandedSize( 在保证适当尺寸前提下,尽量扩充 CGSize 的值 )
NSString *str = @"hahaha";
_textLabel.numberOfLines = 0;
_textLabel.text = str;
//
CGSize size = [_textLabel sizeThatFits:CGSizeMake(CGRectGetWidth(self.view.bounds), CGFLOAT_MAX)];
// NSStringDrawingUsesLineFragmentOrigin -- 当前字符串需要换行来计算高度
CGRect rect = [str boundingRectWithSize:CGSizeMake(CGRectGetWidth(self.view.bounds), CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: _textLabel.font} context:nil];
// 输出为 label size is {413.33333333333331, 203}
NSLog(@"label size is %@", NSStringFromCGSize(size));
//输出为 str rect is {{0, 0}, {413.32080078125, 202.87109375}}
NSLog(@"str rect is %@", NSStringFromCGRect(rect));
_textLabel.frame = CGRectMake(0, 40, size.width, size.height);
//
UIImageView
在 Interface Builder 中,选择对应的 UIImageView,然后点击 Editor — SizeToFitContent,可自动适应图片大小显示
// 图片的拉伸
.contentMode
ScaleToFill — 适配整个高宽
ScaleAspectFit — 保持图片比例,撑满最短边
ScaleAspectFill — 保持图片比例,撑满最长边,多余的部分修剪掉
UIImage
// 可以指定图片中某一块区域进行拉伸或者平铺
- resizableImageWithCapInsets:
- resizableImageWithCapInsets:resizingMode:
// 可对图片的位置进行调整
- imageWithAlignmentRectInsets:
// 改变图片的渲染效果
- imageWithRenderingMode:
// Deprecated
- stretchableImageWithLeftCapWidth:topCapHeight:
UIButton
UITextField
关闭键盘
[UIResponder class]
// 呼出键盘
- becomeFirstResponder
// 隐藏键盘
- resignFirstResponder
关闭键盘可以有几种方法:
- 可以获取到 UITextField 对象,则直接调用 - resignFirstResponder
[_textField resignFirstResponder];
- 父 view 执行 - endEditing
[self.view endEditing:YES];
- 当前 window 执行 - endEditing
[[UIApplication sharedApplication].keyWindow endEditing:YES];
- UIApplication 实例执行 - sendAction:to:from:forEvent:
// 可以将 action 沿着响应链传递
// - sendAction:to:from:forEvent:
[[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder) to:nil from:nil forEvent:nil];
UIApplication 的 sendAction:to:from:forEvent: 和 UIControl 的 sendAction:to:forEvent: 方法之间的关联:
对于一个给定的事件,UIControl 会调用 sendAction:to:forEvent: 来将行为消息转发到 UIApplication 对象,再由 UIApplication 对象调用其 sendAction:to:fromSender:forEvent: 方法来将消息分发到指定的 target 上,而如果我们没有指定 target ,则会将事件分发到响应链上第一个想处理消息的对象上。而如果子类想监控或修改这种行为的话,则可以重写这个方法。
UIScrollView
// 滚动区域的大小
.contentSize
// 当前滚动的具体位置
.contentOffset
// 是否允许点击 status 滚动到顶部
.scrollsToTop
// 分页
.pagingEnabled
// 允许滚动
.scrollEnabled
// 滚动弹性
.bounces
// 单设 contentInset 滚动条范围包含偏移量,如果希望滚动条跟随偏移量,也要设置 scrollIndicatorInset
//
.contentInset
//
.scrollIndicatorInset
// 滚动到指定位置
- setContentOffset:animated:
- scrollRectToVisible:animated:
// 放大缩小的范围
.maximumZoomScale
.minimumZoomScale
// 需先设置最大、最小 和 <UIScrollViewDelegate> 的 - viewForZoomingInScrollView: 来确定要放大缩小的 view
.zoomScale
- setZoomScale:animated:
- UIScrollView 默认存在子视图(两个 imageView)—— 上下和左右滚动条
- UIScrollView 在使用约束布局时,不需要使用 contentSize 来设置内容大小,因为 UIScrollView 中的每个视图都需要完整约束,第一个子视图必需与 UIScrollView 的顶部进行关联,最后一个子视图与底部进行关联,从而撑开 UIScrollView 的高度,UIScrollView 的宽度也是直接读取内容的宽度
- UIScrollView 通过改变 bounds 实现滚动
UIScrollView 内部子视图约束
- UIScrollView 内部子视图的尺寸不能以 UIScrollView 的尺寸为参照
- UIScrollView 内部的子视图的约束必需要完整
判断 UIScrollView 的内容超出了屏幕
可以通过判断 UIScrollView 子视图的坐标位置是否超出当前屏幕的区域,这涉及到坐标转换
- convertPoint:toView:
- convertPoint:fromView:
- convertRect:toView:
- convertRect:fromView:
<UIScrollViewDelegate>
获取滚动中的各种状态
// 滚动回调
- scrollViewDidScroll:
// 拖拽回调
- scrollViewWillBeginDragging:
- scrollViewDidEndDragging:willDecelerate:
// 减速回调
- scrollViewWillBeginDecelerating:
- scrollViewDidEndDecelerating:
// 缩放回调
- scrollViewDidZoom:
UITableView
UITableView 本质上是 UIScrollView + 二维数组的控件
// style -- Plain — section header 和 footer 是浮在Cell上;Grouped — section header 和 footer 是不能浮动的,有缺省的背景色
- initWithFrame:style:
// ios9之前,在dealloc中,需要将delegate和dataSource设置为nil
// <UITableViewDataSource> ,UITableView 数据的来源
.dataSource
<UITableViewDataSource> 协议方法
// section 相关
- numberOfSectionsInTableView:
- tableView:titleForHeaderInSection:
- tableView:titleForFooterInSection:
// tableView 右侧的 section 导航栏
- sectionIndexTitlesForTableView:
// row 相关(必须)
- tableView:numberOfRowsInSection:
- tableView:cellForRowAtIndexPath:
// <UITableViewDelegate>
.delegate
// 高度相关
- tableView:heightForRowAtIndexPath:
- tableView:estimatedHeightForRowAtIndexPath:
- tableView:heightForHeaderInSection:
- tableView:estimatedHeightForHeaderInSection:
- tableView:heightForFooterInSection:
- tableView:estimatedHeightForFooterInSection:
// Section View 相关
- tableView:viewForHeaderInSection:
- tableView:viewForFooterInSection:
// 选中相关
- tableView:willSelectRowAtIndexPath:
- tableView:didSelectRowAtIndexPath:
- tableView:willDeselectRowAtIndexPath:
- tableView:didDeselectRowAtIndexPath:
- tableView:shouldHighlightRowAtIndexPath:
- tableView:didHighlightRowAtIndexPath:
// 是否允许多选
.allowsMultipleSelection
// 重新加载数据
- reloadData
// 分割线相关
.separatorStyle
.separatorColor
.separatorEffect
.separatorInset
// 右侧 section 导航条样式定义
.sectionIndexColor
.sectionIndexBackgroundColor
.sectionIndexTrackingBackgroundColor
- UITableView 数据不够满页时,会出现多余的分割线。解决方式有:
- 设置Section Footer
- 设置tableFooterView
.tableHeaderView
.tableFooterView
UITableViewController
封装了 TableView、 DataSource、 Delegate,它的 view == tableView
- initWithStyle:
- initWithNibName:bundle:
UITableView 界面的定制
UITableViewCell 系统的 style
UITableViewCell 继承于 UIView 。系统提供了四种 UITableViewCell 的 style -- UITableViewCellStyleDefault、UITableViewCellStyleValue1、UITableViewCellStyleValue2、UITableViewCellStyleSubtitle
则对应不同的 style 的属性有:
// cell 旁边的箭头
.accessoryType / .accessoryView
.detailTextLabel
// 分割线
.separator
.textLabel
.imageView
- UITableViewCell 的结构
UITableViewCell 包含一个 contentView,自定义 View 加在 contentView 上。UITableViewCell 在编辑状态下,contentView 外面会存在各种系统定义 View
自定义 UITableViewCell
UITableViewCell 内容高度
计算高度一般等同于计算布局结果
- 手动布局高度(计算效率高;和xib中的布局信息需要保持一致,多处更新)
- AutoLayout 布局计算(计算效率低;不需要获取布局信息,容易维护更新)
- systemLayoutSizeFittingSize:
- systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:
UITableViewCell 和 Section Header / Footer 的复用
UITableView 中的 UITableViewCell 重用 API (初始化方法):
- initWithStyle:reuseIdentifier:
UITableView 的 UITableViewCell 注册方法:
// 从 nib 文件中注册,针对 xib 文件布局
- registerNib:forCellResueIdentifier:
// 从 class 文件中注册,针对代码布局,调用到 - initWithStyle:reuseIdentifier:
- registerClass:forCellResueIdentifier:
重用:
- dequeueReusableCellWithIdentifier:
- dequeueReusableCellWithIndentifier:forIndexPath:
UITableView 中的 Section Header / Footer 重用 API (初始化方法):
[UITableViewHeaderFooterView class]
- initWithReuseIdentifier:
UITableView 的 Section Header / Footer 注册方法:
// 从 nib 文件中注册,针对 xib 文件布局
- registerNib:forHeaderFooterViewReuseIdentifier:
// 从 class 文件中注册,针对代码布局,调用到 - initWithReuseIdentifier:
- registerClass:forHeaderFooterViewReuseIdentifier:
重用:
- dequeueReusableHeaderFooterViewWithIdentifier:
经过重用后, UITableView 只生成和展示能看到的 Cell
.visibleCells
.indexPathsForVisibleRows
系统是如何判断 Cell 是 visible 的 ?
- 通过 contentOffset 和 heightForRowAtIndexPath
- 每次 UITableView 滚动的时候,需要重新判断
- contentSize 依赖 heightForRowAtIndexPath
这样会导致 heightForRowAtIndexPath 调用次数非常多,即在 AutoLayout 布局计算下计算效率低的原因
UITableView 预估高度
- tableView:estimatedHeightForRowAtIndexPath:
- tableView:estimatedHeightForHeaderInSection:
- tableView:estimatedHeightForFooterInSection:
优点:降低了heightForRowAtIndexPath调用次数
缺点:如果 contentSize 预估不准,scrollView 的滚动条可能会跳动
Self-sizing Cells
iOS8以后,提出Self-sizing Cells,即 Cell 的高度是由 Cell 自己计算的,我们不需要提供计算高度的方法,也不需要设置 preferredMaxLayoutWidth,支持 AutoLayout 和 Frame Layout 的方式
self.tableView.estimatedRowHeight = estimatedRowHeight;
self.tableView.rowHeight = UITableViewAutomaticDimension; (默认值)
UITableView 的编辑
列表可以编辑 -- 删除、新增、移动
左滑操作菜单 -- 菜单项可以自定义
选择
进入编辑界面
.editing
- setEditing:animated:
// 显示在 UITableViewCell 左侧,返回 UITableViewCellEditingStyle 枚举值 None、Delete(删除)、Insert(插入)、Delete | Insert (可选)
- tableView:editingStyleForRowAtIndexPath:
// 显示在 UITableViewCell 右侧,是否可移动
- tableView:canMoveRowAtIndexPath:
// 显示在 UITableViewCell 左滑后的操作菜单,返回 UITableViewRowAction 对象数组
- tableView:editActionsForRowAtIndexPath:
// 删除、新增数据 —— <UITableViewDataSource> 回调,实现
- tableView:commitEditingStyle:forRowAtIndexPath:
// 移动数据 —— <UITableViewDataSource> 回调,实现
- tableView:moveRowAtIndexPath:toIndexPath:
// 选择数据
.indexPathForSelectedRow
.indexPathsForSeletedRows
// 通知 UITableView 展示更新,调用
- insertRowsAtIndexPaths:withRowAnimation:
- deleteRowsAtIndexPaths:withRowAnimation:
- reloadRowsAtIndexPaths:withRowAnimation:
- moveRowAtIndexPath:toIndexPath:
//同时展示删除、新增多个更新动作
// beginUpdates 和 endUpdates 包起来
- beginUpdates
- endUpdates
// 或者通知 tableView 更新
- reloadData
UITableView 高亮和选中
// 实现
- tableView:didHighlightRowAtIndexPath:
- tableView:didUnhighlightRowAtIndexPath:
- tableView:didSelectRowAtIndexPath:
- tableView:didDeselectRowAtIndexPath:
// 是否允许多选
.allowMultipleSelectiton
对于 UITableViewCell
.selected
.highlighted
//选中样式
.selectionStyle
// 实现,前调 super
- setSelected:animated:
- setHighlighted:animated: