iOS 視圖與視圖層次結構(內容根據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

顯示結果 

 

   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

  然后再比較視圖的寬和高,將較小的值的一般設置為圓形的半徑:

#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
  • 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

  接下來我們定義  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

  路徑已經定義好了,但是之定義路徑不會進行實際的繪制。我們還需要向 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

  繪制結果:

  現在改變圓形的線條的粗細和顏色。

#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

  下面來改變繪制圖形的軌跡顏色

#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

  運行結果:

  這里我們可以嘗試視圖的 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];

}
#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
  • 繪制同心圓 

  在  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

  運行結果:可以看到我們已經畫出了一些列的同心圓,但是屏幕右邊多出了一條奇怪的線條

  這是因為單個 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

  運行結果:完美

  

  • 繪制圖像

  創建一個  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
  •  深入學習: 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 函數改變當前上下文中的畫筆顏色。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM