I.5 變形

不幸的是,沒人能說出矩陣是什么媳搪,你得自己去觀察铭段。——Morpheus秦爆,The Matrix

在第4章“視覺特效”中我們講解了一些提高圖層序愚、內(nèi)容外觀表現(xiàn)的技術(shù)。這一章中等限,我們研究CGAffineTransform用于圖層旋轉(zhuǎn)爸吮、位移以及變形芬膝。CATransform3d可以將一般的平面矩形(即使是有陰影的圓角矩形)變?yōu)槿S平面。

仿射變形

在第3章“圖層幾何”中形娇,我們用UIViewtransform屬性來旋轉(zhuǎn)時鐘的指針锰霜,但我們并沒有解釋其背后的原理。UIViewtransform屬性是CGAffineTransform屬性桐早,被用于表現(xiàn)二維的旋轉(zhuǎn)癣缅、縮放和位移。CGAffineTransform是一個3行2列的矩陣勘畔,它可以乘以一個二維行向量(在這里是CGPoint)來轉(zhuǎn)換其值(見圖中的黑體值)所灸。

其乘法是將CGPoint向量的每個列值去乘CGAffineTransform矩陣中的每一行,然后結(jié)果可加得到一個新的CGPoint炫七。這在圖中用灰色值表示爬立;要想矩陣乘法成產(chǎn),左邊的矩陣的列數(shù)必須等于右邊矩陣的行數(shù)万哪。因此我們得用單位矩陣來填滿它們侠驯,這樣可以讓計算得以開展而不會影響結(jié)果。我們并不真的需要存儲額外的值奕巍,因為它們并不會改變但在計算時需要它們吟策。

因此,你會經(jīng)车闹梗看見一個二維變形被表示成33的矩陣(而不是23)檩坚。你也會經(jīng)常看見當(dāng)向量值豎直疊在一起時诅福,這個矩陣以2行3列的格式表示匾委。這被稱為列主序格式。我們在圖5.1中顯示的是行主序格式氓润。任何你喜歡的表示形式都是可以的赂乐。

圖5.1 CGAffineTransform和CGPoint用來表示矩陣

當(dāng)變形矩陣應(yīng)用在圖層上時,圖層矩陣的第個角上的點會獨立變形咖气,這會形成一個新的四邊形挨措。CGAffineTransform的“仿射”是指無論矩陣用了什么值,圖層中平行線變形后仍是平行的崩溪。CGAffineTransform可以用于任何符合標(biāo)準(zhǔn)的變形浅役。圖5.2展示了一些仿射或非仿射的變形:

圖5.2 仿射和非仿射變形

創(chuàng)建仿射變形

矩陣數(shù)學(xué)的完整解釋超出了本書的范圍,如果你對矩陣不是早已了解悯舟,變形矩陣的概念可能有點可怕 担租。幸運的是,Core Graphics提供了一些內(nèi)置函數(shù)來直接構(gòu)造簡單變形抵怎,這不需要開發(fā)者進(jìn)行任何數(shù)學(xué)計算奋救。下面的函數(shù)每個都可以創(chuàng)建一個新的CGAffineTransform矩陣:

CGAffineTransformMakeRotation(CGFloat angle)
CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)
CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)

旋轉(zhuǎn)和縮放變形是顯而易見的岭参,它們各自旋轉(zhuǎn)、縮放一個向量尝艘。而位移變形僅是給向量增加特定的x和y值演侯,所以如果向量表示點,它會移動點背亥。

讓我們用一個簡單的項目來演示這些函數(shù)的效果秒际。我們從一個簡單的視圖開始,給它加上一個45度的旋轉(zhuǎn)變形(如圖5.3)狡汉。

圖5.3 用仿射變形旋轉(zhuǎn)45度的視圖

UIView可以通過設(shè)置transform屬性來變形娄徊,但如同所有的布局屬性一樣,UIViewtransform實際上只是一個CALayer特性的封裝盾戴。

CALayer也有一個transform屬性寄锐,但它的類型是CATransform3D,而不是CGAffineTransform尖啡。我們將在這章后面介紹橄仆,但現(xiàn)在并不是我們關(guān)心的。CALayer中的affineTransform等同于UIView中的transform屬性衅斩。表5.1展示了使用affineTransform屬性45度順時針旋轉(zhuǎn)圖層的代碼盆顾。

表5.1 用affineTransform來45度旋轉(zhuǎn)圖層
@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *layerView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    //rotate the layer 45 degrees
    CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI_4);
    self.layerView.layer.affineTransform = transform; }

@end

譯者代碼:

   import UIKit
   class ViewController: UIViewController {
       @IBOutlet weak var layerView: UIView!
       override func viewDidLoad() {
           super.viewDidLoad()
           // 45度旋轉(zhuǎn)圖層
           let transform = CGAffineTransformMakeRotation(CGFloat(M_PI_4))
           self.layerView.layer.setAffineTransform(transform)
       }
   }

注意我們用于角度的值是一個叫M_PI_4的常量,而不是你可能以為的45畏梆。iOS中的所有角度的變形函數(shù)都使用弧度而非角度您宪。弧度通常是特定倍數(shù)的數(shù)學(xué)常量π(pi)奠涌〔系樱弧度π等于180度,所以π除以4等于45度铣猩。

C的數(shù)學(xué)庫(會自動在每個iOS項目中引入)提供了一些普通倍數(shù)的π便于使用,M_PI_4是一個表示π除以4的常量茴丰。如果用弧度思考不方便达皿,你可以使用如下宏來進(jìn)行弧度、角度的轉(zhuǎn)換:

#define RADIANS_TO_DEGREES(x) ((x) / M_PI * 180.0)
#define DEGREES_TO_RADIANS(x) ((x) / 180.0 * M_PI)

組合變形

Core Graphics也提供一組在已有變形之上進(jìn)一步變形的函數(shù)贿肩。當(dāng)你想創(chuàng)建一個單獨的既縮放旋轉(zhuǎn)的變形矩陣時十分有用峦椰。如下:

CGAffineTansformRotate(CGAffineTansform t, CGFloat angle)
CGAffineTansformScale(CGAffineTansform t, CGFloat sx, CGFloat sy)
CGAffineTansformTranslate(CGAffineTansform t, CGFloat tx, CGFloat ty)

當(dāng)你操作變形時,創(chuàng)建一個什么都不做的變形往往十分有用汰规,即CGAffineTansform等于零或空汤功。在矩陣的世界里,這樣一個值被稱為單位矩陣溜哮,Core Graphics為這個提供一個十分方便的常量:

CGAffineTansformIdentity

最后滔金,如果你想結(jié)合兩個已有的變形矩陣色解,你可以用如下函數(shù),這將會從丙個已有矩陣中創(chuàng)建一個新的CGAffineTransform矩陣:

