iOS 常用控件 (temp)

UIKit 继承关系图

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 内容高度

计算高度一般等同于计算布局结果

  1. 手动布局高度(计算效率高;和xib中的布局信息需要保持一致,多处更新)
  2. 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:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容