繪圖
系統(tǒng)將內(nèi)存中的對象渲染成屏幕上的圖片
IOS中實(shí)現(xiàn)繪圖功能的底層框架結(jié)構(gòu)
- 最基礎(chǔ)的繪圖引擎:Core Graphics(QuartZ2D)
- 基于Core Graphics封裝出來的第二個層次:Core Animation
- 基于Core Animation再封裝出來就是UIKit
預(yù)備:
新建一個類,繼承自UIView,實(shí)現(xiàn)drawRect方法,并且實(shí)現(xiàn)繪制的代碼目前只能寫在這個方法中,因?yàn)槔L制過程是一個非常復(fù)雜的過程,系統(tǒng)只允許在特定的區(qū)域內(nèi)拿到繪圖上下文對象CGContextRef,該方法會由系統(tǒng)在創(chuàng)建視圖實(shí)例時自動調(diào)用一次
繪制圖形——UIBezier Path
step1:創(chuàng)建路徑對象,勾勒路徑
step2:設(shè)置填充或描邊的顏色
step3:繪制[path stroke];
直線
UIBezierPath * path = [UIBezierPath bezierPath];
//勾勒路徑
[path moveToPoint:CGPointMake(40, 40)];
[path addLineToPoint:CGPointMake(40, 140)];
//封閉圖形
[path closePath];
弧線
UIBezierPath * path = [UIBezierPath bezierPath];
//圓弧的中心
CGPoint center = CGPointMake(self.bounds.size.width*0.5, self.bounds.size.height*0.5);
//圓弧的半徑
CGFloat radius = MIN(self.bounds.size.width, self.bounds.size.height)*0.5-10;
//勾勒路徑
//起點(diǎn)是二分之三π,終點(diǎn)是起點(diǎn)數(shù)值基礎(chǔ)上累加一個弧度,累加的弧度要由下載數(shù)據(jù)決定,數(shù)據(jù)的百分之百時,弧度時2π,所以下載了0.3時,就用0.3*2π就可以了
[path addArcWithCenter:center radius:radius startAngle:M_PI_2*3 endAngle:M_PI_2*3+self.downloadValue*2*M_PI clockwise:YES];
曲線
UIBezierPath * path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(180, 40)];
//添加曲線
[path addCurveToPoint:CGPointMake(40, 180) controlPoint1:CGPointMake(40, 40) controlPoint2:CGPointMake(180, 180)];
矩形
UIBezierPath * path2 = [UIBezierPath bezierPathWithRect:CGRectMake(50, 50, 200, 100)];
圓角矩形
UIBezierPath * path2 = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(50, 50, 200, 100) cornerRadius:50];
[[UIColor blueColor]setStroke];
橢圓(正圓)
UIBezierPath * path3 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(50, 200, 200, 100)];
總結(jié):
前三種圖形(直線、曲線、圓?。?,先創(chuàng)建UIBezierPath然后使用addXXX方法實(shí)現(xiàn)路徑設(shè)計(jì)
后兩種圖形(矩形、橢圓),在創(chuàng)建UIBezierPath時直接就設(shè)置了路徑,使用bezierPathWithXXX方法
繪制字符串——NSString
只能畫一行
NSString *str = @"這是一段用于測試多文本能夠自動換行的測試文字,大概能夠?qū)憘€兩三行看到結(jié)束就可以,恩,差不多了,就這樣了吧!";
NSDictionary *attributes = @{
NSFontAttributeName:[UIFont systemFontOfSize:20],
NSForegroundColorAttributeName:[UIColor redColor]};
[str drawAtPoint:CGPointMake(30, 30) withAttributes:attributes];
在一個空間內(nèi)繪制,自動換行
[str drawInRect:CGRectMake(30, 30, 150, 200) withAttributes:attributes];
根據(jù)文本內(nèi)容自動設(shè)置空間
//自動計(jì)算出不超出指定的寬度和高度的前提下
//能夠完整的顯示字符串的最合適的高度和寬度
CGRect textFrame = [str boundingRectWithSize:CGSizeMake(200, 999) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil];
[str drawInRect:CGRectMake(50, 50, textFrame.size.width, textFrame.size.height) withAttributes:attributes];
繪制圖片——UIImage
UIImage * image = [UIImage imageNamed:@"icon120"];
[image drawAtPoint:CGPointMake(50, 40)];
[image drawInRect:CGRectMake(50, 160, 120, 120)];
使用UIBezierPath的addClip方法,將路徑以外的部分設(shè)置為繪圖無效區(qū),再來繪制圖片時就能生成異形圖片
[path addClip];
重繪
當(dāng)數(shù)據(jù)發(fā)生改變時,希望能夠根據(jù)新的數(shù)據(jù)重新繪內(nèi)容時,調(diào)用視圖的setNeedsDisplay方法,通知系統(tǒng)重繪。
[self setNeedsDisplay];
使用UIGraphiceBeginImageContextWithOptions和UIGraphiceEndImageContext開辟臨時畫布,在這個區(qū)間內(nèi)可以編寫繪圖代碼,最后將這塊畫布保存成一張新的圖片
//申請一塊臨時畫布
//opaque YES表示不透明 NO表示透明
//scale 畫得比例
UIGraphicsBeginImageContextWithOptions(CGSizeMake(self.imageView.bounds.size.width, self.imageView.bounds.size.height), NO, 1);
UIBezierPath * path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, self.imageView.bounds.size.width, self.imageView.bounds.size.height)];
[path addClip];
UIImage * image = [UIImage imageNamed:@"icon120"];
[image drawInRect:CGRectMake(0, 0, self.imageView.bounds.size.width, self.imageView.bounds.size.height)];
//將畫布中的圖像另存為一張image對象
UIImage * newHeaderImage = UIGraphicsGetImageFromCurrentImageContext();
self.imageView.image = newHeaderImage;
UIGraphicsEndImageContext();
變形Transform
視圖發(fā)生了位移(Translation)或者縮放(Scale)或者旋轉(zhuǎn)(Rotation)這樣的外觀或位置的改變
實(shí)現(xiàn)變形:修改視圖的transform屬性即可
transform屬性屬于CGAffineTransform結(jié)構(gòu)體類型(仿射變換)
包含有6個數(shù)值的3x3矩陣
可以借助函數(shù)幫我們計(jì)算出為了改變視圖的樣式而對應(yīng)的矩陣值
實(shí)現(xiàn)幫我們計(jì)算矩陣的函數(shù):
計(jì)算新的矩陣數(shù)值時,永遠(yuǎn)是基于視圖沒有任何變形的那個狀態(tài)來計(jì)算
CGAffineTransformMakeTranslation()
CGAffineTransformMakeScale()
CGAffineTransformMakeRotation()
計(jì)算新的矩陣數(shù)值時,基于傳入的矩陣狀態(tài)來計(jì)算新的矩陣,會疊加變形效果
CGAffineTransformTranslate()
CGAffineTransformScale()
CGAffineTransformRotate()
使用
self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, 20, 20);
手勢
將用戶物理性的觸屏操作轉(zhuǎn)變成了對象存儲起來,所有手勢的父類UIGestureRecognizer
系統(tǒng)將一下有特點(diǎn)的觸屏操作封裝成了不同的手勢類型包括:
UITapGestureRecognizer->點(diǎn)擊(一次性手勢)
UISwipeGestureRecognizer->輕掃(解鎖)(一次性手勢)
UILongPressGestureRecognizer->長按
UIPanGestureRecognizer->拖拽
UIPinchGestureRecognizer->捏合手勢
UIRotationGestureRecognizer->轉(zhuǎn)轉(zhuǎn)
使用手勢
step1:創(chuàng)建指定手勢的實(shí)例,在創(chuàng)建時設(shè)定好當(dāng)該類型手勢發(fā)生時,系統(tǒng)自動發(fā)什么消息(自動調(diào)用那個方法來響應(yīng))
step2:設(shè)置或讀取手勢對象的核心屬性
step3:將手勢添加到某個視圖中,即代表,當(dāng)用戶在該視圖上做了這樣的手勢時,系統(tǒng)才會捕獲并調(diào)用方法來執(zhí)行響應(yīng)
Tap點(diǎn)擊手勢
核心屬性:numberOfTapsRequired
方法:locationInView獲取觸點(diǎn)在某個視圖坐標(biāo)系下的坐標(biāo)值
//1 創(chuàng)建Tap手勢
UITapGestureRecognizer * tapGR = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap:)];
//2 設(shè)置手勢的屬性
//設(shè)置需要觸發(fā)手勢的點(diǎn)擊數(shù),默認(rèn)1
tapGR.numberOfTapsRequired = 1;
//設(shè)置需要觸發(fā)手勢的觸點(diǎn)的個數(shù),默認(rèn)1
tapGR.numberOfTouchesRequired = 1;
//3 將手勢與具體的視圖綁定在一起
[self.view addGestureRecognizer:tapGR];
//手勢的響應(yīng)方法
-(void)tap:(UITapGestureRecognizer *)gr{
//返回觸摸點(diǎn)在哪個坐標(biāo)系中的位置
CGPoint point = [gr locationInView:self.view];
NSLog(@"%@",NSStringFromCGPoint(point));
}
Swipe輕掃手勢
屬性:direction方向
可以將左和右,或者上和下進(jìn)行組合,使用按位或運(yùn)算符
注意:Tap和Swipe手勢歸為一次性手勢,即手勢發(fā)生過程中,響應(yīng)方法只執(zhí)行一次
UISwipeGestureRecognizer * swipeGR = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipe:)];
//掃動方向
swipeGR.direction = UISwipeGestureRecognizerDirectionRight|UISwipeGestureRecognizerDirectionLeft;
//綁定手勢到視圖上
[self.view addGestureRecognizer:swipeGR];
LongPress長按手勢
設(shè)置:minimumPressDuration長按需要的最少時間
長按手勢有不同的狀態(tài),按下時,起始狀態(tài),移動中,改變狀態(tài),抬起時,結(jié)算狀態(tài)。從一個狀態(tài)到另一個狀態(tài)發(fā)生變化時都會連續(xù)地調(diào)用響應(yīng)方法
UILongPressGestureRecognizer * longPressGR = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPress:)];
//設(shè)置長按動作的間隔秒數(shù)
longPressGR.minimumPressDuration = 2;
[self.view addGestureRecognizer:longPressGR];
-(void)longPress:(UILongPressGestureRecognizer *)gr{
NSLog(@"%@",NSStringFromCGPoint([gr locationInView:self.view]));
}
Pinch捏合手勢
UIPinchGestureRecognizer * pinchGR = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinch:)];
[self.view addGestureRecognizer:pinchGR];
-(void)pinch:(UIPinchGestureRecognizer *)gr{
//代表動作快慢的速率
//正數(shù)代表外擴(kuò),負(fù)數(shù)代表往內(nèi)捏合,絕對值越大,代表動作越快,越小代表動作越慢
CGFloat veloctity = gr.velocity;
//代表動作外擴(kuò)或向內(nèi)捏合的比率倍數(shù)
//外擴(kuò):大于1的數(shù),捏合:小于1的數(shù)
CGFloat scale = gr.scale;
NSLog(@"velocity = %.2f,scale=%.2f",veloctity,scale);
//每次計(jì)算完之后置1,讓每次比較以上一次得到的數(shù)為基準(zhǔn)
gr.scale = 1;
}
Rotation旋轉(zhuǎn)手勢
UIRotationGestureRecognizer * rotationGR = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotation:)];
[self.view addGestureRecognizer:rotationGR];
-(void)rotation:(UIRotationGestureRecognizer *)gr{
//旋轉(zhuǎn)的弧度
//順時針正數(shù),逆時針負(fù)數(shù)
CGFloat rotation = gr.rotation;
NSLog(@"%.2f",rotation);
}
Pan拖拽手勢
UIPanGestureRecognizer * panGR = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
[self.view addGestureRecognizer:panGR];
-(void)pan:(UIPanGestureRecognizer *)gr{
//手勢觸點(diǎn),在self.view坐標(biāo)系下的點(diǎn)的坐標(biāo)值
CGPoint location = [gr locationInView:self.view];
//手勢移動到的新點(diǎn)相對于手勢起始點(diǎn)的橫向縱向距離
//手勢移動了多遠(yuǎn)
CGPoint translation = [gr translationInView:self.view];
NSLog(@"%@ %@",NSStringFromCGPoint(location),NSStringFromCGPoint(translation));
}
多手勢并存:
step1:設(shè)置需要并處的手勢的代理為當(dāng)前控制器
step2:控制器遵守協(xié)議UIGestureRecognizerDelegate
step3:實(shí)現(xiàn)
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;方法,返回YES
//拖拽功能
UIPanGestureRecognizer * panGR = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
[self.view addGestureRecognizer:panGR];
//縮放功能
UIPinchGestureRecognizer *pinchGR = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinch:)];
[self.view addGestureRecognizer:pinchGR];
//設(shè)置自己為代理
pinchGR.delegate = self;
//旋轉(zhuǎn)功能
UIRotationGestureRecognizer * rotationGR = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotation:)];
[self.view addGestureRecognizer:rotationGR];
//設(shè)置自己為代理
rotationGR.delegate = self;
//雙擊還原
UITapGestureRecognizer * tapGR = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap:)];
tapGR.numberOfTapsRequired = 2;
[self.view addGestureRecognizer:tapGR];
//實(shí)現(xiàn)圖片跟隨手勢移動
-(void)pan:(UIPanGestureRecognizer *)gr{
//相對于起始點(diǎn)走了多遠(yuǎn)
//當(dāng)視圖發(fā)生放大縮小時,相應(yīng)的視圖的坐標(biāo)系的比例也會放大或縮小
//要修改的transform屬性,數(shù)據(jù)是使用imageView坐標(biāo)系中的刻度值
//所以在讀取手勢移動了的距離是多少時,也要讀取在imageView這個坐標(biāo)系下的距離
CGPoint translation = [gr translationInView:self.imageView];
self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, translation.x, translation.y);
//將本次走了的距離歸零
[gr setTranslation:CGPointZero inView:self.imageView];
//-------修改中心點(diǎn)實(shí)現(xiàn)位移------
// CGPoint translation2 = [gr translationInView:self.view];
// CGPoint center = self.imageView.center;
// center.x += translation2.x;
// center.y += translation2.y;
// self.imageView.center = center;
// [gr setTranslation:CGPointZero inView:self.view];
}
跟隨手勢捏合實(shí)現(xiàn)放大縮小
-(void)pinch:(UIPinchGestureRecognizer *)gr{
self.imageView.transform = CGAffineTransformScale(self.imageView.transform, gr.scale, gr.scale);
//去掉本次比率,歸1
gr.scale = 1;
}
//縮放
-(void)rotation:(UIRotationGestureRecognizer *)gr{
self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, gr.rotation);
gr.rotation = 0;
}
//實(shí)現(xiàn)多點(diǎn)觸控并發(fā)方法
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
//雙擊還原
-(void)tap:(UITapGestureRecognizer *)gr{
//Identity系統(tǒng)提供的一個常量,該常量中記錄的就是沒有發(fā)生任何變形時的那個矩陣
self.imageView.transform = CGAffineTransformIdentity;
}
深入坐標(biāo)系
UIView——》frame bounds center transform
frame
a>什么是frame?
類型:CGRect結(jié)構(gòu)體類型
作用:記錄了試圖左頂點(diǎn)在俯視圖坐標(biāo)系中的位置,以及視圖在父視圖內(nèi)占的寬高
b>什么時候使用frame?
當(dāng)需要將一個視圖添加到另一個視圖中做子視圖時,一定要設(shè)置frame,保證在父視圖中的位置即占據(jù)的區(qū)域大小
c>當(dāng)修改frame時,其他三個屬性是否會改變?
bounds:YES
center:YES
transform:NO
2>bounds
a>什么是bounds?
類型:CGRect結(jié)構(gòu)體類型
作用:bounds中的x和y用于記錄該視圖自己坐標(biāo)系的基準(zhǔn)值,默認(rèn)都是0,0后兩個值記錄的就是該視圖自己的大小尺寸
b>什么時候使用bounds?
如果想要修改子視圖的位置,可以調(diào)整bounds中的x或y(無法直接修改,要先建立CGRect的數(shù)值接收,修改后重新賦值),需要讀取視圖的大小時,那么就使用bounds中的width和height。在沒有變形時,視圖有多大,在父視圖中就會占據(jù)多大空間,所以bounds中的w和h與frame中的w和h相等。
c>當(dāng)修改bounds時,其他三個屬性是否會改變?
frame:YES
center:NO
transform:NO
3>center
a>什么是center?
類型:CGPoint類型
作用:記錄視圖中心點(diǎn)在父視圖坐標(biāo)系下的位置
b>什么時候使用center?
如果想實(shí)現(xiàn)子視圖位置的變化,則可以修改center
添加子視圖時,如果不使用frame,那么可以使用bounds+center的組合設(shè)置來完成
c>當(dāng)修改center時,其他三個屬性是否會改變?
frame:YES
bounds:NO
transform:NO
4>transform
a>什么是transform?
類型:CGAffineTransform類型
作用:實(shí)現(xiàn)視圖在展現(xiàn)出外觀時,發(fā)生的位置的偏移、放大或縮小的效果、以及旋轉(zhuǎn)效果
b>什么時候使用transform?
在展現(xiàn)外觀時,有以上變化效果時,修改transform。
c>當(dāng)修改transform時,其他三個屬性是否會改變?
frame:YES
bounds:NO
center:NO
結(jié)論:
屏幕可以分為兩個空間,一個是看的見的用戶空間,一個是看不見的設(shè)備空間,transform屬性記錄的就是如何將設(shè)備空間中的視圖映射到用戶空間上,也就是說transform記錄的是映射規(guī)則
frame是用戶空間中的數(shù)據(jù),也就是記錄看見的結(jié)果的數(shù)據(jù),bounds+center時設(shè)備空間中的數(shù)據(jù),也就是合起來記錄看不見的那個空間中視圖的大小和位置,在不改變映射規(guī)則(transform)時,表里如一,但是,一旦改變了映射規(guī)則,那么就會表里不一,里面存儲的視圖展現(xiàn)出來時會根據(jù)transform發(fā)生改變
添加子視圖,找frame
讀取視圖大小,找bounds
改位置,找center
實(shí)在是想旋轉(zhuǎn),找transform
故事板中添加手勢
- 從資源庫中選擇合適的手勢圖標(biāo),拖拽到界面上
- 為了減少綁定過程,將手勢直接拖拽到綁定的目標(biāo)視圖上
- 可以在場景頂端的橫條中找到拖拽過的手勢,然后選中,在第四個檢查器中做常規(guī)設(shè)置
- 拆分視圖下,選中手勢對象,連線到代碼中,添加對該手勢動作的響應(yīng)
圖片視圖默認(rèn)不能與用戶交互,所以添加手勢到圖片視圖上以后,要開啟用戶交互功能,設(shè)置userInterActionEnabled為YES
UITouch觸控
將用戶的物理觸屏動作轉(zhuǎn)變成了數(shù)據(jù)存到了UITouch對象中
1>普通的視圖
touchesBegan
touchesMoved
touchesEnded
touchesCancel
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
//按下觸點(diǎn),就將此點(diǎn)記錄到屬性中
self.beginPoint = [touch locationInView:self];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
//創(chuàng)建路徑
self.path = [UIBezierPath bezierPathWithRect:CGRectMake(self.beginPoint.x, self.beginPoint.y, point.x-self.beginPoint.x, point.y-self.beginPoint.y)];
self.path.lineWidth = 3;
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//觸點(diǎn)抬起時,將臨時的路徑確定保存到數(shù)組中
[self.allPath addObject:self.path];
}
- (void)drawRect:(CGRect)rect
{
[[UIColor redColor] setStroke];
//當(dāng)前正在描繪,但還不確定結(jié)果的那個矩形
[self.path stroke];
//將所有存到數(shù)組中的已經(jīng)確定了的矩形都重新一遍
for (UIBezierPath *path in self.allPath)
{
[path stroke];
}
}
2>控制器自帶的視圖
實(shí)現(xiàn)控制器的
touchesBegan
touchesMoved
touchesEnded
3>UITouch的用處
a>跟蹤觸點(diǎn)的軌跡,做類似于畫板這樣的應(yīng)用
b>視圖跟蹤觸點(diǎn)的移動,比pan手勢的跟蹤位置更準(zhǔn)確
布局(Layout)
子視圖在父視圖中的擺放位置,通過設(shè)置frame實(shí)現(xiàn)布局,但是由于父視圖可能因?yàn)槠聊坏姆N類、橫豎屏翻轉(zhuǎn)、鍵盤彈起、各種bar的顯示隱藏等因素發(fā)生改變,而此時如果不修改子視圖的frame,依然保持在原有尺寸下擺放,那么界面就會出現(xiàn)混亂。所以當(dāng)父視圖大小發(fā)生變化時,希望調(diào)整子視圖的frame,適應(yīng)新的尺寸,保證布局出來的界面依然美觀,于是使用布局技術(shù)實(shí)現(xiàn)該需求。
核心理念:改frame
哪些因素會影響父視圖大小發(fā)生變化?
1>屏幕尺寸(3.5、4、4.7、5.5)
2>橫豎屏旋轉(zhuǎn)
3>各種Bar
4>鍵盤彈起
5>特殊的bar 來電話的綠色條、開啟熱點(diǎn)的藍(lán)色條、錄音時的紅色條
純代碼布局
理念:只要檢測到父視圖的frame發(fā)生了變化,則將父視圖中的所有子視圖重新計(jì)算frame
關(guān)鍵點(diǎn):如何檢測到父視圖frame發(fā)生了變化
a>對控制器自帶的那個view的直接子視圖的布局
代碼寫在控制的viewDidLayoutSubViews方法中
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
//布局按鈕
CGRect frame = self.button.frame;
frame.origin.x = self.view.bounds.size.width - 20 - frame.size.width;
//獲取頂部被bar占據(jù)的高度
frame.origin.y = self.topLayoutGuide.length + 20;
self.button.frame = frame;
frame = self.label.frame;
frame.origin.x = self.view.bounds.size.width - frame.size.width - 20;
//獲取底部被bar占據(jù)的高度
frame.origin.y = self.view.bounds.size.height - 20 - frame.size.height - self.bottomLayoutGuide.length;
self.label.frame = frame;
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.navigationController setNavigationBarHidden:!self.navigationController.navigationBarHidden animated:YES];
[self.navigationController setToolbarHidden:!self.navigationController.toolbarHidden animated:YES];
}
b>對某個view的內(nèi)部的子視圖的布局
代碼寫在視圖的layoutSubViews方法中(第一句一定要調(diào)用super中對應(yīng)方法)
如:UITableViewCell對它內(nèi)部子視圖的布局、自定義tabBar對內(nèi)部的子視圖的布局
如何獲取視圖的頂部和底部被系統(tǒng)的bar占據(jù)的高度
AutoResizing布局
理念:視圖的位置由視圖與父視圖之間的間距決定,此間距可以設(shè)置為木棍或彈簧;視圖的大小遵循等比變換原則——如父視圖原有寬度是100,變化后是200,則父視圖的寬是原來的二倍,于是父視圖中的所有子視圖的寬度也會變?yōu)樵瓉淼膶挾鹊亩?/p>
如何使用:
預(yù)備:一定要關(guān)閉Auto Layout,然后在視圖的第五個檢查器中會有6條紅線,
step1:如果確定子視圖與父視圖的邊緣距離固定,則點(diǎn)亮該方向的紅線,
step2:如果確定子視圖的寬高也需要變化,則點(diǎn)亮中間帶有箭頭的紅線。
Auto Layout
核心理念:通過為視圖添加多個約束來說明視圖的位置,當(dāng)父視圖發(fā)生變化時,系統(tǒng)就會在部位被任何一個約束的情況下,為我們自動計(jì)算出新的frame,從而達(dá)到自動布局的效果
約束:添加的對視圖位置或大小的限定條件
如:
文字描述:按鈕距離頂部20,距離左邊緣20,寬100,高40
約束1:豎直方向上的約束
約束2:水平方向上的約束
約束3:大小中寬的約束
約束4:大小中高的約束
使用約束的原則:
1>約束要準(zhǔn)確
2>約束不能沖突
如何為視圖添加約束:
方式一:不用寫代碼,在故事板中通過配置菜單
方式二:使用代碼創(chuàng)建約束
約束類型:NSLayoutConstraint
1>萬能公式法:
任何一個約束都可以使用下面的公式進(jìn)行描述:
view1.attr1 = view2.attr2 * multiplier + constant
按鈕的左邊緣距離父視圖的左邊緣為20個點(diǎn)
button.left = view.left * 1 + 20;
按鈕的寬度是另一個按鈕的寬度的兩倍
button.width = button2.width * 2 + 0;
按鈕的右邊緣距離父視圖的右邊緣為20
button.right = view.right * 1 - 20;
按鈕的寬度為80
button.width = nil.not * 0 + 80;
注意:
a>使用代碼創(chuàng)建的視圖實(shí)例,添加到父視圖中時,系統(tǒng)會偷偷地為該視圖添加兩個約束,將視圖的左和上的紅線變成約束,一般都需要關(guān)閉該功能
b>約束創(chuàng)建好之后,要將該約束添加到視圖所在的父視圖身上
//1 關(guān)閉button1和button2的自動翻譯紅線為約束功能
button1.translatesAutoresizingMaskIntoConstraints = NO;
button2.translatesAutoresizingMaskIntoConstraints = NO;
//2 創(chuàng)建約束
//button1距離左邊緣20個點(diǎn) button1.left = view.left * 1 + 20
NSLayoutConstraint * c1 = [NSLayoutConstraint constraintWithItem:button1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:20];
[self.view addConstraint:c1];
//button1.top = view.top * 1 + 20
NSLayoutConstraint * c2 = [NSLayoutConstraint constraintWithItem:button1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:60];
[self.view addConstraint:c2];
//button1.right = button2.left * 1 - 10
NSLayoutConstraint * c3 = [NSLayoutConstraint constraintWithItem:button1 attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:button2 attribute:NSLayoutAttributeLeft multiplier:1 constant:-10];
[self.view addConstraint:c3];
//button1.width = button2.width * 1 + 0
NSLayoutConstraint * c4 = [NSLayoutConstraint constraintWithItem:button1 attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:button2 attribute:NSLayoutAttributeWidth multiplier:1 constant:0];
[self.view addConstraint:c4];
//button2.right = view.right * 1 - 20
NSLayoutConstraint * c5 = [NSLayoutConstraint constraintWithItem:button2 attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1 constant:-20];
[self.view addConstraint:c5];
//button2.top = button1.top * 1 + 0
NSLayoutConstraint * c6 = [NSLayoutConstraint constraintWithItem:button2 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:button1 attribute:NSLayoutAttributeTop multiplier:1 constant:0];
[self.view addConstraint:c6];
//button1.height = nil.notanattri * 0 + 40
NSLayoutConstraint * c7 = [NSLayoutConstraint constraintWithItem:button1 attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0 constant:40];
[self.view addConstraint:c7];
//button2.height = button1.height * 1 + 0
NSLayoutConstraint * c8 = [NSLayoutConstraint constraintWithItem:button2 attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:button1 attribute:NSLayoutAttributeHeight multiplier:1 constant:0];
[self.view addConstraint:c8];
2>VFL法:Visual Format Language
使用一個特殊的字符串來表達(dá)一組視圖之間的約束關(guān)系,系統(tǒng)會自動將字符串中表達(dá)的約束一次性創(chuàng)建成功,存到一個數(shù)組中
特殊的字符串中可用的特殊符號?
[ ]視圖
-默認(rèn)8個點(diǎn)間距
-x-間距為x
( )視圖的寬度
|父視圖邊緣
V:豎直方向
button1.translatesAutoresizingMaskIntoConstraints = NO;
button2.translatesAutoresizingMaskIntoConstraints = NO;
button3.translatesAutoresizingMaskIntoConstraints = NO;
//創(chuàng)建一個尺寸對照表
NSDictionary * metrics = @{@"left":@20,
@"right":@20,
@"spacing":@20};
//創(chuàng)建視圖的對照表
//此函數(shù)會自動將傳入的對象名字符串做key,將此對象做key的value,生成如下形式的字典{@"b1":b1,@"b2":b2,@"b3":b3}
NSDictionary * dictionary = NSDictionaryOfVariableBindings(button1,button2,button3);
//1 創(chuàng)建水平方向的約束描述
NSString * hVFL = @"|-left-[button1]-spacing-[button2(==button1)]-spacing-[button3(==button1)]-right-|";
//2 將VFL變成一組約束
NSArray * cs1 = [NSLayoutConstraint constraintsWithVisualFormat:hVFL options:NSLayoutFormatAlignAllTop|NSLayoutFormatAlignAllBottom metrics:metrics views:dictionary];
//3 添加約束到父視圖
[self.view addConstraints:cs1];
//創(chuàng)建豎直方向的VFL
NSString * vVFL = @"V:|-spacing-[button1(40)]";
NSArray * cs2 = [NSLayoutConstraint constraintsWithVisualFormat:vVFL options:0 metrics:metrics views:dictionary];
[self.view addConstraints:cs2];
動畫(Animation)
動畫
一般指"幀動畫",一幀就是一張靜態(tài)圖片,一般1秒24幀以上,人眼就無法分辨圖片的切換間隙了
幀率:FPS(Frame Per Second)每秒多少幀
ios中的動畫
a>UIImage
[UIImage animatedImageNamed:@"ship-anim" duration:播放完一遍所有圖片的時間]
b>UIImageView
1>拖拽到Assests.xcassets中的圖片資源只能使用UIImage imageNamed方式來加載,不能使用絕對路徑的方式來加載,因?yàn)橘Y源庫中的圖片部署到沙盒中的xxx.app包中時都被轉(zhuǎn)化到Assets.car文件中,此文件中沒有路徑一說。
2>沒有拖拽到Assests.xcassets資源庫中的圖片,部署時會被添加到xxx.app包種的根路徑下,此時加載這些有路徑的圖片時,可以使用imageNamed方法,也可以使用[[NSBundle mainBundle]pathForResource:fileName ofType:nil]方法。NSBundle mainBundle代表的就是xxx.app的根路徑
c>UIView
類方法:animatedxxx方法
可以做的動畫效果:連續(xù)的改變center,實(shí)現(xiàn)位移動畫,改變alpha,實(shí)現(xiàn)淡入淡出,改變transform,實(shí)現(xiàn)連續(xù)的旋轉(zhuǎn)
基本步驟:
step1:在動畫方法外,設(shè)置視圖的開始狀態(tài)
step2:調(diào)用UIView的類方法animatedxxx,填寫完動畫種類、時常相關(guān)參數(shù)后,在block塊內(nèi),設(shè)置好視圖的動畫結(jié)束后的狀態(tài)即可
d>通過修改約束實(shí)現(xiàn)動畫
step1:先設(shè)置約束新的狀態(tài)值
step2:調(diào)用UIView的animateWithDurationXX時,在block塊內(nèi)調(diào)用修改約束的視圖的父視圖的layoutIfNeeded方法完成動畫過程的設(shè)置
Core Animation
UIView核心顯示功能就是依靠CALayer實(shí)現(xiàn)的
UIView和CALayer的關(guān)系:1)UIView的顯示能力是依靠底層的CALayer實(shí)現(xiàn)的,每一個UIView都包含了一個CALayer對象,修改了CALayer,會影響表現(xiàn)出來的UIView外觀。2)CALayer是不能夠響應(yīng)事件的,但UIView由于繼承自UIResponder,所以UIView還可以響應(yīng)用戶的觸摸事件。
Core Animation中的動畫只能添加到CALayer類型的對象身上,與UIView動畫最大的區(qū)別就是CA動畫是假的,視圖看著好像位置發(fā)生了變化,但其實(shí)實(shí)際位置沒變,在UIView動畫中,由于明確的設(shè)定了動畫結(jié)束時的狀態(tài),所以視圖的數(shù)據(jù)會隨著動畫的結(jié)束而真的被改變
動畫類的父類是CAAnimation,一般使用它的子類CABasicAnimation和CAKeyFrameAnimation
獲取UIView底層的那個CALayer對象
通過視圖的.layer屬性就能拿到該視圖底層的層對象
使用CALayer可以做什么
1>可以修改系統(tǒng)已有的UIView的layer改變視圖的外觀
常用屬性:
masksToBounds->是否可裁剪(修改圓形圖片一定要選)
backgroundColor->背景色
shadowXXXX->陰影
borderColor->邊框的顏色
borderWidth->邊框的寬度
corneRadius->圓角邊框
transform(CATransform3D)->變形
與尺寸位置有關(guān)的三個重要屬性:
bounds->大小
position->位置
anchorPoint->錨點(diǎn)
2>創(chuàng)建新的CALayer來完成視圖的設(shè)計(jì)
CALayer->繪制圖片
CATextLayer->繪制字符串
CAShapeLayer->繪制圖形
3>CALayer的動畫
CADisplayLink
理念:類似于定時器NSTimer,自身已經(jīng)設(shè)定好,一秒鐘會調(diào)用指定的方法60次,只需要算出每一幀改變的數(shù)值是多少
CABasicAnimation
基礎(chǔ)動畫:只需要設(shè)置動畫的起始值和終點(diǎn)值即可,一定要設(shè)置keyPath屬性,以此說明動畫需要修改的屬性名是哪個
CAKeyFrameAnimation
關(guān)鍵幀動畫:可以定制動畫過程中的細(xì)節(jié),所以可以通過values屬性記錄中間每一個重要的變化點(diǎn)的數(shù)據(jù),可以認(rèn)為基礎(chǔ)動畫就是一個只有兩個關(guān)鍵幀的動畫,另外,關(guān)鍵幀最重要的效果就是可以自定義動畫路徑,通過path屬性記錄
NSNotification通知
什么是通知
一個對象發(fā)生了改變,希望讓其他更多的對象知道他的改變,從而實(shí)現(xiàn)其他對象隨之發(fā)生改變的一種信息傳遞手段
如何實(shí)現(xiàn)通知
使用系統(tǒng)發(fā)出的通知
//獲取通知中心
NSNotificationCenter * center = [NSNotificationCenter defaultCenter];
//給通知中心發(fā)通知
//name:通知的名稱
//object:誰發(fā)出的通知
//userInfo:通知的內(nèi)容
[center postNotificationName:@"Update" object:self userInfo:@{@"title":@"TBBT",
@"episode":@"第12集"}];
//注冊對"Update"通知的監(jiān)聽
//observer:誰想接收通知
//selector:用哪個方法響應(yīng)
//name:監(jiān)聽的通知叫什么名
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resiveNewNotifiaciton:) name:@"Update" object:nil];
//該方法會在接收到通知時自動調(diào)用
//參數(shù)就是系統(tǒng)封裝好的通知對象,通過此參數(shù)可以拿到
//發(fā)出通知中的userInfo信息
-(void)resiveNewNotifiaciton:(NSNotification *)notification{
NSDictionary * dict = notification.userInfo;
NSLog(@"%@更新到了%@",dict[@"title"],dict[@"episode"]);
}
聊天氣泡界面及功能
鍵盤彈起時,
使用約束設(shè)計(jì)Cell
創(chuàng)建高度不定的動態(tài)單元格
點(diǎn)擊右下角不關(guān)鍵盤——設(shè)置不要給文本框連線而是給文本框設(shè)置delegate,在當(dāng)前控制器中遵守UITextFIeldDelegate,實(shí)現(xiàn)shouldClickReturn……方法中不要調(diào)用resignFirstResponder
使用下滑手勢關(guān)閉鍵盤——給tableView添加一個Swipe手勢,設(shè)置該手勢的delegate為當(dāng)前控制器,遵守UIGestureRecoganizerDelegate協(xié)議,實(shí)現(xiàn)一個返回值是BOOL,xxxSimulatn的方法,返回YES
- (void)viewDidLoad {
[super viewDidLoad];
//為了讓tableView自適應(yīng)高度,需要設(shè)置如下兩個屬性
self.tableView.estimatedRowHeight = 70;
self.tableView.rowHeight = UITableViewAutomaticDimension;
//設(shè)置tableView的內(nèi)邊距,向下錯64個點(diǎn)
self.tableView.contentInset = UIEdgeInsetsMake(64, 0, 0, 0);
}
//在view即將顯示時添加對系統(tǒng)發(fā)出的鍵盤通知的監(jiān)聽
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
//為當(dāng)前控制器注冊鍵盤彈起和關(guān)閉通知
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(openKeyboard:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(closeKeyboard:) name:UIKeyboardWillHideNotification object:nil];
}
//在view即將消失時取消鍵盤通知的監(jiān)聽
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
//收到彈起鍵盤的通知后執(zhí)行
-(void)openKeyboard:(NSNotification *)notification{
//讀取彈起的鍵盤的高度
CGFloat keyboardHeight = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
//讀取動畫的時長
CGFloat duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue];
//讀取動畫的種類
NSInteger option = [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] intValue];
//修改底部輸入視圖的bottom約束
self.bottomLayoutConstraint.constant = keyboardHeight;
[UIView animateWithDuration:duration delay:0 options:option animations:^{
[self.view layoutIfNeeded];
//鍵盤彈起后,表視圖移動到最底部
[self scrollToTableViewLastRow];
} completion:nil];
}
//收到收起鍵盤的通知后執(zhí)行
-(void)closeKeyboard:(NSNotification *)notification{
//讀取動畫的時長
CGFloat duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue];
//讀取動畫的種類
NSInteger option = [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] intValue];
//修改底部輸入視圖的bottom約束
self.bottomLayoutConstraint.constant = 0;
[UIView animateWithDuration:duration delay:0 options:option animations:^{
[self.view layoutIfNeeded];
} completion:nil];
}
//控制表視圖滾動到最底部
-(void)scrollToTableViewLastRow{
NSIndexPath * lastIndexPath = [NSIndexPath indexPathForRow:self.allMessage.count-1 inSection:0];
[self.tableView scrollToRowAtIndexPath:lastIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
//界面顯示后,表格已經(jīng)生成了,在滾動到底部
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self scrollToTableViewLastRow];
}