CGAffineTansformConcat(CGAffineTansform t1, CGAffineTansform t2);

讓我們用這些結(jié)合函數(shù)創(chuàng)造更復(fù)雜的變形餐茵。我們將依次應(yīng)用50%縮放科阎、30度旋轉(zhuǎn)以及向右位移200點(如表5.2)。圖5.4展示了最終結(jié)果忿族。

表5.2 用多個函數(shù)創(chuàng)建一個組合變形
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var layerView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // 創(chuàng)建一個新的變形
        var transform = CGAffineTransformIdentity

        // 縮放50%
        transform = CGAffineTransformScale(transform, 0.5, 0.5)

        // 旋轉(zhuǎn)30度
        transform = CGAffineTransformRotate(transform, CGFloat(M_PI) / 180.0 * 30.0)

        // 位移200點
        transform = CGAffineTransformTranslate(transform, 200, 0)

        // 應(yīng)用到圖層上
        self.layerView.layer.setAffineTransform(transform)
    }

}
圖5.4 依次應(yīng)用多個仿射變形的效果

有一點和圖5.4不符的是:圖像右移距離不是指定的200點锣笨,它也同樣向下移動而不是僅僅平移。其原因在于你是依次施加變形的道批,前的的變形會影響后面的變形错英。200點的向右位移被旋轉(zhuǎn)30度然后縮放50%,所以它實際上變成斜向下的100點隆豹。

這意味著你應(yīng)用變形的順序會影響結(jié)果椭岩;先旋轉(zhuǎn)后位移不同于先位移后旋轉(zhuǎn)。

剪切變換

因為Core Graphics為你提供了正確計算變形矩陣的函數(shù)噪伊,你很少需要直接設(shè)置CGAffineTransform的值簿煌,除非你想創(chuàng)建剪切形變,這在Core Graphics中是沒有內(nèi)置函數(shù)的鉴吹。

剪切變形是第四種放射變形姨伟。它比位移、旋轉(zhuǎn)和縮放少見(這是為什么Core Graphics沒有為它提供內(nèi)置函數(shù))豆励,但它有時十分有用夺荒。它可以很好地應(yīng)用于圖片(如圖5.5)。術(shù)語就是“扭曲”良蒸。表5.3展示了用于剪切變形函數(shù)的代碼技扼。

圖5.5 一個水平的扭曲
表5.3 實現(xiàn)扭曲變形
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var layerView: UIView!

    func CGAffineTransformMakeShear(x: CGFloat, y: CGFloat) -> CGAffineTransform {
        var transform = CGAffineTransformIdentity
        transform.c = -x
        transform.b = y
        return transform
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // 扭曲45度圖層
        self.layerView.layer.setAffineTransform(CGAffineTransformMakeShear(1, y: 0))
    }

}

3D變形

正好CG前綴所示,CGAffineTransform類型屬于Core Graphics框架嫩痰。Core Graphics是一個嚴(yán)格的2D繪圖API剿吻,CGAffineTransform只能用于2D變形(就是只能用于二維平面)。

在第3章中串纺,我們看了zPosition屬性丽旅,這允許我們前后移動圖層(相對于用戶視窗)。transform屬性(是一個CATransform3D煩死)包含了這一方法纺棺,允許我們在三維空間移動旋轉(zhuǎn)圖層榄笙。

CGAffineTransform一樣,CATransform3D是一個矩陣祷蝌。但并不是一個23矩陣茅撞,CATransform3D是一個44矩陣可以直接在3D中變形一個點(如圖5.6)。

圖5.6 CATransform3D矩陣變形一個3D點

Core Animation提供一些如同CGAffineTransform矩陣一樣的函數(shù)來創(chuàng)建組合CATransform3D矩陣。這些函數(shù)和Core Grapihcs很像米丘,但3D位移和縮放提供一個額外的z參數(shù)剑令,而且旋轉(zhuǎn)函數(shù)接收xyz角度表示蠕蚜,它們共同組成了旋轉(zhuǎn)軸的向量尚洽。

CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z)
CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz)
CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz)

你現(xiàn)在應(yīng)該對X軸和Y軸很熟悉,它們是分別向右和下沿伸的(盡管你可能回憶起第3章中介紹說在這只是在iOS上靶累,而在Mac OS上Y軸向上指)腺毫。Z軸與它們垂直并且指向鏡頭(如圖5.7)。

圖5.7 X挣柬、Y和Z軸及繞它們旋轉(zhuǎn)的平面

正如你在圖中所見潮酒,繞Z軸旋轉(zhuǎn)相當(dāng)于我們先前提及的3D仿射旋轉(zhuǎn)。而繞X軸或Y軸旋轉(zhuǎn)會旋轉(zhuǎn)出屏幕的二維平面向鏡頭傾斜邪蛔。

讓我舉個例子:表5.4的代碼使用CATransform3DMakeRotation來繞Y軸旋轉(zhuǎn)我們視圖的主圖層急黎。我們可以把它想成向右傾斜視圖,因此我們是以一個角度來看它的侧到。

結(jié)果在圖5.8勃教,但它并不大像我們想像中的樣子。

表5.4 繞Y軸旋轉(zhuǎn)圖層
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var layerView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // 繞Y軸45度旋轉(zhuǎn)圖層
        let transform = CATransform3DMakeRotation(CGFloat(M_PI_4), 0, 1, 0)
        self.layerView.layer.transform = transform
    }

}
圖5.8 一個繞Y軸旋轉(zhuǎn)45度的視圖

它起來壓根不像是旋轉(zhuǎn)了匠抗;它看起來更像是使用縮放變形水平壓縮了故源。我們做錯了嗎?

不汞贸,它的確是正確的绳军。視圖看起來窄了是因為我們斜斜的看它,所以只一部分面對鏡頭矢腻。它看起來不正確的原因是沒有透視门驾。

透視投影

在現(xiàn)實中,由于透視會導(dǎo)致近大遠(yuǎn)小多柑。我們可能期望遠(yuǎn)處的視圖看起來比近處的視圖小奶是,但這并沒有發(fā)生。我們現(xiàn)在看到的視圖是等軸測投影竣灌,這是一個提供平行線的3D繪圖方法诫隅,與我們先前提及的仿射變形更為相似。

在等軸測投影中帐偎,遠(yuǎn)處的物體和近處的物體縮放比例一致。這種投影有其用處(如蛔屹,對于建筑繪圖或鳥瞰圖以及偽3D視頻游戲等)削樊,但它不是我們現(xiàn)在想要的。

