2012年1月6日 星期五

Filter4Cam 學習之 CALayers Tutorial

since: 2011/12/31
update: 2012/01/06

reference: Introduction to CALayers Tutorial | Ray Wenderlich

A. 什麼是 CALayers ?
   1. CALayers 只是一個用來在螢幕上描繪可視內容的矩形之類別, 沒錯這也是 UIViews
      做的事. 但是這只是一個手法: 每一個 UIView 所畫的內容, 都包含了一個 root layer!
      你可以從以下的 code 來存取這個 layer(預設已建好的):
      CALayer *myLayer = myView.layer;

   2. CALayer 類別的好處是: 它包含了一大堆可以設定的屬性讓你用來改變可見的外觀,
      例如:
      a. 圖層(layer) 的大小與位置.
      b. 圖層的背景顏色.
      c. 圖層的內容(圖像或用 Core Graphics 繪製的內容).
      d. 圖層的轉角是否使用圓形的.
      e. 為圖層設定陰影.
      f. 為圖層設定邊框.
      g. 其它等等.

   ---------------------------------------------------------------------------------------------

B. 開始新專案
   1. Xcode > File > New > New Project...
       iOS > Application > Single View Application > Next >
       Product Name: LayerFun
       Device Family: iPhone
       Used Storyboard: checked
       Used Automatic Reference Couting: checked
       > Next

   2. 首先, 將 CALayersCore Animation 會使用到的 framework: QuartzCore 加到專案
       裡. 接著修改 ViewController.m 檔案如下:
// Import QuartzCore.h at the top of the file
#import <QuartzCore/QuartzCore.h>

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //@add
    // set the layer's background color to orange
    self.view.layer.backgroundColor = [UIColor orangeColor].CGColor;
    // round the corners a bit by setting the corner radius
    self.view.layer.cornerRadius = 20.0;
    // shrink the frame a bit so it's easier to see (not work here)
    self.view.layer.frame = CGRectInset(self.view.layer.frame, 20, 20);
}

   3. 編譯並執行:

   ---------------------------------------------------------------------------------------------

C. CALayers 與 Sublayers

  1. 就像 UIViews 可以有 subviews, CALayers 也可以有 sublayers. 你可以建立一個新的
     CALayer 如下所示:

     CALayer *sublayer = [CALayer layer];

  2. 一旦有了 CALayer, 就可以設定任何你想要的屬性. 記住, 有一項屬性必須明確地設定:
     它的 frame (或 bounds/position). 接著, 便可以將新的 layer 當成其它 layer 的
      sublayer:
     [myLayer addSublayer:sublayer];

  3. 在 ViewController.m 檔案裡的 viewDidLoad 方法裡, 替目前 view 的 layer 加入一個
      sublayer :

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //@add
    self.view.layer.backgroundColor = [UIColor orangeColor].CGColor;
    self.view.layer.cornerRadius = 20.0;
    self.view.layer.frame = CGRectInset(self.view.layer.frame, 80, 80);
   
    //@add sublayer
    CALayer *sublayer = [CALayer layer];
    sublayer.backgroundColor = [UIColor blueColor].CGColor;
    // set shadows
    sublayer.shadowOffset = CGSizeMake(0, 3);
    sublayer.shadowRadius = 5.0;
    sublayer.shadowColor = [UIColor blackColor].CGColor;
    sublayer.shadowOpacity = 0.8;
    // sets the frame
    sublayer.frame = CGRectMake(30, 30, 128, 192);
    [self.view.layer addSublayer:sublayer];
}

   說明: 這些座標是相對於 parent layer's frame 的.

  4. 編譯並執行:

   ---------------------------------------------------------------------------------------------

D. 設定 CALayer 影像內容
   1. 在 ViewController.m 檔案裡的 viewDidLoad 方法裡, 在 addSublayer: 之前,
      加入以下的 code:

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    ....
   
    //@add
    sublayer.contents = (id) [UIImage imageNamed:@"BattleMapSplashScreen.jpg"].CGImage;

    sublayer.borderColor = [UIColor blackColor].CGColor;
    sublayer.borderWidth = 2.0;
   
    [self.view.layer addSublayer:sublayer];
}

   2. 編譯並執行:

   ---------------------------------------------------------------------------------------------

E. 圓角與影像內容
   1. 如果在有影像內容的 CALayer 設定 cornerRadius, 影像仍然會畫超出圓角的範圍.
       可以藉由設定 sublayer.masksToBounds = YES 來解決, 但是如此一來陰影就會被
       遮罩屏除, 而無法顯示.

   2. 解決辦法之一, 建立二個 layers. 外部的 layer 僅僅只是一個著色的 CALayer 並賦予
       外框與陰影. 內部的 layer 包含影像, 並且為圓角的遮罩. 如此一來, 外部的 layer 能夠
       畫出陰影, 並且內部的 layer 包含著影像.

   3. 在 ViewController.m 檔案裡的 viewDidLoad 方法裡, 修改以下的 code:
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //@add
    self.view.layer.backgroundColor = [UIColor orangeColor].CGColor;
    self.view.layer.cornerRadius = 20.0;
    self.view.layer.frame = CGRectInset(self.view.layer.frame, 80, 80);

    CALayer *sublayer = [CALayer layer];
    sublayer.backgroundColor = [UIColor blueColor].CGColor;
    sublayer.shadowOffset = CGSizeMake(0, 3);
    sublayer.shadowRadius = 5.0;
    sublayer.shadowColor = [UIColor blackColor].CGColor;
    sublayer.shadowOpacity = 0.8;
    sublayer.frame = CGRectMake(30, 30, 128, 192);
    sublayer.borderColor = [UIColor blackColor].CGColor;
    sublayer.borderWidth = 2.0;
    sublayer.cornerRadius = 10.0;
    [self.view.layer addSublayer:sublayer];
   
    CALayer *imageLayer = [CALayer layer];
    imageLayer.frame = sublayer.bounds;
    imageLayer.cornerRadius = 10.0;
    imageLayer.contents = (id) [UIImage imageNamed:@"BattleMapSplashScreen.jpg"].CGImage;

    imageLayer.masksToBounds = YES;
    [sublayer addSublayer:imageLayer];
}

   4. 編譯並執行:

   ---------------------------------------------------------------------------------------------

