UIKit 繼承關(guān)系圖

UIView
// 背景色
.backgroundColor
// 隱藏
.hidden
// 透明度
.alpha
// 不透明
.opaque
.tintColor
// 子視圖是否能超出界面
.clipsToBounds
.transform
// 標(biāo)簽
.tag
- viewWithTag:
.multipleTouchEnabled
// 所屬父 view
.superview
- removeFromSuperview
// 子 view
.subviews
- addSubview:
//
- didAddSubview:
- willRemoveSubview:
- willMoveToSuperview:
- didMoveToSuperview
- willMoveToWindow:
- didMoveToWindow
// 一般情況下,使用 - drawRect 時(shí),會(huì)把 .opaque 設(shè)置為 NO
- drawRect:
// 通知矩形區(qū)域需要重新繪制
- 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)
// 實(shí)現(xiàn),前調(diào) super
- awakeFromNib
設(shè)置自定義 UIView 高寬和取消頂部狀態(tài)欄
在對(duì)應(yīng)的 xib 文件中,選擇對(duì)應(yīng) UIView ,點(diǎn)擊 Attributes inspector — Simulated Metrics 中設(shè)置
Size — Freeform
Status Bar — None
Top Bar — None
Bottom Bar — None

關(guān)聯(lián) xib 視圖界面與類文件
在對(duì)應(yīng)的 xib 文件中,選擇對(duì)應(yīng) UIView ,點(diǎn)擊 Identity inspector — Custom Class 中設(shè)置
Class - 為對(duì)應(yīng)的 Class 文件

