iOS 視圖與視圖層次結構

jieyan 8年前發布 | 10K 次閱讀 iOS開發 移動開發

  • 視圖基礎

  1. 視圖是  UIView  對象,或者其子對象。

  2. 視圖知道如何繪制自己。

  3. 視圖可以處理事件,例如觸摸(touch)。

  4. 視圖會按照層次結構排列,位于視圖層次結構頂端的是應用窗口。

  • 視圖層次結構

任何應用有且只有一個 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

 

 本文由用戶 jieyan 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!