為了修復(fù)這點,我們需要修改我們的變形矩陣來在我們先前用的旋轉(zhuǎn)變形之外引入透視變形(有時也稱作Z變形)漫贞。Core Animation沒有提供給我們?nèi)魏魏瘮?shù)來設(shè)置透視變形甸箱,所以我們不得不手動修改我們的矩陣。幸運的是迅脐,這十分簡單:CATransform#D的透視效果由矩陣中的m34元素的值控制芍殖。m34值(如圖5.9所示)被用于計算X和Y值部分縮放遠(yuǎn)離鏡頭多少。

圖5.9 CATransform3D中用于透視的m34值

默認(rèn)下谴蔑,m34值為0豌骏。我們可以對我們的場景設(shè)置m34屬性一個-1.0/d的值,這里d是想像中的鏡頭和屏幕的距離隐锭,用點來衡量窃躲。我們?nèi)绾斡嬎氵@個距離應(yīng)該是多少?我們并不需要真的計算钦睡,我們只需要編造一點蒂窒。

因為鏡頭并非真實存在,我們可以隨意指定它的位置只要看起來不錯就可以荞怒。通常500到1000的值效果不錯洒琢,但你可能發(fā)現(xiàn)或大或小的值可能對特定排列的視圖效果更好『肿溃縮小距離值會增加透視效果衰抑,所以一個非常小的值會看起來特別扭曲,一個很大的值會看起來和沒有透視效果一樣(等軸測)撩嚼。表5.5展示了給我們視圖添加透視的代碼停士,圖5.10展示了結(jié)果。

表5.5 給變形添加透視
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var layerView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // 創(chuàng)建一個變形
        var transform = CATransform3DIdentity

        // 應(yīng)用透視
        transform.m34 = -1.0 / 500.0

        // 繞Y軸45度旋轉(zhuǎn)圖層
        transform = CATransform3DRotate(transform, CGFloat(M_PI_4), 0, 1, 0)

        // 應(yīng)用到圖層
        self.layerView.layer.transform = transform
    }

}
圖5.10 我們應(yīng)用了透視變形的視圖

盡頭

當(dāng)以透視繪制時,遠(yuǎn)離鏡頭的物體會變小。如果離的足夠遠(yuǎn)榜苫,它們會縮小成一個點竹习。所有遠(yuǎn)處的物體最終會交匯于同一個盡頭。

在現(xiàn)實中碍庵,盡頭通常是你視圖的中心(如圖5.11),一般我們要在應(yīng)用里創(chuàng)建真實的透視,盡頭應(yīng)該在屏幕中心薄辅,或者至少有你3D物體的視圖中心。

圖5.11 盡頭

Core Animation定義盡頭在被變形的圖層anchorPoint處(這通常是圖層中心抠璃,也可能不是站楚,第3章有詳解)。這是在說搏嗡,盡頭在于anchorPoint在視圖應(yīng)用變形的位置窿春;如果變形包括一個將圖層移到屏幕某處的位移拉一,盡頭將在它位移前的位置。

當(dāng)你改變圖層的position旧乞,你也改變了盡頭位置蔚润。當(dāng)你處理3D時應(yīng)當(dāng)牢記這一點。如果你想改變某圖層的m34值來使其看起來像是3D的尺栖,你應(yīng)該將它放在屏幕中心然后使用位移(而不是改變它的position)來移至最終的位置嫡纠,這樣它才可以和屏幕上的其它物體有共同的盡頭。

sublayerTransform屬性
如果存在多個有3D變形的視圖或圖層延赌,對它們每個獨自應(yīng)用相同的m34值來確保它們在變形前共有一個在屏幕中心的positon除盏。如果你自定義一個常量或函數(shù)來創(chuàng)建并指定它們位置,這一切會相當(dāng)簡單皮胡,但同樣有限制(例如痴颊,你不能通過Interface Builder排列視圖)。這是一個更好的方式屡贺。

CALayer有另一個更變形屬性叫做sublayerTransform蠢棱。這也是一個CATransform3D,但并不是對其應(yīng)用的圖層變形甩栈,它只影響其子圖層泻仙。這意味著你可以對一個容器圖層應(yīng)用透視變形來影響它的子圖層,它的所有子圖層都會自動繼承透視效果量没。

只需要在一個你認(rèn)為方便的地方設(shè)置透視變形玉转,但它還有一個顯著優(yōu)點:盡頭被設(shè)為窗口圖層的中心,而不是每個子圖層獨立設(shè)置殴蹄。這意味著你可以隨意使用圖層的positionframe來移動子圖層而不是先把它們移到屏幕中樣再通過變形移動來保持它們的盡頭不變究抓。

讓我們用一個例子演示這一點。我們會在Interface Builder中并排放置兩個視圖(如圖5.12)袭灯。然后通過設(shè)置它們?nèi)萜饕晥D的透視變形刺下,我們可以給它們應(yīng)用相同的透視和盡頭』看表5.6中相對于圖5.13結(jié)果的代碼橘茉。

圖5.12 在容器視圖中并排的兩個視圖
表5.6 使用sublayerTransform
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var containerView: UIView!

    @IBOutlet weak var layerView1: UIImageView!
    @IBOutlet weak var layerView2: UIImageView!
    override func viewDidLoad() {
        super.viewDidLoad()

        // 給容器應(yīng)用透視變形
        var perspective = CATransform3DIdentity
        perspective.m34 = -1.0 / 500.0
        self.containerView.layer.sublayerTransform = perspective

        // layerView1繞Y軸45度旋轉(zhuǎn)
        let transform1 = CATransform3DMakeRotation(CGFloat(M_PI_4), 0, 1, 0)
        self.layerView1.layer.transform = transform1

        // layerView2繞Y軸45度旋轉(zhuǎn)
        let transform2 = CATransform3DMakeRotation(CGFloat(-M_PI_4), 0, 1, 0)
        self.layerView2.layer.transform = transform2
    }

}
同樣透視的兩個獨立的變形視圖

背面

既然我們可以在三維空間上旋轉(zhuǎn)圖層,我們也可以從后面觀察它們姨丈。如果我們在表5.4中把角度改為M_PI(180度)而非現(xiàn)在的M_PI_4(45度)畅卓,我們將把圖形旋轉(zhuǎn)一個半圓,這樣它就會背對鏡頭蟋恬。

圖層從背面看起來什么樣翁潘?如圖5.14所示。

圖5.14 我們視圖的背面歼争,顯示一個雪人圖像的鏡像

正如你所見唐础,圖層是雙面的箱歧;前后是成鏡面對稱的。盡管這并不是一個必須想要的特性一膨。如果你的圖層有文字或控件,看見它們的鏡像會讓用戶感到疑惑洒沦。它也有潛在的浪費:想像由圖層中不透明的立方體組成的實體豹绪,為什么我們要浪費GPU周期來繪制我們永遠(yuǎn)不會看見的背面?

CALayer有一個屬性叫doubleSided用來控制圖層的背面是否被繪制申眼。doubleSided是一個BOOL值且默認(rèn)為YES瞒津。如果你將其設(shè)為NO,那么當(dāng)圖層背向鏡頭括尸,它壓根不會被繪制巷蚪。

圖層扁平化

如果我們把一個對一個圖層施加變形,而這個圖層包含一個被向相反方向變形的圖層濒翻,會發(fā)生什么屁柏?疑惑驪?看圖5.15有送。

圖5.15 內(nèi)嵌圖層施加相反變形

注意看內(nèi)圖層的負(fù)45度旋轉(zhuǎn)是如何抵消外圖層的45度旋轉(zhuǎn)的淌喻,這導(dǎo)致內(nèi)圖層最終還是指向正上。

邏輯上來說雀摘,如果內(nèi)圖層和外圖層有相反的變形(在這里裸删,是繞Z軸的旋轉(zhuǎn)),我們可能以為這兩個變形會相互抵消阵赠。

讓我們用實踐證實這點涯塔。表5.7展示了相關(guān)代碼,圖5.16展示了結(jié)果清蚀。[1]

表5.7 繞Z軸反方向旋轉(zhuǎn)
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var outerView: UIView!
    @IBOutlet weak var innerView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // 外圖層45度旋轉(zhuǎn)
        let outer = CATransform3DMakeRotation(CGFloat(M_PI_4), 0, 0, 1)
        self.outerView.layer.transform = outer

        // 內(nèi)圖層-45度旋轉(zhuǎn)
        let inner = CATransform3DMakeRotation(CGFloat(-M_PI_4), 0, 0, 1)
        self.innerView.layer.transform = inner
    }
}
圖5.16 旋轉(zhuǎn)視圖符合圖5.15中的預(yù)期

然而譯者實現(xiàn)效果如下:


反方向旋轉(zhuǎn)的外視圖和內(nèi)視圖

這看起來像是按照預(yù)期的工作的∝拜現(xiàn)在讓我們試試3D的表現(xiàn)。我們將修改代碼讓它們繞Y軸旋轉(zhuǎn)而非Z軸轧铁,同時加上透視來看清楚發(fā)生了什么每聪。我們不能用表5.6中的sublayerTransform方法,因為我們內(nèi)圖層不是容器的直接子圖層齿风,所以我們應(yīng)該為它們分別添加透視(如表5.8)药薯。

表5.8 繞Y軸反方向旋轉(zhuǎn)
override func viewDidLoad() {
    super.viewDidLoad()

    // 外圖層45度旋轉(zhuǎn)
    var outer = CATransform3DIdentity
    outer.m34 = -1.0 / 500.0
    outer = CATransform3DRotate(outer, CGFloat(M_PI_4), 0, 1, 0)
    self.outerView.layer.transform = outer

    // 內(nèi)圖層-45度旋轉(zhuǎn)
    var inner = CATransform3DIdentity
    inner.m34 = -1.0 / 500.0
    inner = CATransform3DRotate(inner, CGFloat(-M_PI_4), 0, 1, 0)
    self.innerView.layer.transform = inner
}

我們預(yù)期會看到如圖5.17的結(jié)果。

圖5.17 預(yù)期的繞Y軸旋轉(zhuǎn)的結(jié)果

但是并不是如我們預(yù)期救斑,我們看見如圖5.18的東西童本。發(fā)生了什么?我們的內(nèi)圖層仍然向左傾斜脸候,也扭曲了穷娱;它被預(yù)期在方塊面上鞍竽琛!

事實證明雖然Core Animation圖層存在于3D空間泵额,它們并不存在于同樣的3D空間中配深。每個圖層的3D場景是扁平化的。當(dāng)你從正面看圖層時嫁盲,你會看見由其子圖層創(chuàng)建的3D場景的假象篓叶,當(dāng)你傾斜圖層時,你會意識到3D場景只是畫在圖層表面的羞秤。

圖5.18 實際上繞Y軸反方向旋轉(zhuǎn)的結(jié)果

譯者自己代碼的效果:


繞Y軸反方向旋轉(zhuǎn)

這類似于你在玩3D游戲時傾斜屏幕缸托。你可能在你游戲中看見了一面墻,但傾斜屏幕并不能讓你四下觀察這面墻瘾蛋。屏幕上的場景并不隨你看的角度不同而改變俐镐;圖層內(nèi)容也是同樣的。

這使得用Core Animation制作復(fù)雜3D場景十分困難哺哼。你不能用圖層樹構(gòu)造層次化的3D結(jié)構(gòu)佩抹,同一場景的任何3D表面必須與同一圖層為兄弟,這是因為每個父圖層都會扁平化其子圖層幸斥。

至少匹摇,在你使用正常的CALayer實例時這是正確的。CALayer有一個叫CATransformLayer的子類被設(shè)計來解決這一問題甲葬。這將在第6章“特定圖層”中講解廊勃。

實體

既然你已經(jīng)了解了3D空間中放置圖層的基礎(chǔ),讓我們嘗試構(gòu)造一個3D實體(好吧经窖,從技術(shù)上講是一個中空物體坡垫,但它看起來是實心的)。我們將用六個獨立的視圖作為表面來構(gòu)造一個立方體画侣。

為了實現(xiàn)我們例子的目的冰悠,立方體在Interface Builder會如圖5.19排列。我們可以用代碼創(chuàng)建表面配乱,但使用Interface Builder的優(yōu)勢在于我們可以輕松地為每個表面添加溉卓、排列子視圖。記住這些表面是普通的用戶界面元素搬泥,它們可以包含其它視圖和控件桑寨。它們是完整的且會互相影響,所以在我們把它們折疊成一個立方體后還會存在忿檩。

由于使用xib混編stroyboard較為麻煩尉尾,譯者使用代碼創(chuàng)建表面。