xib 文件與 Class 關(guān)聯(lián)的兩種形式的區(qū)別
可以注意到,在 xib 文件中的 File’s Owner 亦可關(guān)聯(lián) Class,這種方式也是可以的
兩者的區(qū)別在于:
- File’s Owner: 除了可以關(guān)聯(lián) UIView 類,也可以關(guān)聯(lián) UIViewController 。即關(guān)聯(lián) View 與 ViewController root view 的關(guān)系
- UIView Class:自定義 UIView 對(duì)象。即只能是指向 UIView
//返回的是 NSArray 數(shù)組,獲取指定 xib 文件中的視圖列表
//通常在 xib 文件中,只有一個(gè)根視圖,所以用 .firstObject 來(lái)獲取這個(gè)根視圖
// 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 時(shí),上述方法使用的區(qū)別
// File’s Owner:
// owner 必須指向當(dāng)前這個(gè) newView 的實(shí)例,也就是這個(gè)方法的執(zhí)行必須這個(gè)自定義的 Custom View 中進(jìn)行定義,也就是說(shuō)我們要自定義一個(gè)初始化方法(initXXX),來(lái)返回這個(gè) Custom View
TempView *newView = [[[NSBundle mainBundle] loadNibNamed:[nibName] owner:[newView instance] options:nil] firstObject];
// UIView Class:
// 可以在任意地方通過(guò) nibName 來(lái)獲取當(dāng)前 xib 文件中的視圖列表
TempView *tempView = [[[NSBundle mainBundle] loadNibNamed:[nibName] owner:nil options:nil] firstObject];
CALayer
UIView 有屬性 .layer ,指向一個(gè) CALayer 對(duì)象
我們可以自定義屬性 .layerClass 的 getter 來(lái)返回一個(gè)自定義的 CALayer 子類
CALayer 是什么
- 圖層
- 屏幕上顯示和動(dòng)畫
- UIView 背后的TA
// 當(dāng)我們?cè)O(shè)置
view1.backgroundColor = ...
view1.frame = ...
view2.layer.backgroundColor = ...
view2.layer.frame = ...
// 是一樣的
CALayer 可以做什么
- 顯示(陰影、圓角、邊框、遮罩、變換……)
- 動(dòng)畫
CALayer vs UIView
- CALayer 是 UIView 的內(nèi)部實(shí)現(xiàn)細(xì)節(jié)
- Similar hierarchy
Layer 和 UIView 有相同的層級(jí)樹(shù),也就是說(shuō),當(dāng) view1 為 view2 的子視圖時(shí),view1.layer 也為 view2.layer 的子 layer - CALayer 不處理事件 。CALayer 繼承于 NSObject,UIView 繼承于 UIResponder
選擇:
- UIView 能滿足絕大部分簡(jiǎn)單的繪制需求、動(dòng)畫需求
- CALayer 提供更多的靈活性
自定義 Layer
- 自定義 CALayer 子類,實(shí)現(xiàn) - drawInContext: 方法,根據(jù)參數(shù)的 CGContextRef 上下文繪制
- 在自定義的 UIView 子類中實(shí)現(xiàn) <CALayerDelegate> 協(xié)議方法 - drawLayer:inContext:
當(dāng)實(shí)現(xiàn)了 - drawLayer:inContext: 方法時(shí),- drawRect: 方法失效
假如在 UIView 的 - drawRect: 方法中主動(dòng)調(diào)用 [self.layer drawInContext:ctx],因?yàn)?self.layer 并沒(méi)有實(shí)現(xiàn)自己的 - drawInContext: 方法,所以 self.layer 會(huì)向它的 delegate 也就是 UIView 有沒(méi)有實(shí)現(xiàn) - drawLayer:inContext: 方法,如果 UIView 沒(méi)有實(shí)現(xiàn) - drawLayer:inContext: 方法,就會(huì)回到 - drawRect: 方法,即引起死循環(huán) - CALayer 有屬性 .delegate ,可傳入實(shí)現(xiàn) <CALayerDelegate> 協(xié)議方法 - drawLayer:inContext: 的對(duì)象
CALayer 顯示的實(shí)現(xiàn)
CALayer 的坐標(biāo)系也是以左上角為原點(diǎn),也有屬性 .frame 和 .bounds ,中心是 .position
.sublayers
- addSublayer:
// 背景色,CGColorRef
.backgroundColor
// 邊框顏色
.borderColor
// 邊框?qū)挾?br>
.borderWidth
// 圓角
.cornerRadius
// 子 layer 是否能超出父 layer 范圍
.masksToBounds
/*
錨點(diǎn),如圖釘和一面白紙,用圖釘把白紙釘在墻上,這張紙可以圍繞著圖釘旋轉(zhuǎn),圖釘在這張紙上的位置就是 anchorPoint , anchorPoint 范圍為 0 - 1 ,表示這個(gè)點(diǎn)在這張白紙上的相對(duì)位置,默認(rèn)為(0.5,0.5),也就是中心(position)位置,position 為 layer 的中 anchorPoint 在 super layer 中的位置坐標(biāo),position 的計(jì)算公式:
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];
// 打印結(jié)果為:
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];
// 打印結(jié)果為:
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];
// 打印結(jié)果為:
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 內(nèi)容
.contents
// 標(biāo)記 layer 的 contents 需要更新
- setNeedsDisplay
// 對(duì) layer 的 contents 的轉(zhuǎn)換,CATransform3D 結(jié)構(gòu)體
.transform
UIView.transform vs CALayer.transform
區(qū)別:2D vs 3D
- CALayer.transform屬性是是個(gè)CATransform3D類型的數(shù)據(jù),默認(rèn)值為CATransform3DIdentity
- CGAffineTransform 是用于2D層面的, 操作的是UIView或者其他 2D Core Graphics 元素。
- CATransform3D 是 Core Animation 的結(jié)構(gòu)體,是用來(lái)做更復(fù)雜的關(guān)于 CALayer 的 3D 操作。
- CATransform3D 定義了一個(gè)三維變換(4x4的矩陣),用于圖層的偏移、旋轉(zhuǎn),縮放,歪斜和透視等效果。
- 需要了解相關(guān)的3D變換方法:CATransform3DMakeTranslation,CATransform3DMakeScale,CATransform3DMakeRotation,以及CATransform3DTranslate,CATransform3DScale,CATransform3DRotate。
- CATransform3D.m34 表示透視效果,但需要和 CATransform3DRotate 配合使用才有效果。也就是前提是z方向上有變化(即沿x軸或者y軸旋轉(zhuǎn)之后)。
不規(guī)則圖片( Mask Layer )的遮罩
// layer 中有一個(gè)mask屬性,它本身也是一個(gè)CALayer對(duì)象,我們將layer本身稱為content layer,將mask的layer稱為mask layer
.mask
遮罩的原理,就是將 alpha channel 去定義 content layer 的顯示區(qū)域
| Alpha | Visibility |
|---|---|
| 1 | 可見(jiàn) |
| 0-1 | 半透明 |
| 0 | 不可見(jiàn) |
mask layer 雖然是 CALayer 對(duì)象,但只有透明度信息是有用的,它本身的顏色信息是會(huì)被忽略的
mask layer 本身是沒(méi)有 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 的坐標(biāo)系以左上角為 (0,0) ,右下角為 (1,1)
- 漸變顏色 -- 至少2個(gè)色值
- 可以有透明色
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
]];
//漸變分割線,默認(rèn)等分
[layer setLocations:@[@0.25, @0.5, @0.75]];
// 設(shè)置起始點(diǎn)和終點(diǎn),如下代碼即為水平方向從左往右的變化
[layer setStartPoint:CGPointMake(0, 0)];
[layer setEndPoint:CGPointMake(1, 0)];
[self.view.layer addSublayer:layer];
//這里從0-0.25都是純黃色,從0.75到1都是純藍(lán)色,

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實(shí)現(xiàn)翻轉(zhuǎn)效果
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翻轉(zhuǎn)了,但是startPoint和endPoint還是按原圖來(lái)算
gradientLayer.startPoint = CGPointMake(0, 0.7);
gradientLayer.endPoint = CGPointMake(0, 1);
mirrorImageView.layer.mask = gradientLayer;
CAShapeLayer
用來(lái)繪制形狀,用來(lái)畫各種形狀,如圓形,曲線……
// 繪制形狀的路徑,CGPathRef
.path
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
// Core Graphics
// 指定繪制路徑
// 創(chuàng)建路徑對(duì)象
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 有更好的性能表現(xiàn),因?yàn)?CATextLayer 更底層
CATextLayer *textLayer = [CATextLayer layer];
textLayer.contentsScale = [UIScreen mainScreen].scale;
// 文字的顏色,只有當(dāng)不是富文本時(shí)才會(huì)生效
textLayer.foregroundColor = [UIColor blackColor].CGColor;
textLayer.backgroundColor = [UIColor orangeColor].CGColor;
// 換行
textLayer.wrapped = YES;
// 對(duì)齊方式
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 高度的計(jì)算
- [UIView class] 的 - sizeThatFits:
- [NSString class] 的 - boundingRectWithSize:options:attributes:context:
//options -- NSStringDrawingOptions 枚舉
//attributes -- 主要告知繪制區(qū)域需要繪制文本的字符大小
//context -- 設(shè)置字符間距 - 基于 AutoLayout 的 - systemLayoutSizeFittingSize:
使用這個(gè)方法的前提條件是展示這個(gè)控件的約束必須完美,也就是必須完整地約束上下左右四個(gè)方向
使用這個(gè)方法前必須設(shè)置 .preferredMaxLayoutWidth 用來(lái)約束換行操作,當(dāng)內(nèi)容超過(guò)約束區(qū)域時(shí)就會(huì)自動(dòng)換行,并且更新約束布局,preferredMaxLayoutWidth 就是告訴最大的參考寬度
參數(shù) targetSize 是 CGSize 類型,系統(tǒng)為我們提供了兩個(gè)固定的值,分別為 UILayoutFittingCompressedSize( 在保證適當(dāng)尺寸前提下,盡量壓縮 CGSize 的值 ) 和 UILayoutFittingExpandedSize( 在保證適當(dāng)尺寸前提下,盡量擴(kuò)充 CGSize 的值 )
NSString *str = @"hahaha";
_textLabel.numberOfLines = 0;
_textLabel.text = str;
//
CGSize size = [_textLabel sizeThatFits:CGSizeMake(CGRectGetWidth(self.view.bounds), CGFLOAT_MAX)];
// NSStringDrawingUsesLineFragmentOrigin -- 當(dāng)前字符串需要換行來(lái)計(jì)算高度
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 中,選擇對(duì)應(yīng)的 UIImageView,然后點(diǎn)擊 Editor — SizeToFitContent,可自動(dòng)適應(yīng)圖片大小顯示
// 圖片的拉伸
.contentMode
ScaleToFill — 適配整個(gè)高寬
ScaleAspectFit — 保持圖片比例,撐滿最短邊
ScaleAspectFill — 保持圖片比例,撐滿最長(zhǎng)邊,多余的部分修剪掉
UIImage
// 可以指定圖片中某一塊區(qū)域進(jìn)行拉伸或者平鋪
- resizableImageWithCapInsets:
- resizableImageWithCapInsets:resizingMode:
// 可對(duì)圖片的位置進(jìn)行調(diào)整
- imageWithAlignmentRectInsets:
// 改變圖片的渲染效果
- imageWithRenderingMode:
// Deprecated
- stretchableImageWithLeftCapWidth:topCapHeight:
UIButton
UITextField
關(guān)閉鍵盤
[UIResponder class]
// 呼出鍵盤
- becomeFirstResponder
// 隱藏鍵盤
- resignFirstResponder
關(guān)閉鍵盤可以有幾種方法:
- 可以獲取到 UITextField 對(duì)象,則直接調(diào)用 - resignFirstResponder
[_textField resignFirstResponder]; - 父 view 執(zhí)行 - endEditing
[self.view endEditing:YES]; - 當(dāng)前 window 執(zhí)行 - endEditing
[[UIApplication sharedApplication].keyWindow endEditing:YES]; - UIApplication 實(shí)例執(zhí)行 - sendAction:to:from:forEvent:
// 可以將 action 沿著響應(yīng)鏈傳遞
// - sendAction:to:from:forEvent:
[[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder) to:nil from:nil forEvent:nil];
UIApplication 的 sendAction:to:from:forEvent: 和 UIControl 的 sendAction:to:forEvent: 方法之間的關(guān)聯(lián):
對(duì)于一個(gè)給定的事件,UIControl 會(huì)調(diào)用 sendAction:to:forEvent: 來(lái)將行為消息轉(zhuǎn)發(fā)到 UIApplication 對(duì)象,再由 UIApplication 對(duì)象調(diào)用其 sendAction:to:fromSender:forEvent: 方法來(lái)將消息分發(fā)到指定的 target 上,而如果我們沒(méi)有指定 target ,則會(huì)將事件分發(fā)到響應(yīng)鏈上第一個(gè)想處理消息的對(duì)象上。而如果子類想監(jiān)控或修改這種行為的話,則可以重寫這個(gè)方法。
UIScrollView
// 滾動(dòng)區(qū)域的大小
.contentSize
// 當(dāng)前滾動(dòng)的具體位置
.contentOffset
// 是否允許點(diǎn)擊 status 滾動(dòng)到頂部
.scrollsToTop
// 分頁(yè)
.pagingEnabled
// 允許滾動(dòng)
.scrollEnabled
// 滾動(dòng)彈性
.bounces
// 單設(shè) contentInset 滾動(dòng)條范圍包含偏移量,如果希望滾動(dòng)條跟隨偏移量,也要設(shè)置 scrollIndicatorInset
//
.contentInset
//
.scrollIndicatorInset
// 滾動(dòng)到指定位置
- setContentOffset:animated:
- scrollRectToVisible:animated:
// 放大縮小的范圍
.maximumZoomScale
.minimumZoomScale
// 需先設(shè)置最大、最小 和 <UIScrollViewDelegate> 的 - viewForZoomingInScrollView: 來(lái)確定要放大縮小的 view
.zoomScale
- setZoomScale:animated:
- UIScrollView 默認(rèn)存在子視圖(兩個(gè) imageView)—— 上下和左右滾動(dòng)條
- UIScrollView 在使用約束布局時(shí),不需要使用 contentSize 來(lái)設(shè)置內(nèi)容大小,因?yàn)?UIScrollView 中的每個(gè)視圖都需要完整約束,第一個(gè)子視圖必需與 UIScrollView 的頂部進(jìn)行關(guān)聯(lián),最后一個(gè)子視圖與底部進(jìn)行關(guān)聯(lián),從而撐開(kāi) UIScrollView 的高度,UIScrollView 的寬度也是直接讀取內(nèi)容的寬度
- UIScrollView 通過(guò)改變 bounds 實(shí)現(xiàn)滾動(dòng)
UIScrollView 內(nèi)部子視圖約束
- UIScrollView 內(nèi)部子視圖的尺寸不能以 UIScrollView 的尺寸為參照
- UIScrollView 內(nèi)部的子視圖的約束必需要完整
判斷 UIScrollView 的內(nèi)容超出了屏幕
可以通過(guò)判斷 UIScrollView 子視圖的坐標(biāo)位置是否超出當(dāng)前屏幕的區(qū)域,這涉及到坐標(biāo)轉(zhuǎn)換
- convertPoint:toView:
- convertPoint:fromView:
- convertRect:toView:
- convertRect:fromView:
<UIScrollViewDelegate>
獲取滾動(dòng)中的各種狀態(tài)
// 滾動(dòng)回調(diào)
- scrollViewDidScroll:
// 拖拽回調(diào)
- scrollViewWillBeginDragging:
- scrollViewDidEndDragging:willDecelerate:
// 減速回調(diào)
- scrollViewWillBeginDecelerating:
- scrollViewDidEndDecelerating:
// 縮放回調(diào)
- scrollViewDidZoom:
UITableView
UITableView 本質(zhì)上是 UIScrollView + 二維數(shù)組的控件
// style -- Plain — section header 和 footer 是浮在Cell上;Grouped — section header 和 footer 是不能浮動(dòng)的,有缺省的背景色
- initWithFrame:style:
// ios9之前,在dealloc中,需要將delegate和dataSource設(shè)置為nil
// <UITableViewDataSource> ,UITableView 數(shù)據(jù)的來(lái)源
.dataSource
<UITableViewDataSource> 協(xié)議方法
// section 相關(guān)
- numberOfSectionsInTableView:
- tableView:titleForHeaderInSection:
- tableView:titleForFooterInSection:
// tableView 右側(cè)的 section 導(dǎo)航欄
- sectionIndexTitlesForTableView:
// row 相關(guān)(必須)
- tableView:numberOfRowsInSection:
- tableView:cellForRowAtIndexPath:
// <UITableViewDelegate>
.delegate
// 高度相關(guān)
- tableView:heightForRowAtIndexPath:
- tableView:estimatedHeightForRowAtIndexPath:
- tableView:heightForHeaderInSection:
- tableView:estimatedHeightForHeaderInSection:
- tableView:heightForFooterInSection:
- tableView:estimatedHeightForFooterInSection:
// Section View 相關(guān)
- tableView:viewForHeaderInSection:
- tableView:viewForFooterInSection:
// 選中相關(guān)
- tableView:willSelectRowAtIndexPath:
- tableView:didSelectRowAtIndexPath:
- tableView:willDeselectRowAtIndexPath:
- tableView:didDeselectRowAtIndexPath:
- tableView:shouldHighlightRowAtIndexPath:
- tableView:didHighlightRowAtIndexPath:
// 是否允許多選
.allowsMultipleSelection
// 重新加載數(shù)據(jù)
- reloadData
// 分割線相關(guān)
.separatorStyle
.separatorColor
.separatorEffect
.separatorInset
// 右側(cè) section 導(dǎo)航條樣式定義
.sectionIndexColor
.sectionIndexBackgroundColor
.sectionIndexTrackingBackgroundColor
- UITableView 數(shù)據(jù)不夠滿頁(yè)時(shí),會(huì)出現(xiàn)多余的分割線。解決方式有:
- 設(shè)置Section Footer
- 設(shè)置tableFooterView
.tableHeaderView
.tableFooterView
UITableViewController
封裝了 TableView、 DataSource、 Delegate,它的 view == tableView
- initWithStyle:
- initWithNibName:bundle:
UITableView 界面的定制
UITableViewCell 系統(tǒng)的 style
UITableViewCell 繼承于 UIView 。系統(tǒng)提供了四種 UITableViewCell 的 style -- UITableViewCellStyleDefault、UITableViewCellStyleValue1、UITableViewCellStyleValue2、UITableViewCellStyleSubtitle
則對(duì)應(yīng)不同的 style 的屬性有:
// cell 旁邊的箭頭
.accessoryType / .accessoryView
.detailTextLabel
// 分割線
.separator
.textLabel
.imageView
- UITableViewCell 的結(jié)構(gòu)
UITableViewCell 包含一個(gè) contentView,自定義 View 加在 contentView 上。UITableViewCell 在編輯狀態(tài)下,contentView 外面會(huì)存在各種系統(tǒng)定義 View
自定義 UITableViewCell
UITableViewCell 內(nèi)容高度
計(jì)算高度一般等同于計(jì)算布局結(jié)果
- 手動(dòng)布局高度(計(jì)算效率高;和xib中的布局信息需要保持一致,多處更新)
- AutoLayout 布局計(jì)算(計(jì)算效率低;不需要獲取布局信息,容易維護(hù)更新)
- systemLayoutSizeFittingSize:
- systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:
UITableViewCell 和 Section Header / Footer 的復(fù)用
UITableView 中的 UITableViewCell 重用 API (初始化方法):
- initWithStyle:reuseIdentifier:
UITableView 的 UITableViewCell 注冊(cè)方法:
// 從 nib 文件中注冊(cè),針對(duì) xib 文件布局
- registerNib:forCellResueIdentifier:
// 從 class 文件中注冊(cè),針對(duì)代碼布局,調(diào)用到 - initWithStyle:reuseIdentifier:
- registerClass:forCellResueIdentifier:
重用:
- dequeueReusableCellWithIdentifier:
- dequeueReusableCellWithIndentifier:forIndexPath:
UITableView 中的 Section Header / Footer 重用 API (初始化方法):
[UITableViewHeaderFooterView class]
- initWithReuseIdentifier:
UITableView 的 Section Header / Footer 注冊(cè)方法:
// 從 nib 文件中注冊(cè),針對(duì) xib 文件布局
- registerNib:forHeaderFooterViewReuseIdentifier:
// 從 class 文件中注冊(cè),針對(duì)代碼布局,調(diào)用到 - initWithReuseIdentifier:
- registerClass:forHeaderFooterViewReuseIdentifier:
重用:
- dequeueReusableHeaderFooterViewWithIdentifier:
經(jīng)過(guò)重用后, UITableView 只生成和展示能看到的 Cell
.visibleCells
.indexPathsForVisibleRows
系統(tǒng)是如何判斷 Cell 是 visible 的 ?
- 通過(guò) contentOffset 和 heightForRowAtIndexPath
- 每次 UITableView 滾動(dòng)的時(shí)候,需要重新判斷
- contentSize 依賴 heightForRowAtIndexPath
這樣會(huì)導(dǎo)致 heightForRowAtIndexPath 調(diào)用次數(shù)非常多,即在 AutoLayout 布局計(jì)算下計(jì)算效率低的原因
UITableView 預(yù)估高度
- tableView:estimatedHeightForRowAtIndexPath:
- tableView:estimatedHeightForHeaderInSection:
- tableView:estimatedHeightForFooterInSection:
優(yōu)點(diǎn):降低了heightForRowAtIndexPath調(diào)用次數(shù)
缺點(diǎn):如果 contentSize 預(yù)估不準(zhǔn),scrollView 的滾動(dòng)條可能會(huì)跳動(dòng)
Self-sizing Cells
iOS8以后,提出Self-sizing Cells,即 Cell 的高度是由 Cell 自己計(jì)算的,我們不需要提供計(jì)算高度的方法,也不需要設(shè)置 preferredMaxLayoutWidth,支持 AutoLayout 和 Frame Layout 的方式
self.tableView.estimatedRowHeight = estimatedRowHeight;
self.tableView.rowHeight = UITableViewAutomaticDimension; (默認(rèn)值)
UITableView 的編輯
列表可以編輯 -- 刪除、新增、移動(dòng)
左滑操作菜單 -- 菜單項(xiàng)可以自定義
選擇
進(jìn)入編輯界面
.editing
- setEditing:animated:
// 顯示在 UITableViewCell 左側(cè),返回 UITableViewCellEditingStyle 枚舉值 None、Delete(刪除)、Insert(插入)、Delete | Insert (可選)
- tableView:editingStyleForRowAtIndexPath:
// 顯示在 UITableViewCell 右側(cè),是否可移動(dòng)
- tableView:canMoveRowAtIndexPath:
// 顯示在 UITableViewCell 左滑后的操作菜單,返回 UITableViewRowAction 對(duì)象數(shù)組
- tableView:editActionsForRowAtIndexPath:
// 刪除、新增數(shù)據(jù) —— <UITableViewDataSource> 回調(diào),實(shí)現(xiàn)
- tableView:commitEditingStyle:forRowAtIndexPath:
// 移動(dòng)數(shù)據(jù) —— <UITableViewDataSource> 回調(diào),實(shí)現(xiàn)
- tableView:moveRowAtIndexPath:toIndexPath:
// 選擇數(shù)據(jù)
.indexPathForSelectedRow
.indexPathsForSeletedRows
// 通知 UITableView 展示更新,調(diào)用
- insertRowsAtIndexPaths:withRowAnimation:
- deleteRowsAtIndexPaths:withRowAnimation:
- reloadRowsAtIndexPaths:withRowAnimation:
- moveRowAtIndexPath:toIndexPath:
//同時(shí)展示刪除、新增多個(gè)更新動(dòng)作
// beginUpdates 和 endUpdates 包起來(lái)
- beginUpdates
- endUpdates
// 或者通知 tableView 更新
- reloadData
UITableView 高亮和選中
// 實(shí)現(xiàn)
- tableView:didHighlightRowAtIndexPath:
- tableView:didUnhighlightRowAtIndexPath:
- tableView:didSelectRowAtIndexPath:
- tableView:didDeselectRowAtIndexPath:
// 是否允許多選
.allowMultipleSelectiton
對(duì)于 UITableViewCell
.selected
.highlighted
//選中樣式
.selectionStyle
// 實(shí)現(xiàn),前調(diào) super
- setSelected:animated:
- setHighlighted:animated: