iOS 視圖與視圖層次結構
-
視圖基礎
-
視圖是 UIView 對象,或者其子對象。
-
視圖知道如何繪制自己。
-
視圖可以處理事件,例如觸摸(touch)。
-
視圖會按照層次結構排列,位于視圖層次結構頂端的是應用窗口。
-
視圖層次結構
任何應用有且只有一個 UIWindow 對象。 UIWindow 對象就像是一個容器,負責包含應用中的所有的視圖。應用需要在啟動時創建并設置 UIWindow 對象,然后為其添加其他視圖。
加入窗口的視圖會成為該窗口的 子視圖 。窗口的子視圖還可以有自己的子視圖,從而構成一個以 UIWindow 對象為根視圖的,類似于樹形結構的視圖層次結構。
視圖層次結構形成之后,系統會將其繪制到屏幕上,繪制過程可以分為兩步:
1.層次結構中的每個視圖(包括 UIWindow 對象)分別繪制自己。視圖會將自己繪制到圖層( layer )上,每個 UIView 對象都有一個 layer 屬性,指向一個 CALayer 類的對象
2. 所有視圖的圖層何曾一幅圖像,繪制到屏幕上。
獲取當前應用程序的 UIWindow 方法是 UIWindow * keyWindow = [UIApplication sharedApplication].keyWindow;
-
創建UIView子類
首先創建一個UIView子類。
視圖及其frame屬性
在控制器中創建一個CGRect結構,然后使用該結構創建一個視圖對象,并將這個視圖對象加入到控制器視圖子視圖上。
import "ViewController.h"
import "JXHypnosisView.h" // 為創建的子類
@interface ViewController ()
@end
@implementation ViewController
(void)viewDidLoad { [super viewDidLoad];
// 創建 CGRect 結構 CGRect rect = CGRectMake(100, 100, 100, 200);
// 創建視圖 JXHypnosisView * firstView = [[JXHypnosisView alloc] initWithFrame:rect]; firstView.backgroundColor = [UIColor redColor];
// 將視圖添加到控制器View上 [self.view addSubview:firstView];
}
@end </pre>
顯示結果
CGRect 結構包含該另外兩個結構:origin 和size 。其中origin 的類型是CGPoint 結構,該結構包含兩個float 類型測成員。size 的類型是CGSize 結構,該結構也包含兩個float 類型的成員:width 和height 。
所以我們創建的視圖對象,在上圖中可以看出 JXHypnosisView 對象的左上角位于父視圖右側100點、下方200點的位置。此外,因為這個frame 結構中的size 是(100,200) ,所以我們自定義JXHypnosisView 對象的寬度是100點、高度是200點 。
我們這里所說的這些值的單位是點(points),不是像素(pixels)。 如果是像素,那么在不同的Retina顯示屏上顯示的大小是不同的。在Retina 顯示屏上,一個點是兩個像素高度。(所以在跟美工溝通的時候最好讓他們根據像素來做圖片,并且圖片的像素大小是點的兩倍,或者三倍)。
每個視圖對象都有一個 superview 屬性。將一個視圖作為子視圖加入另一個視圖時,會自動創建相應的反向關聯。
-
在drawRect:方法中自定義繪圖
前面我們編寫了一個簡單的自定義的JXHypnosisView對象,并且設置了他的一些基本的屬性,如位置,大小,顏色等。在本節中我們將在drawRect:方法中編寫繪圖代碼。
視圖根據drawRect:方法將自己繪制到圖層上。UIView 的子類可以覆蓋drawRect:方法完成自定義的繪圖任務。例如, UIButton的drawRect:方法默認會在frame 表示的矩形區域中心畫出一行淺藍色的文字。
覆蓋drawRect:后首先應該獲取視圖從UIView繼承而來的bounds 屬性,該屬性定義了一個矩形范圍,表示視圖的繪制區域。
視圖在繪制自己時,會參考一個坐標系,bounds 表示的矩形位于自己的坐標系,而frame 表示的矩形位于父視圖的坐標系,但是兩個矩形的大小是相同的。
frame 和bounds 表示的矩形用法不同。前者用于確定與視圖層次結構中其他視圖的相對位置,從而將自己的圖層與其他視圖的圖層正確組合成屏幕上的圖像。而后者屬性用于確定繪制區域,避免將自己繪制到圖層邊界之外(其視圖是相對于自己而言,設置只有寬高有效)。
-
繪制圓形
接下來在JXHypnosisView的drawRect方法中添加繪圖代碼,畫出一個盡可能大的圓形,但是不能好過視圖的繪制區域。
首先,需要根據視圖的bounds屬性找到繪制預期的中心點:
import "JXHypnosisView.h"
@implementation JXHypnosisView
(void)drawRect:(CGRect)rect { CGRect bounds = self.bounds;
// 根據bounds計算中心點 CGPoint center; center.x = bounds.origin.x + bounds.size.width / 2.0; center.y = bounds.origin.y + bounds.size.height / 2.0; }
@end </pre>
然后再比較視圖的寬和高,將較小的值的一般設置為圓形的半徑:
import "JXHypnosisView.h"
@implementation JXHypnosisView
(void)drawRect:(CGRect)rect { CGRect bounds = self.bounds;
// 根據bounds計算中心點 CGPoint center; center.x = bounds.origin.x + bounds.size.width / 2.0; center.y = bounds.origin.y + bounds.size.height / 2.0;
// 根據視圖的寬高比較中的較小的值計算圓形的半徑 float radius = (MIN(bounds.size.width, bounds.size.height) / 2.0); }
@end </pre>
-
UIBezierPath
UIBezierPath 是用來繪制直線或者曲線的一個類。
首先要創建一個 UIBezierPath 對象:
import "JXHypnosisView.h"
@implementation JXHypnosisView
(void)drawRect:(CGRect)rect { CGRect bounds = self.bounds;
// 根據bounds計算中心點 CGPoint center; center.x = bounds.origin.x + bounds.size.width / 2.0; center.y = bounds.origin.y + bounds.size.height / 2.0;
// 根據視圖的寬高比較中的較小的值計算圓形的半徑 float radius = (MIN(bounds.size.width, bounds.size.height) / 2.0);
UIBezierPath * path = [[UIBezierPath alloc] init]; }
@end </pre>
接下來我們定義 UIBezierPath 對象需要繪制的路徑。
import "JXHypnosisView.h"
@implementation JXHypnosisView
(void)drawRect:(CGRect)rect { CGRect bounds = self.bounds;
// 根據bounds計算中心點 CGPoint center; center.x = bounds.origin.x + bounds.size.width / 2.0; center.y = bounds.origin.y + bounds.size.height / 2.0;
// 根據視圖的寬高比較中的較小的值計算圓形的半徑 float radius = (MIN(bounds.size.width, bounds.size.height) / 2.0);
UIBezierPath * path = [[UIBezierPath alloc] init];
// 以中心點為圓心,radius的值為半徑,定義一個 0 到 M_PI * 2.0 弧度的路徑(整圓) [path addArcWithCenter:center
radius:radius startAngle:0.0 endAngle:M_PI * 2.0 clockwise:YES];
}
@end </pre>
路徑已經定義好了,但是之定義路徑不會進行實際的繪制。我們還需要向 UIBezierPath 對象發送消息,繪制之前定制的路徑:
import "JXHypnosisView.h"
@implementation JXHypnosisView
(void)drawRect:(CGRect)rect { CGRect bounds = self.bounds;
// 根據bounds計算中心點 CGPoint center; center.x = bounds.origin.x + bounds.size.width / 2.0; center.y = bounds.origin.y + bounds.size.height / 2.0;
// 根據視圖的寬高比較中的較小的值計算圓形的半徑 float radius = (MIN(bounds.size.width, bounds.size.height) / 2.0);
UIBezierPath * path = [[UIBezierPath alloc] init];
// 以中心點為圓心,radius的值為半徑,定義一個 0 到 M_PI * 2.0 弧度的路徑(整圓) [path addArcWithCenter:center
radius:radius startAngle:0.0 endAngle:M_PI * 2.0 clockwise:YES];
// 繪制路徑 [path stroke]; }
@end </pre>
繪制結果:
現在改變圓形的線條的粗細和顏色。
import "JXHypnosisView.h"
@implementation JXHypnosisView
(void)drawRect:(CGRect)rect { CGRect bounds = self.bounds;
// 根據bounds計算中心點 CGPoint center; center.x = bounds.origin.x + bounds.size.width / 2.0; center.y = bounds.origin.y + bounds.size.height / 2.0;
// 根據視圖的寬高比較中的較小的值計算圓形的半徑 float radius = (MIN(bounds.size.width, bounds.size.height) / 2.0);
UIBezierPath * path = [[UIBezierPath alloc] init];
// 以中心點為圓心,radius的值為半徑,定義一個 0 到 M_PI * 2.0 弧度的路徑(整圓) [path addArcWithCenter:center
radius:radius startAngle:0.0 endAngle:M_PI * 2.0 clockwise:YES];
// 設置線條寬度為 10 點 path.lineWidth = 10;
// 繪制路徑 [path stroke]; }
@end </pre>
下面來改變繪制圖形的軌跡顏色
import "JXHypnosisView.h"
@implementation JXHypnosisView
(void)drawRect:(CGRect)rect { CGRect bounds = self.bounds;
// 根據bounds計算中心點 CGPoint center; center.x = bounds.origin.x + bounds.size.width / 2.0; center.y = bounds.origin.y + bounds.size.height / 2.0;
// 根據視圖的寬高比較中的較小的值計算圓形的半徑 float radius = (MIN(bounds.size.width, bounds.size.height) / 2.0);
UIBezierPath * path = [[UIBezierPath alloc] init];
// 以中心點為圓心,radius的值為半徑,定義一個 0 到 M_PI * 2.0 弧度的路徑(整圓) [path addArcWithCenter:center
radius:radius startAngle:0.0 endAngle:M_PI * 2.0 clockwise:YES];
// 設置線條寬度為 10 點 path.lineWidth = 10;
// 設置繪制顏色為灰色 [[UIColor lightGrayColor] setStroke];
// 繪制路徑 [path stroke]; }
@end </pre>
運行結果:
這里我們可以嘗試視圖的 backgroundColor 屬性不會受到drawRect中代碼的影響,通常應該將重寫drawRect方法的視圖的背景色設置為透明對應于clearColor),這樣可以讓視圖只顯示drawRect 方法中繪制的內容。
import "ViewController.h"
import "JXHypnosisView.h"
@interface ViewController ()
@end
@implementation ViewController
(void)viewDidLoad { [super viewDidLoad];
// 創建 CGRect 結構 CGRect rect = CGRectMake(100, 200, 200, 300);
// 創建視圖 JXHypnosisView * firstView = [[JXHypnosisView alloc] initWithFrame:rect]; firstView.backgroundColor = [UIColor redColor]; NSLog(@"%f",firstView.bounds.origin.x); // 將視圖添加到控制器View上 [self.view addSubview:firstView];
} </pre>
import "JXHypnosisView.h"
@implementation JXHypnosisView
(instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) {
// 設置 JXHypnosisView 對象的背景顏色為透明 self.backgroundColor = [UIColor clearColor];
} return self; }
(void)drawRect:(CGRect)rect { CGRect bounds = self.bounds;
// 根據bounds計算中心點 CGPoint center; center.x = bounds.origin.x + bounds.size.width / 2.0; center.y = bounds.origin.y + bounds.size.height / 2.0;
// 根據視圖的寬高比較中的較小的值計算圓形的半徑 float radius = (MIN(bounds.size.width, bounds.size.height) / 2.0);
UIBezierPath * path = [[UIBezierPath alloc] init];
// 以中心點為圓心,radius的值為半徑,定義一個 0 到 M_PI * 2.0 弧度的路徑(整圓) [path addArcWithCenter:center
radius:radius startAngle:0.0 endAngle:M_PI * 2.0 clockwise:YES];
// 設置線條寬度為 10 點 path.lineWidth = 10;
// 設置繪制顏色為灰色 [[UIColor lightGrayColor] setStroke];
// 繪制路徑 [path stroke]; }
@end </pre>
-
繪制同心圓
在 JXHypnosisView中繪制多個同心圓有兩個方法,第一個方法是創建多個UIBezierPath對象,每個對象代表一個圓形;第二個方法是使用一個UIBezierPath對象繪制多個圓形,為每個圓形定義一個繪制路徑。很明顯第二種方法更好。
import "JXHypnosisView.h"
@implementation JXHypnosisView
(instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) {
// 設置 JXHypnosisView 對象的背景顏色為透明 self.backgroundColor = [UIColor clearColor];
} return self; }
(void)drawRect:(CGRect)rect { CGRect bounds = self.bounds;
// 根據bounds計算中心點 CGPoint center; center.x = bounds.origin.x + bounds.size.width / 2.0; center.y = bounds.origin.y + bounds.size.height / 2.0;
// 根據視圖的寬高比較中的較小的值計算圓形的半徑 float radius = (MIN(bounds.size.width, bounds.size.height) / 2.0);
// 是最外層圓形成為視圖的外接圓 float maxRadius = hypotf(bounds.size.width, bounds.size.height) / 2.0;
UIBezierPath * path = [[UIBezierPath alloc] init];
// 以中心點為圓心,radius的值為半徑,定義一個 0 到 M_PI * 2.0 弧度的路徑(整圓) [path addArcWithCenter:center
radius:radius startAngle:0.0 endAngle:M_PI * 2.0 clockwise:YES];
for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) { [path addArcWithCenter:center radius:currentRadius startAngle:0.0 endAngle:M_PI * 2.0 clockwise:YES]; }
// 設置線條寬度為 10 點
path.lineWidth = 10;
// 設置繪制顏色為灰色
[[UIColor lightGrayColor] setStroke];
// 繪制路徑
[path stroke];
}
@end </pre>
運行結果:可以看到我們已經畫出了一些列的同心圓,但是屏幕右邊多出了一條奇怪的線條
這是因為單個UIBezierPath對象將多個路徑(每個路徑可以畫出一個圓形)連接起來,形成了一個完成的路徑。可以將UIBezierPath對象想象成一支在紙上畫畫的鉛筆-但是當我們繪制完成一個圓形之后去繪制另外一個圓形時,鉛筆并沒有抬起,所以才會出現一條很奇怪的線條。
import "JXHypnosisView.h"
@implementation JXHypnosisView
(instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) {
// 設置 JXHypnosisView 對象的背景顏色為透明 self.backgroundColor = [UIColor clearColor];
} return self; }
(void)drawRect:(CGRect)rect { CGRect bounds = self.bounds;
// 根據bounds計算中心點 CGPoint center; center.x = bounds.origin.x + bounds.size.width / 2.0; center.y = bounds.origin.y + bounds.size.height / 2.0;
// 是最外層圓形成為視圖的外接圓 float maxRadius = hypotf(bounds.size.width, bounds.size.height) / 2.0;
UIBezierPath * path = [[UIBezierPath alloc] init];
for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) {
// 用來設置繪制起始位置 [path moveToPoint:CGPointMake(center.x + currentRadius, center.y)]; [path addArcWithCenter:center radius:currentRadius startAngle:0.0 endAngle:M_PI * 2.0 clockwise:YES];
}
// 設置線條寬度為 10 點 path.lineWidth = 10;
// 設置繪制顏色為灰色 [[UIColor lightGrayColor] setStroke];
// 繪制路徑 [path stroke];
}
@end </pre>
運行結果:完美
-
繪制圖像
創建一個UIImage 對象:UIImage * logoImage = [UIImage imageNamed: @" train " ]; ,然后在drawRect方法中將圖像會知道視圖上:[logoImage drawInRect:someRect]
import "JXHypnosisView.h"
@implementation JXHypnosisView
(instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) {
// 設置 JXHypnosisView 對象的背景顏色為透明 self.backgroundColor = [UIColor clearColor];
} return self; }
(void)drawRect:(CGRect)rect {
CGRect bounds = self.bounds;
// 根據bounds計算中心點
CGPoint center;
center.x = bounds.origin.x + bounds.size.width / 2.0;
center.y = bounds.origin.y + bounds.size.height / 2.0;
// 是最外層圓形成為視圖的外接圓
float maxRadius = hypotf(bounds.size.width, bounds.size.height) / 2.0;
UIBezierPath * path = [[UIBezierPath alloc] init];
for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) {
// 用來設置繪制起始位置
[path moveToPoint:CGPointMake(center.x + currentRadius, center.y)];
[path addArcWithCenter:center
radius:currentRadius
startAngle:0.0
endAngle:M_PI * 2.0
clockwise:YES];
}
// 設置線條寬度為 10 點
path.lineWidth = 10;
// 設置繪制顏色為灰色
[[UIColor lightGrayColor] setStroke];
// 繪制路徑
[path stroke];
// 創建UIImage對象
UIImage * logoImage = [UIImage imageNamed:@"train"];
// 繪制圖像
[logoImage drawInRect:bounds];
}
@end </pre>
-
深入學習:Core Graphics
UIImage、UIBezierPath和NSString都提供了至少一種用于在drawRect中繪圖的方法,這些繪圖的方法會在drawRect執行時分別將圖像,圖形,和文本繪制到視圖的圖層上。
無論是繪制JPEG、PDF還是視圖的圖層,都是由Core Graphics 框架完成的。Core Graphics是一套提供2D繪圖功能的C語言API,使用C結構和C函數模擬了一套面向對象的編程機制,并沒有OC對象和方法。Core Graphics 中最重要的“對象”是圖形上下文 ,圖形上下文是CGContextRef的“對象”,負責存儲繪畫狀態(例如畫筆顏色和線條粗細)和繪制內容所處的內存空間。
視圖的drawRect方法在執行之前,系統首先為視圖的圖層創建一個圖形上下文,然后為繪畫狀態設置一些默認參數。drawRect方法開始執行時,隨著圖形上下文不斷執行繪圖操作,圖層上的內容也會隨之改變。drawRect執行完畢后,系統會將圖層與其他圖層一起組合成完整的圖像并顯示在屏幕上。
參與繪圖操作的類都定義了改變繪畫狀態和執行繪圖操作的方法,這些方法其實調用了對應的Core Graphics 函數。例如,向UIColor對象發送setCtroke 消息時,會調用Core Graphics 中的 CGContextSetRGBSrokeColor 函數改變當前上下文中的畫筆顏色。
來自:http://www.cnblogs.com/wang-com/p/5862931.html