表5.9 創(chuàng)建立方體
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var containerView: UIView!
    var faces: Array<UIView> = []

    func getRandomColor() -> UIColor {
        // 創(chuàng)建隨機顏色
        let red: CGFloat = CGFloat(arc4random() % 255) / 255.0
        let green: CGFloat = CGFloat(arc4random() % 255) / 255.0
        let blue: CGFloat = CGFloat(arc4random() % 255) / 255.0
        return UIColor(red: red, green: green, blue: blue, alpha: 1)
    }

    func createFace(number: NSInteger) -> UIView {
        // 創(chuàng)建表面視圖
        let face = UIView(frame: CGRectMake(0, 0, 200, 200))
        face.backgroundColor = UIColor.whiteColor()

        // 創(chuàng)建標(biāo)簽
        let label = UILabel(frame: CGRectZero)
        label.text = number.description
        label.textColor = getRandomColor()
        label.font = UIFont.systemFontOfSize(40)

        // 將標(biāo)簽放在中間
        label.sizeToFit()   // 用于將標(biāo)簽的bounds大小設(shè)為恰好符合
        let faceSize = face.bounds.size
        label.center = CGPointMake(faceSize.width / 2.0, faceSize.height / 2.0)

        // 將標(biāo)簽加入表面視圖
        face.addSubview(label)

        return face
    }

    func addFace(index: NSInteger, withTransform transform: CATransform3D) {
        // 獲得表面視圖并把它加入容器中
        let face = self.faces[index]
        self.containerView.addSubview(face)

        // 將表面視圖在容器中居中
        let containerSize = self.containerView.bounds.size
        face.center = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0)

        // 應(yīng)用變形
        face.layer.transform = transform
    }

    override func viewDidLayoutSubviews() {
        // 創(chuàng)建六個表面
        for (var i = 1; i <= 6; i++) {
            let face = createFace(i)
            faces.append(face)
        }

        // 設(shè)置容器子視圖視圖形變
        var perspective = CATransform3DIdentity
        perspective.m34 = -1.0 / 500.0
        self.containerView.layer.sublayerTransform = perspective

        // 添加立方體的表面1
        var transform = CATransform3DMakeTranslation(0, 0, 100)
        self.addFace(0, withTransform: transform)

        // 添加立方體的表面2
        transform = CATransform3DMakeTranslation(100, 0, 0)
        transform = CATransform3DRotate(transform, CGFloat(M_PI_2), 0, 1, 0)
        self.addFace(1, withTransform: transform)

        // 添加立方體的表面3
        transform = CATransform3DMakeTranslation(0, -100, 0)
        transform = CATransform3DRotate(transform, CGFloat(M_PI_2), 1, 0, 0)
        self.addFace(2, withTransform: transform)

        // 添加立方體的表面4
        transform = CATransform3DMakeTranslation(0, 100, 0)
        transform = CATransform3DRotate(transform, CGFloat(-M_PI_2), 1, 0, 0)
        self.addFace(3, withTransform: transform)

        // 添加立方體的表面5
        transform = CATransform3DMakeTranslation(-100, 0, 0)
        transform = CATransform3DRotate(transform, CGFloat(-M_PI_2), 0, 1, 0)
        self.addFace(4, withTransform: transform)

        // 添加立方體的表面6
        transform = CATransform3DMakeTranslation(0, 0, -100)
        transform = CATransform3DRotate(transform, CGFloat(M_PI), 0, 1, 0)
        self.addFace(5, withTransform: transform)
        

    }
}
圖5.20 朝前顯示的立方體

我們的立方體從這個角度看并不顯眼燥透;它看起來就像是正方形沙咏。為了正確的表現(xiàn)它辨图,我們需要從一個不一樣的視角看它。

旋轉(zhuǎn)立方體本身可能會很麻煩肢藐,因為我們不得不獨立旋轉(zhuǎn)每一面故河。一個簡單的方法是旋轉(zhuǎn)鏡頭,這個我們可以能過修改容器視圖的sublayerTransform來實現(xiàn)吆豹。

在其應(yīng)用于containterView前忧勿,加上下面幾行來旋轉(zhuǎn)perspective變形矩陣。

perspective = CATransform3DRotate(perspective, CGFloat(-M_PI_4), 1, 0, 0)
perspective = CATransform3DRotate(perspective, CGFloat(-M_PI_4), 0, 1, 0)

它有繞Y軸45度旋轉(zhuǎn)鏡頭的效果(或者說相對于鏡頭旋轉(zhuǎn)整個屏幕瞻讽,這取決于你怎么想),然后再一個繞X軸旋轉(zhuǎn)45度熏挎。我們現(xiàn)在從一個角看立方體速勇,我們可以看到它實際的樣子(如圖5.21)。

圖5.21 從一個角看立方體

光影

它現(xiàn)在看起來是像一個立方體坎拐,但很難辨別不同面的連接烦磁。Core Animation可以用3D顯示圖層,但沒有的概念哼勇。如果你想讓你的立方體看起來更真實都伪,你需要自己加上陰影效果。你可以通過調(diào)整不同視圖的背影色來實現(xiàn)或?qū)λ鼈兲崆笆褂糜泄饩€效果的圖像积担。

如果你需要創(chuàng)建動態(tài)的光影效果陨晶,你可以在每個圖層上覆蓋一個半透明的黑色陰影圖層,然后根據(jù)視圖方向改變透明度帝璧。為了計算陰影圖層的透明度先誉,你需要獲得每一面的正常向量(與該面垂直的向量)然后計算它們與想象中光線來源的的向量積。向量積可以告訴你光線源和這個圖層的角度的烁,我表明了它應(yīng)當(dāng)被照亮的程度褐耳。

這個方法的一種實現(xiàn)是表5.10。我們用了GLKit框架來做向量計算(你需要在你的項目中引入這一框架)渴庆。每一個面的CATransform3D使用一些指針方法轉(zhuǎn)型為一個GLKMatrix4铃芦,然后這個3*3_旋轉(zhuǎn)矩陣_使用GLKMatrix4GetMatrix3函數(shù)擴展。這個旋轉(zhuǎn)矩陣是用來決定圖層方向的變形的一部分襟雷,我們可以用它來計算正常向量刃滓。

圖5.22展示了結(jié)果。嘗試微調(diào)LIGHT_DIRECTION向量和AMBIENT_LIGHT值來調(diào)整光影效果嗤军。

表5.10 給立方體表面應(yīng)用動態(tài)光影效果
import UIKit
import GLKit

let LIGHT_DIRECTON = GLKVector3Make(0, 1, -0.5)
let AMBIENT_LIGHT: CGFloat = 0.5

class ViewController: UIViewController {

    @IBOutlet weak var containerView: UIView!
    var faces: Array<UIView> = []

    func getRandomColor() -> UIColor {
        // 創(chuàng)建隨機顏色
        let red: CGFloat = CGFloat(arc4random() % 255) / 255.0
        let green: CGFloat = CGFloat(arc4random() % 255) / 255.0
        let blue: CGFloat = CGFloat(arc4random() % 255) / 255.0
        return UIColor(red: red, green: green, blue: blue, alpha: 1)
    }

    func createFace(number: NSInteger) -> UIView {
        // 創(chuàng)建表面視圖
        let face = UIView(frame: CGRectMake(0, 0, 200, 200))
        face.backgroundColor = UIColor.whiteColor()

        // 創(chuàng)建標(biāo)簽
        let label = UILabel(frame: CGRectZero)
        label.text = number.description
        label.textColor = getRandomColor()
        label.font = UIFont.systemFontOfSize(40)

        // 將標(biāo)簽放在中間
        label.sizeToFit()   // 用于將標(biāo)簽的bounds大小設(shè)為恰好符合
        let faceSize = face.bounds.size
        label.center = CGPointMake(faceSize.width / 2.0, faceSize.height / 2.0)

        // 將標(biāo)簽加入表面視圖
        face.addSubview(label)

        return face
    }