F. CALayer 與客製化繪圖內容

   1. 如何利用 Core Graphics 來客製化繪製 layer 的內容:
      概念是: 將一個類別設為 layer 的 delegate, 並且該類別必需實作
      drawLayer:inContext 方法. 這樣便能夠在該方法內包含任何 Core Graphics 繪製
      的 code.  
    
   2. 現在試著做: 新增一個 layer 並且在其內繪製一個圖案. 你將會需要把 view controller
       設為 layer 的 delegate, 並且實作 drawLayer:inContext 方法來繪製圖案. 繪製圖案的
       code 將會使用到與 Core Graphics 101: Patterns 相同的 code.

   3. 在 ViewController.m 檔案裡的 viewDidLoad 方法裡, 新增以下的 code:
- (void)viewDidLoad
{
    [super viewDidLoad];
    ....
    //@add
    CALayer *customDrawn = [CALayer layer];
    customDrawn.delegate = self;
    customDrawn.backgroundColor = [UIColor greenColor].CGColor;
    customDrawn.frame = CGRectMake(30, 250, 128, 40);
    customDrawn.shadowOffset = CGSizeMake(0, 3);
    customDrawn.shadowRadius = 5.0;
    customDrawn.shadowColor = [UIColor blackColor].CGColor;
    customDrawn.shadowOpacity = 0.8;
    customDrawn.cornerRadius = 10.0;
    customDrawn.borderColor = [UIColor blackColor].CGColor;
    customDrawn.borderWidth = 2.0;
    customDrawn.masksToBounds = YES;
    [self.view.layer addSublayer:customDrawn];
    [customDrawn setNeedsDisplay];
}

    說明: a. customDrawn 這個 layer 將 delegate 設為 self (view controller). 意謂著,
                    self
(view controller) 必需去實作 drawLayer:inContext 這個方法來繪製
                    layer 的內容. 

              b. 新增 layer 之後, 必需藉由呼叫 setNeedsDisplay 方法來告訴 layer 要去
                   refresh 自己本身(並且呼叫 drawLayer:inContext) , 否則
                   drawLayer:inContext 將不會被呼叫到.

   4. 在 ViewController.h 檔案中, 新增以下的 code, 用來宣告繪製圖案的方法:
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
{
}

//@add
void MyDrawColoredPattern (void *info, CGContextRef context);

@end

   5. 在 ViewController.m 檔案中, 新增以下的 code, 用來定義繪製圖案方法的內容與
       實作 drawLayer:inContext 方法:

#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>

//@add: 如果 Xcode 出現: Implicit declaration of function 'radians' is invalid in C99
static inline double radians (double degrees) {return degrees * M_PI/180;}
....
void MyDrawColoredPattern (void *info, CGContextRef context) {
 
    CGColorRef dotColor = [UIColor colorWithHue:0 saturation:0 brightness:0.07 alpha:1.0].CGColor;
    CGColorRef shadowColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.1].CGColor;
 
    CGContextSetFillColorWithColor(context, dotColor);
    CGContextSetShadowWithColor(context, CGSizeMake(0, 1), 1, shadowColor);
 
    CGContextAddArc(context, 3, 3, 4, 0, radians(360), 0);
    CGContextFillPath(context);
 
    CGContextAddArc(context, 16, 16, 4, 0, radians(360), 0);
    CGContextFillPath(context);
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context {
 
    CGColorRef bgColor = [UIColor colorWithHue:0.6 saturation:1.0 brightness:1.0 alpha:1.0].CGColor;
    CGContextSetFillColorWithColor(context, bgColor);
    CGContextFillRect(context, layer.bounds);
 
    static const CGPatternCallbacks callbacks = { 0, &MyDrawColoredPattern, NULL };
 
    CGContextSaveGState(context);
    CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
    CGContextSetFillColorSpace(context, patternSpace);
    CGColorSpaceRelease(patternSpace);
 
    CGPatternRef pattern = CGPatternCreate(NULL,
                                           layer.bounds,
                                           CGAffineTransformIdentity,
                                           24,
                                           24,
                                           kCGPatternTilingConstantSpacing,
                                           true,
                                           &callbacks);
    CGFloat alpha = 1.0;
    CGContextSetFillPattern(context, pattern, &alpha);
    CGPatternRelease(pattern);
    CGContextFillRect(context, layer.bounds);
    CGContextRestoreGState(context);
}

   6. 編譯並執行:

沒有留言:

張貼留言

注意:只有此網誌的成員可以留言。