    func applyLightingToFace(face: CALayer) {
        // 增加光線層
        let layer = CALayer()
        layer.frame = face.bounds
        face.addSublayer(layer)

        // 轉(zhuǎn)換face的變形的矩陣
        // GLKMatrix4有和CATransform3D一樣的結(jié)構(gòu)
        let transform = face.transform
        let matrix4: GLKMatrix4 = GLKMatrix4Make(Float(transform.m11), Float(transform.m12), Float(transform.m13), Float(transform.m14), Float(transform.m21), Float(transform.m22), Float(transform.m23), Float(transform.m24), Float(transform.m31), Float(transform.m32), Float(transform.m33), Float(transform.m34), Float(transform.m41), Float(transform.m42), Float(transform.m43), Float(transform.m44))
        let matrix3: GLKMatrix3 = GLKMatrix4GetMatrix3(matrix4)

        // 獲得face的正常向量
        var normal = GLKVector3Make(0, 0, 1)
        normal = GLKMatrix3MultiplyVector3(matrix3, normal)
        normal = GLKVector3Normalize(normal)

        // 獲得與光向量的點積
        let light = GLKVector3Normalize(LIGHT_DIRECTON)
        let dotProduct = GLKVector3DotProduct(light, normal)

        // 設(shè)置光線層的透明度
        let shadow = 1 + CGFloat(dotProduct) - AMBIENT_LIGHT
        let color = UIColor(white: 0, alpha: shadow)
        layer.backgroundColor = color.CGColor

    }

    func addFace(index: NSInteger, withTransform transform: CATransform3D) {
        // 獲得表面視圖并把它加入容器中
        let face = self.faces[index]
        self.containerView.addSubview(face)

        // 將表面視圖在容器中居中
        let containerSize = self.containerView.bounds.size
        face.center = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0)

        // 應(yīng)用變形
        face.layer.transform = transform

        // 應(yīng)用光影
        self.applyLightingToFace(face.layer)
    }

    override func viewDidLayoutSubviews() {
        // 創(chuàng)建六個表面
        for (var i = 1; i <= 6; i++) {
            let face = createFace(i)
            faces.append(face)
        }

        // 設(shè)置容器子視圖視圖形變
        var perspective = CATransform3DIdentity
        perspective.m34 = -1.0 / 500.0
        perspective = CATransform3DRotate(perspective, CGFloat(-M_PI_4), 1, 0, 0)
        perspective = CATransform3DRotate(perspective, CGFloat(-M_PI_4), 0, 1, 0)
        self.containerView.layer.sublayerTransform = perspective

        // 添加立方體的表面1
        var transform = CATransform3DMakeTranslation(0, 0, 100)
        self.addFace(0, withTransform: transform)

        // 添加立方體的表面2
        transform = CATransform3DMakeTranslation(100, 0, 0)
        transform = CATransform3DRotate(transform, CGFloat(M_PI_2), 0, 1, 0)
        self.addFace(1, withTransform: transform)

        // 添加立方體的表面3
        transform = CATransform3DMakeTranslation(0, -100, 0)
        transform = CATransform3DRotate(transform, CGFloat(M_PI_2), 1, 0, 0)
        self.addFace(2, withTransform: transform)

        // 添加立方體的表面4
        transform = CATransform3DMakeTranslation(0, 100, 0)
        transform = CATransform3DRotate(transform, CGFloat(-M_PI_2), 1, 0, 0)
        self.addFace(3, withTransform: transform)

        // 添加立方體的表面5
        transform = CATransform3DMakeTranslation(-100, 0, 0)
        transform = CATransform3DRotate(transform, CGFloat(-M_PI_2), 0, 1, 0)
        self.addFace(4, withTransform: transform)

        // 添加立方體的表面6
        transform = CATransform3DMakeTranslation(0, 0, -100)
        transform = CATransform3DRotate(transform, CGFloat(M_PI), 0, 1, 0)
        self.addFace(5, withTransform: transform)
        

    }
}
圖5.22 有動態(tài)計算光影的立方體

觸摸事件

由于譯者前期并沒有加上按鈕注盈,因此更新了一下新的代碼如下:

import UIKit
import GLKit

let LIGHT_DIRECTON = GLKVector3Make(0, 1, -0.5)
let AMBIENT_LIGHT: CGFloat = 0.5

class ViewController: UIViewController {

    @IBOutlet weak var containerView: UIView!
    var faces: Array<UIView> = []

    func getRandomColor() -> UIColor {
        // 創(chuàng)建隨機顏色
        let red: CGFloat = CGFloat(arc4random() % 255) / 255.0
        let green: CGFloat = CGFloat(arc4random() % 255) / 255.0
        let blue: CGFloat = CGFloat(arc4random() % 255) / 255.0
        return UIColor(red: red, green: green, blue: blue, alpha: 1)
    }

    func createFace(number: NSInteger) -> UIView {
        // 創(chuàng)建表面視圖
        let face = UIView(frame: CGRectMake(0, 0, 200, 200))
        let faceSize = face.bounds.size
        face.backgroundColor = UIColor.whiteColor()

        // 創(chuàng)建按鈕
        let button = UIButton(frame: CGRectMake(0, 0, 100, 100))
        button.layer.cornerRadius = 25
        button.backgroundColor = UIColor.redColor()
        button.alpha = 0.1

        // 將按鈕移到中間
        button.center = CGPointMake(faceSize.width / 2.0, faceSize.height / 2.0)

        // 將按鈕加入表面
        face.addSubview(button)

        // 創(chuàng)建標(biāo)簽
        let label = UILabel(frame: CGRectZero)
        label.text = number.description
        label.textColor = getRandomColor()
        label.font = UIFont.systemFontOfSize(40)

        // 將標(biāo)簽放在中間
        label.sizeToFit()   // 用于將標(biāo)簽的bounds大小設(shè)為恰好符合
        label.center = CGPointMake(faceSize.width / 2.0, faceSize.height / 2.0)

        // 將標(biāo)簽加入表面視圖
        face.addSubview(label)

        return face
    }

    func applyLightingToFace(face: CALayer) {
        // 增加光線層
        let layer = CALayer()
        layer.frame = face.bounds
        face.addSublayer(layer)

        // 轉(zhuǎn)換face的變形的矩陣
        // GLKMatrix4有和CATransform3D一樣的結(jié)構(gòu)
        let transform = face.transform
        let matrix4: GLKMatrix4 = GLKMatrix4Make(Float(transform.m11), Float(transform.m12), Float(transform.m13), Float(transform.m14), Float(transform.m21), Float(transform.m22), Float(transform.m23), Float(transform.m24), Float(transform.m31), Float(transform.m32), Float(transform.m33), Float(transform.m34), Float(transform.m41), Float(transform.m42), Float(transform.m43), Float(transform.m44))
        let matrix3: GLKMatrix3 = GLKMatrix4GetMatrix3(matrix4)

        // 獲得face的正常向量
        var normal = GLKVector3Make(0, 0, 1)
        normal = GLKMatrix3MultiplyVector3(matrix3, normal)
        normal = GLKVector3Normalize(normal)

        // 獲得與光向量的點積
        let light = GLKVector3Normalize(LIGHT_DIRECTON)
        let dotProduct = GLKVector3DotProduct(light, normal)

        // 設(shè)置光線層的透明度
        let shadow = 1 + CGFloat(dotProduct) - AMBIENT_LIGHT
        let color = UIColor(white: 0, alpha: shadow)
        layer.backgroundColor = color.CGColor

    }

    func addFace(index: NSInteger, withTransform transform: CATransform3D) {
        // 獲得表面視圖并把它加入容器中
        let face = self.faces[index]
        self.containerView.addSubview(face)

        // 將表面視圖在容器中居中
        let containerSize = self.containerView.bounds.size
        face.center = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0)

        // 應(yīng)用變形
        face.layer.transform = transform

        // 應(yīng)用光影
        self.applyLightingToFace(face.layer)
    }

    override func viewDidLayoutSubviews() {
        // 創(chuàng)建六個表面
        for (var i = 1; i <= 6; i++) {
            let face = createFace(i)
            faces.append(face)
        }

        // 設(shè)置容器子視圖視圖形變
        var perspective = CATransform3DIdentity
        perspective.m34 = -1.0 / 500.0
        perspective = CATransform3DRotate(perspective, CGFloat(-M_PI_4), 1, 0, 0)
        perspective = CATransform3DRotate(perspective, CGFloat(-M_PI_4), 0, 1, 0)
        self.containerView.layer.sublayerTransform = perspective

        // 添加立方體的表面1
        var transform = CATransform3DMakeTranslation(0, 0, 100)
        self.addFace(0, withTransform: transform)

        // 添加立方體的表面2
        transform = CATransform3DMakeTranslation(100, 0, 0)
        transform = CATransform3DRotate(transform, CGFloat(M_PI_2), 0, 1, 0)
        self.addFace(1, withTransform: transform)

        // 添加立方體的表面3
        transform = CATransform3DMakeTranslation(0, -100, 0)
        transform = CATransform3DRotate(transform, CGFloat(M_PI_2), 1, 0, 0)
        self.addFace(2, withTransform: transform)

        // 添加立方體的表面4
        transform = CATransform3DMakeTranslation(0, 100, 0)
        transform = CATransform3DRotate(transform, CGFloat(-M_PI_2), 1, 0, 0)
        self.addFace(3, withTransform: transform)

        // 添加立方體的表面5
        transform = CATransform3DMakeTranslation(-100, 0, 0)
        transform = CATransform3DRotate(transform, CGFloat(-M_PI_2), 0, 1, 0)
        self.addFace(4, withTransform: transform)

        // 添加立方體的表面6
        transform = CATransform3DMakeTranslation(0, 0, -100)
        transform = CATransform3DRotate(transform, CGFloat(M_PI), 0, 1, 0)
        self.addFace(5, withTransform: transform)
        

    }
}

原著繼續(xù)如下:

你可能注意到雖然我們可以看見第三個面上的按鈕,但按下去后沒有用叙赚。為什么呢老客?

這并不是因為iOS不能正確的傳遞觸摸事件到3D中的按鈕位置僚饭;它實際上是有這個能力的。這個問題是視圖層次胧砰。正如我們在第3章中簡單提及的鳍鸵,觸摸事件根據(jù)它們父視圖的視圖順序傳遞,而不是它們在3D空間的Z坐標(biāo)尉间。當(dāng)我們給立方體增加面視圖時偿乖,我們是以數(shù)字順序添加的,所以面4哲嘲、5贪薪、6會在面3的視圖/圖層順序前(畫家理論)。

即使我們看不見面4眠副、5画切、6(因為它們被面1、2囱怕、3擋住了)霍弹,iOS仍給予它們在觸摸事件中的第一響應(yīng)權(quán)。當(dāng)我們嘗試觸摸面3上的按鈕的時候娃弓,面5或面6(取決于我們按哪個)攔截了相應(yīng)的觸摸事件典格,就像我們在一個普通的2D布局中將它們放到了前面一樣。

你可能認(rèn)為將doubleSided設(shè)為NO可能有所幫助台丛,因為它會將遠(yuǎn)的面視圖渲染不見耍缴,但不幸的是這并不起作用;由于背對鏡頭而被隱藏的視圖仍會攔截觸摸事件(并不像用hidden屬性隱蔽或設(shè)置透明度為0的視圖一樣)齐佳,所以禁用雙面渲染并不能解決這一問題(盡管它可能因為性能原因被采用)私恬。

而解決方法也有很多:我們可以將除了面3 外的所有面視圖的userInteractionEnabled設(shè)為NO,這樣它們不再接受觸摸炼吴”久或者我們可以簡單的在程序中最后添加我們的面3。無論何種方式硅蹦,我們都可以點擊這個按鈕(如圖5.23)荣德。最終代碼如下:

import UIKit
import GLKit

let LIGHT_DIRECTON = GLKVector3Make(0, 1, -0.5)
let AMBIENT_LIGHT: CGFloat = 0.5

class ViewController: UIViewController {

    @IBOutlet weak var containerView: UIView!
    var faces: Array<UIView> = []

    func getRandomColor() -> UIColor {
        // 創(chuàng)建隨機顏色
        let red: CGFloat = CGFloat(arc4random() % 255) / 255.0
        let green: CGFloat = CGFloat(arc4random() % 255) / 255.0
        let blue: CGFloat = CGFloat(arc4random() % 255) / 255.0
        return UIColor(red: red, green: green, blue: blue, alpha: 1)
    }

    func createFace(number: NSInteger) -> UIView {
        // 創(chuàng)建表面視圖
        let face = UIView(frame: CGRectMake(0, 0, 200, 200))
        let faceSize = face.bounds.size
        face.backgroundColor = UIColor.whiteColor()

        // 創(chuàng)建按鈕
        let button = UIButton(frame: CGRectMake(0, 0, 100, 100))
        button.layer.cornerRadius = 25
        button.backgroundColor = UIColor.redColor()
        button.alpha = 0.1
        button.tag = number
        button.addTarget(self, action: "touchDown:", forControlEvents: UIControlEvents.TouchDown)

        // 將按鈕移到中間
        button.center = CGPointMake(faceSize.width / 2.0, faceSize.height / 2.0)

        // 將按鈕加入表面
        face.addSubview(button)

        // 創(chuàng)建標(biāo)簽
        let label = UILabel(frame: CGRectZero)
        label.text = number.description
        label.textColor = getRandomColor()
        label.font = UIFont.systemFontOfSize(40)

        // 將標(biāo)簽放在中間
        label.sizeToFit()   // 用于將標(biāo)簽的bounds大小設(shè)為恰好符合
        label.center = CGPointMake(faceSize.width / 2.0, faceSize.height / 2.0)

        // 將標(biāo)簽加入表面視圖
        face.addSubview(label)

        return face
    }

    func touchDown(sender: UIButton!) {
        if (sender.tag == 3) {
            sender.alpha = 1
        }
    }

    func applyLightingToFace(face: CALayer) {
        // 增加光線層
        let layer = CALayer()
        layer.frame = face.bounds
        face.addSublayer(layer)

        // 轉(zhuǎn)換face的變形的矩陣
        // GLKMatrix4有和CATransform3D一樣的結(jié)構(gòu)
        let transform = face.transform
        let matrix4: GLKMatrix4 = GLKMatrix4Make(Float(transform.m11), Float(transform.m12), Float(transform.m13), Float(transform.m14), Float(transform.m21), Float(transform.m22), Float(transform.m23), Float(transform.m24), Float(transform.m31), Float(transform.m32), Float(transform.m33), Float(transform.m34), Float(transform.m41), Float(transform.m42), Float(transform.m43), Float(transform.m44))
        let matrix3: GLKMatrix3 = GLKMatrix4GetMatrix3(matrix4)

        // 獲得face的正常向量
        var normal = GLKVector3Make(0, 0, 1)
        normal = GLKMatrix3MultiplyVector3(matrix3, normal)
        normal = GLKVector3Normalize(normal)

        // 獲得與光向量的點積
        let light = GLKVector3Normalize(LIGHT_DIRECTON)
        let dotProduct = GLKVector3DotProduct(light, normal)

        // 設(shè)置光線層的透明度
        let shadow = 1 + CGFloat(dotProduct) - AMBIENT_LIGHT
        let color = UIColor(white: 0, alpha: shadow)
        layer.backgroundColor = color.CGColor

    }

    func addFace(index: NSInteger, withTransform transform: CATransform3D) {
        // 獲得表面視圖并把它加入容器中
        let face = self.faces[index]
        self.containerView.addSubview(face)

        // 將表面視圖在容器中居中
        let containerSize = self.containerView.bounds.size
        face.center = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0)

        // 應(yīng)用變形
        face.layer.transform = transform

        // 應(yīng)用光影
        self.applyLightingToFace(face.layer)
    }

    override func viewDidLayoutSubviews() {
        // 創(chuàng)建六個表面
        for (var i = 1; i <= 6; i++) {
            let face = createFace(i)
            faces.append(face)
        }

        // 設(shè)置容器子視圖視圖形變
        var perspective = CATransform3DIdentity
        perspective.m34 = -1.0 / 500.0
        perspective = CATransform3DRotate(perspective, CGFloat(-M_PI_4), 1, 0, 0)
        perspective = CATransform3DRotate(perspective, CGFloat(-M_PI_4), 0, 1, 0)
        self.containerView.layer.sublayerTransform = perspective

        // 添加立方體的表面1
        var transform = CATransform3DMakeTranslation(0, 0, 100)
        self.addFace(0, withTransform: transform)

        // 添加立方體的表面2
        transform = CATransform3DMakeTranslation(100, 0, 0)
        transform = CATransform3DRotate(transform, CGFloat(M_PI_2), 0, 1, 0)
        self.addFace(1, withTransform: transform)

        // 添加立方體的表面4
        transform = CATransform3DMakeTranslation(0, 100, 0)
        transform = CATransform3DRotate(transform, CGFloat(-M_PI_2), 1, 0, 0)
        self.addFace(3, withTransform: transform)

        // 添加立方體的表面5
        transform = CATransform3DMakeTranslation(-100, 0, 0)
        transform = CATransform3DRotate(transform, CGFloat(-M_PI_2), 0, 1, 0)
        self.addFace(4, withTransform: transform)

        // 添加立方體的表面6
        transform = CATransform3DMakeTranslation(0, 0, -100)
        transform = CATransform3DRotate(transform, CGFloat(M_PI), 0, 1, 0)
        self.addFace(5, withTransform: transform)

        // 添加立方體的表面3
        transform = CATransform3DMakeTranslation(0, -100, 0)
        transform = CATransform3DRotate(transform, CGFloat(M_PI_2), 1, 0, 0)
        self.addFace(2, withTransform: transform)
        

    }
}
未點擊前
圖5.23 現(xiàn)在背面視圖不會阻攔按鈕,我們可以點擊它

總結(jié)

這一章介紹了2D和3D變形童芹。你學(xué)了一些矩陣數(shù)學(xué)涮瞻,以及如何用Core Animation創(chuàng)建3D場景。你看見了背面的圖層的樣子并且了解了你不能在一個平面圖像中四面觀察一個物體假褪。最后署咽,這一章演示了當(dāng)處理觸摸事件時,視圖或圖層在層次中的順序比它們顯示在屏幕上的順序重要。

第6章第講解Core Animation提供的特定的CALayer子類以及它們的不同用途宁否。

內(nèi)圖層圖像

外圖層圖像

  1. ?
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末窒升,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子慕匠,更是在濱河造成了極大的恐慌饱须,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件台谊,死亡現(xiàn)場離奇詭異蓉媳,居然都是意外死亡,警方通過查閱死者的電腦和手機锅铅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門酪呻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人盐须,你說我怎么就攤上這事号杠。” “怎么了丰歌?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長屉凯。 經(jīng)常有香客問我立帖,道長,這世上最難降的妖魔是什么悠砚? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任晓勇,我火速辦了婚禮,結(jié)果婚禮上灌旧,老公的妹妹穿的比我還像新娘绑咱。我一直安慰自己,他們只是感情好枢泰,可當(dāng)我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布描融。 她就那樣靜靜地躺著,像睡著了一般衡蚂。 火紅的嫁衣襯著肌膚如雪窿克。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天毛甲,我揣著相機與錄音年叮,去河邊找鬼。 笑死玻募,一個胖子當(dāng)著我的面吹牛只损,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播七咧,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼跃惫,長吁一口氣:“原來是場噩夢啊……” “哼叮叹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起辈挂,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤衬横,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后终蒂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜂林,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年拇泣,在試婚紗的時候發(fā)現(xiàn)自己被綠了噪叙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡霉翔,死狀恐怖睁蕾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情债朵,我是刑警寧澤子眶,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站序芦,受9級特大地震影響臭杰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谚中,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一渴杆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宪塔,春花似錦磁奖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至南誊,卻和暖如春敢辩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背弟疆。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工戚长, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人怠苔。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓同廉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子迫肖,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容