programming iOS - view drawing (一)

許多UIView的子類曲梗,如一個UIButton或一個UILabel,它們知道怎么繪制自己妓忍。遲早虏两,你也將想要做一些自己的繪制。你可以事先準(zhǔn)備好您的繪圖作為一個圖像文件世剖。您可以用代碼繪制一張圖片在應(yīng)用程序運行中定罢。您可以在`UIView`子類,例如一個UIImageVie或一個UIButton中顯示在你的圖像旁瘫。一個純粹的UIView有關(guān)于繪畫的一切祖凫,繪圖很大程度上取決于你;你的代碼決定view畫什么,你的界面就是什么酬凳。

Images and Image Views

基本的UIKit的圖像類是UIImage蝙场。UIImage可以讀取磁盤上的文件,因此粱年,如果圖像不需要動態(tài)創(chuàng)建售滤,一種簡單的方式就是在應(yīng)用程序運行前提供一個圖片文件放到程序包資源中。系統(tǒng)知道如何處理標(biāo)準(zhǔn)圖像文件類型,如TIFF完箩,JPEG赐俗,GIF和PNG;當(dāng)圖像文件將被包含在你的應(yīng)用程序包,你應(yīng)該優(yōu)先提供PNG格式的圖片,因為系統(tǒng)對PNG格式有種有特殊的親和力弊知。還可以以其他方式獲取圖像數(shù)據(jù)阻逮,例如通過下載,并轉(zhuǎn)變成一個UIImage秩彤。相反叔扼,你也可以用代碼構(gòu)造一個UIImage并顯示在你的界面或保存到磁盤。

Image Files

可以通過UIImage的初始化init(named:)方法來獲得app bundle中的圖片文件漫雷。這個方法在兩個地方查找圖片:

  • Asset catalog

    我們在asset catalog中查找與name名稱相同的圖片瓜富。該名稱是區(qū)分大小寫的。

  • Top level of app bundle

    我們在app bundle的最上層尋找與name名稱相同的圖片降盹。該名稱是區(qū)分大小寫的与柑,并應(yīng)包括文件擴展名;如果它不包括文件擴展名,則假定.PNG蓄坏。

當(dāng)調(diào)用 init(named:)時价捧,asset catalogapp bundle top level 之前搜索圖片文件。如果有多個asset catalog涡戳,它們都會進(jìn)行搜索结蟋,但搜索順序是不確定的并且不能指定搜索順序,所以應(yīng)該避免圖像具有相同的名稱渔彰。

關(guān)于init(named:)的一個好處是圖像數(shù)據(jù)可能會緩存在內(nèi)存中嵌屎,,如果你以后再通過調(diào)用init(named:)訪問為相同的圖像胳岂,緩存中的數(shù)據(jù)可以立即编整√蛳。或者乳丰,你可以使用init(contentsOfFile:)直接從 app bundle 里面讀取圖像數(shù)據(jù)而不緩存,但是需要提供一個路徑字符串作為參數(shù); 你可以通過NSBundle.mainBundle()獲取到app's bundle内贮,然后通過NSBundle的實列方法产园,例如pathForResource:ofType獲取文件的路徑。

獲取app bundle內(nèi)資源的方法夜郁,如 init(named:)pathFor- Resource:ofType:什燕,都會識別實際的資源文件的后綴。在double-resolution 屏幕上,當(dāng)通過文件名在 app bundle 內(nèi)獲取圖片時竞端,具有相同圖片名并且含有@2x的圖片將自動使用屎即,與由此生成的UIImagescale屬性值為2.0。同樣,如果文件具有相同的名稱并且有 @3x技俐,它擴展將會用于iPhone 6 Plus,并且 scale 屬性值為 3.0乘陪。

通過這種方式,您的應(yīng)用程序可以包含一個圖像文件在在不同分辨率下的多個版本雕擂。由于scale屬性啡邑,圖像的高分辨率版本和單分辨率的版本繪制的大小相同。因此井赌,在高分辨率的屏幕上谤逼,代碼不用修改就能工作,但圖片看起來清晰仇穗。

同樣流部,具有相同名稱的由~ipad擴展的文件當(dāng)appipad上運行時會被自動使用 。你可以使用這種方式在universal app中根據(jù)不同的設(shè)備 iPhone 或者 iPad 提供不同的圖像(這對任何在bundle中的資源都是有效的而不只是圖片)

asset catalog的一個好處是你可以忘記所有關(guān)于這些后綴名約定仪缸。asset catalog知道在一個image set內(nèi)何時使用備用圖像贵涵,不是根據(jù)它的名字,而是根據(jù)它在catalog中的位置恰画。把single-,double-,triple-resolution分辨率的圖片放到標(biāo)記著1x,2x,3x的位置上宾茂。對于不同的iPad版的圖像,檢查iPhoneiPad的圖像集的屬性檢查器,不同的圖片位置將會出現(xiàn)在asset catalog中拴还。

Asset catalog也可以區(qū)分圖像在不同size class 下的版本跨晴。在圖像集的屬性檢查器中,使用寬度和高度的彈出菜單來指定要區(qū)分哪個size class片林。如果我們把運行著app的iPhone旋轉(zhuǎn)到橫向端盆,如果有既有的圖片集中的Any heightCompact height圖像都可以使用的話,會優(yōu)先使用Compact height版本的圖像费封。這些功能是實時的在app運行期間;如果應(yīng)用程序從橫向旋轉(zhuǎn)為縱向焕妙,Any height會自動替換掉Compact height的圖片,如果圖片集中的2中圖像都可以使用的話弓摘。

Asset catalog這種神奇的能力是通過trait collectionsUIImageAsset類來實現(xiàn)的焚鹊。當(dāng)圖像通過初始化init(named:)asset catalog中獲取時,它的imageAsset屬性是一個UIImageAsset韧献。在圖像集所有的圖像都可以通過UIImageAsset獲取;每個圖像都有trait collection末患,你可以訪問圖像的imageAsset的屬性和 imageWithTraitCollection:從相同的圖像集中得到特定trait collection的圖片。

一個內(nèi)置的顯示圖像的interface object能自動的識別trait collection;它接收traitCollectionDidChange:消息并相應(yīng)地作出響應(yīng)锤窑。我們可以通過構(gòu)造一個有image屬性的UIView來實現(xiàn)這個功能:

class MyView: UIView {
    var image: UIView!
    override func traitCollectionDidChange(previous: UITraitCollection?) {
    self.setNeedDisplay()
    }
    override func drawRect() {
        if var im = self.image {
            if let asset = self.image.imageAsset {
                let tc = self.traitCollection
                im = asset.imageWithTraitCollection(tc)
            }
            im.drawAtPoint(CGPointZero)
        }
    }
}

此外璧针,你的代碼也可以將圖像合并到一個UIImageAsset - 代碼相當(dāng)于一個asset catalog中的image set,但是并沒有asset catalog 渊啰。因此探橱,你可以實時的創(chuàng)建圖像申屹,或者在app bundle的外部獲取圖像,并且自動配置當(dāng)iPhoneportrait ori‐ entation時使用前一個圖像,當(dāng)iPhonelandscape orientation 時使用另一個圖像:

let tcdisp = UITraitCollection(displayScale:UIScreen.mainScreen().scale)
let tcphone = UITraitCollection(userInterfaceIdiom: .Phone)
let tcreg = UITraitCollection(verticalSizeClass: .Regular)
let tc1 = UITraitCollection(traitsFromCollections: [tcdisp, tcphone, tcreg]) 
let tccom = UITraitCollection(verticalSizeClass: .Compact)
let tc2 = UITraitCollection(traitsFromCollections: [tcdisp, tcphone, tccom]) 
let moods = UIImageAsset() 
let frowney = UIImage(named:"frowney")! 
let smiley = UIImage(named:"smiley")!
moods.registerImage(frowney, withTraitCollection: tc1)
moods.registerImage(smiley, withTraitCollection: tc2)

之后隧膏,如果把frowney放到用戶界面 - 例如独柑,一個UIImageViewimage屬性 - ,當(dāng)app改變方向時,它將和smiley交替顯示∷街玻可喜的是忌栅,即使是沒有永久引用frowneysmiley曲稼,或UIImageAssetmoods)索绪,這都會自動發(fā)生。原因是贫悄,frowneysmiley由系統(tǒng)緩存(因為調(diào)用init(named:))瑞驱,他們各自保持一個它們自己關(guān)聯(lián)的UIImageAsset的強引用。

通過init(named:inBundle:compatibleWith- TraitCollection:)app bundle或者asset catalog獲取圖像時可以指定一個目標(biāo)trait collection窄坦。bundle參數(shù)經(jīng)常為nil唤反,這表示app‘s main bundle

Image Views

許多內(nèi)置的CocoaCocoa interface objects接受一個UIImage做為自己的一部分去繪制;例如鸭津,一個UIButton能夠顯示圖像彤侍,UINavigationBar或者UITabBar可以有一個背景圖像。當(dāng)你只是想一個圖像出現(xiàn)在你的界面上逆趋,你可能把它交給一個圖像視圖 - UIImageView - 它的作用就是顯示圖像盏阶。

nib編輯器在這方面提供了一些快捷方式:一個interface object的屬性檢查器中名斟,會有一個彈出菜單魄眉,其中包含項目中的所有圖像,這些圖像也會在Media library中顯示(Command-Option- Control-4)岩梳。Media library的圖像往往可以拖動到畫布上的interface object上顯示,如果只是拖動Media library里的圖像到空白的view脾歇,它被轉(zhuǎn)換成顯示該圖像的一個UIImageView藕各。

一個UIImageView實際上可以有兩幅圖片,其中一個分配給自己的image 屬性和另一個分配給其highlightedImage屬性;UIImageViewhighlighted屬性值決定在任何給定的時間顯示哪幅圖。和button一樣,UIImageView只會在用戶點擊它的時候高亮顯示绢慢。不過胰舆,在某些特定的情況下UIImageView會對它周圍的高亮作出回應(yīng);例如在table view cell中,當(dāng)cell高亮的時候倦零,ImageView會顯示它的highlighted image

UIImageView也是UIView诞帐,因此它可以有圖像屬性外也可以有背景顏色停蕉,它可以有一個alpha(透明度)值慧起,等等。圖像可能有透明的區(qū)域灿意,也可以是任何形狀缤剧,UIImageView都會顯示它;沒有背景顏色的UIImageView不會顯示在界面中汗销,除非它的image屬性不為空弛针,圖像僅僅只是顯示在界面中,用戶不會意識到它是在一個矩形框中付材。即沒有背景顏色,image屬性也沒有值的UIImageView是不可見的富寿,所以你可以以一個空的UIImageView開始,并隨后在代碼中指定image屬性。你可以指定一個新的圖像來代替舊的变勇,或者設(shè)置image屬性的值為nil來移除UIImageView的圖像。

UIImageView如何繪制自己的圖像取決于其contentMode屬性(UIViewContentMode)的設(shè)置链患。 (該contentMode屬性是從UIView繼承的)例如,.ScaleToFill意味著圖像的寬度和高度都設(shè)置為視圖的寬度和高度贸毕,從而完全填充視圖即使這會改變圖像的長寬比;.Center居中繪制圖像而且不改變t圖像的大小。理解contentMode最好的方式是在nib中為UIImageView分配一個小圖像击蹲,然后在屬性檢查器中,切換不同的mode类咧,觀察圖像如何繪制自身。

你還應(yīng)該注意UIImageViewclipsToBounds屬性;如果是false值戳,即使它的圖像比image view更大,或者圖像沒有被contentMode按比例縮小赴捞,那么圖像可以延伸超出image view本身全部顯示。

當(dāng)在代碼中創(chuàng)建UIImageView,你可以充分利用便利構(gòu)造器的優(yōu)勢然评,init(image:) (或者 init(image:highlightedImage:)).默認(rèn)的contentMode.ScaleToFill,但圖像初始時是不縮放的,而是調(diào)整view自身的大小去匹配圖像亿眠。你仍然可能需要把UIImageView放到它的父視圖上正確位置。在下面這個例子中竟趾,我把火星圖片在應(yīng)用程序界面中心:

let iv = UIImageView(image: UIImage(named: "Mars"))  //asset catalog
mainView.addSubview(iv)
iv.center = iv.superview!.bounds.center
iv.frame.makeIntegralInPlace()

為一個已經(jīng)存在的UIImageView指定圖片時,UIImageView的大小如何改變?nèi)Q于它是否使用autolayout屎飘。如果沒有使用autolayout,或者它的大小被約束完全限定,那么UIImageView的大小不發(fā)生變化押桃。但在autolayout下,除非其他約束阻止波丰,新的圖像的尺寸會變?yōu)?code>image view的intrinsicContentSize,因此imageview將變?yōu)閳D像的大小。

image view會自動從圖像的alignmentRectInsets獲得其alignmentRectInsets先馆。因此,如果你打算使用自動布局來調(diào)整image view對齊到其他對象仿野,你可以將相應(yīng)的alignmentRectInsets設(shè)置到將要顯示的圖像上,那么image view會正確的顯示劣针。要實現(xiàn)這個功能,通過在原始圖像上調(diào)用imageWithAlignmentRectInsets派生出新的圖像辣苏。

從理論上講煌张,你應(yīng)該能夠在Asset catalog中設(shè)置圖像的alignmentRectInsets链嘀。但是當(dāng)寫這篇文章時怀泊,此功能無法正常工作。

Resizable Images

在界面中某些地方可能需要可以調(diào)整大小的圖像;例如,在作為一個滑塊或進(jìn)度條視圖的軌道的自定義圖像必須能夠調(diào)整大小忠藤,以便它可以填充任何長度的空間。還有經(jīng)常需要通過平鋪或拉伸現(xiàn)有圖像填充背景的其他情形瓜贾。

通過在正常的圖像上調(diào)用resizableImageWithCapInsets:resizingMode:方法來創(chuàng)建動態(tài)伸縮的圖像。capInsets:參數(shù)是一個UIEdgeInsets胃夏,其分量代表向內(nèi)到圖像的邊緣的距離(可以理解為內(nèi)邊距)仰禀。在一個比圖像大的context中,可調(diào)整大小的圖像有2種表現(xiàn)方式,這取決于resizingMode:的值(UIImageResizingMode):

  • .Tile


    變化的區(qū)域的內(nèi)部圖片是平鋪(重復(fù));每一條邊是由非變化區(qū)域的相應(yīng)邊緣矩形組成的。相對于變化區(qū)域的四個角落的繪制不變周瞎。

  • .Stretch


    變化區(qū)域的內(nèi)部被拉伸一次以填充;每一條邊是由非變化區(qū)域的相應(yīng)邊緣矩形組成的退盯。相對于變化區(qū)域的四個角落的繪制不變囤攀。

在下面的例子中,假設(shè)self.iv是一個絕對高度和寬度(因此它不會用它的圖像的大小來設(shè)置自己的大序蛳巍)而且contentMode屬性為.ScaleToFill(圖像會有伸縮的行為)的UIImageView。首先,我會說明怎么平鋪整個圖像;注意椒楣,capInsets:的值是UIEdgeInsetsZero

let mars = UIImage(named: "Mars")!
let marsTiled = mars.resizableImageWithCapInsets(UIEdgeInsetsZero, resizingMode: .Tile)
self.iv.image = marsTiled

然后,改變上面代碼種capInsets參數(shù)的值來重新平鋪上面的圖片:

let marsTiled = mars.resizableImageWithCapInsets(
    UIEdgeInsetsMake(
        mars.size.height / 4.0, 
        mars.size.width / 4.0, 
        mars.size.height / 4.0, 
        mars.size.width / 4.0
    ), resizingMode: .Tile)

然后來說明拉伸毛俏,從改變上面代碼的resizingMode開始:

let marsTiled = mars.resizableImageWithCapInsets(
    UIEdgeInsetsMake(
        mars.size.height / 4.0, 
        mars.size.width / 4.0, 
        mars.size.height / 4.0, 
        mars.size.width / 4.0
    ), resizingMode: .Stretch)

效果如下:


一個常見的延伸策略是讓幾乎一半的原始圖像作為cap inset免绿,只留下中心的一兩個像素來填充空白區(qū)域:

let marsTiled = mars.resizableImageWithCapInsets(
    UIEdgeInsetsMake(
        mars.size.height / 2.0 - 1, 
        mars.size.width / 2.0 - 1, 
        mars.size.height / 2.0 -1, 
        mars.size.width / 2.0 - 1
    ), resizingMode: .Stretch)

效果如下


你也應(yīng)該嘗試不同的contentMode設(shè)置迹卢。在上的例子中誊垢,如果該圖像視圖的contentMode.ScaleAspectFill,并且如果圖像視圖的clipsToBoundstrue芋肠,我們會得到一種漸變的效果,因為拉神完成的圖象的頂部和底部已經(jīng)超出image view的邊界而不會被繪制出來。
效果如下:

你可以通過項目的asset catalog而不是代碼來配置一個可調(diào)整大小的圖像。經(jīng)常出現(xiàn)的情況是:一個特定的圖像將在您的應(yīng)用中主要被用來作為一個可調(diào)整大小的圖像彤叉,并且總是具有同樣的capInsetsresizingMode,所以很有必要只配置此圖像一次,而不是重復(fù)寫相同的代碼兔辅。即使圖像在asset catalog中配置為可調(diào)整大小,它也可以做為一個正常的圖片出現(xiàn)在你的界面中, 例如,你可以把它分配給根據(jù)圖像大小調(diào)整自身大小的image view,或者是不會壓縮或者拉伸自己圖像的image view褐澎。

要在asset catalog中配置一個可調(diào)整大小的圖像,首先選擇圖像,然后在Slicing section的屬性檢查器中,更改Slices彈出菜單為Horizontal,Vertical寺枉,或者是Horizontal and Vertical。當(dāng)你執(zhí)行此操作時催式,會有更多的界面出現(xiàn)供你配置參數(shù)。您可以在另外的彈出菜單中配置resizingMode。也可以用數(shù)字,或單擊畫布右下角的Show Slicing生年,這都會在畫布上顯示配置好的圖像桌粉。圖形編輯器是可縮放的霜大,所以你可以放大到你覺得舒服的大小。

這個功能實際上比resizableImageWithCapInsetsresizingMode:更加強大.它可以讓你從平鋪或拉伸區(qū)域分別指定結(jié)束的caps,而剩下的部分將會被切掉。如下圖所示:


在上圖中左上角,右上角士骤,左下角和右下角的暗色區(qū)域?qū)⒃瓨永L制。窄帶將被拉長添忘,頂部中心的小矩形將被拉伸以填充大部分的區(qū)域。但是圖像的其余部分若锁,被紗布幕覆蓋的中央大片區(qū)域搁骑,將被完全省略。結(jié)果如下圖:

Image Rendering Mode

iOS應(yīng)用的用戶界面在某些地方會自動將圖像作為一個透明遮罩(transparency mask)又固,也被稱為template。這意味著圖像的每個像素的透明度(alpha)會起作用口予,而顏色值將被忽略娄周。在屏幕上顯示的圖像是由圖像的透明度值和一個純色(tint color)混合而成。tab bar item的圖片顯示就是這種方式沪停。

圖像被怎樣顯示是通過圖像的只讀renderingMode的屬性控制的;只能通過在圖像上調(diào)用imageWithRenderingMode:來生成一個新的圖像來更改rendering mode值.渲染模式的值(UIImageRenderingMode)為:

  • .Automatic
  • .AlwaysOriginal
  • .AlwaysTemplate

默認(rèn)值是.Automatic煤辨。這意味在某些被限制的上下文中它被用作透明遮罩(transparency mask)外,大部分情況都會被原樣繪制出來木张。

通過設(shè)置renderingMod屬性众辨,你可以強制正常繪制圖像,即使在一個把它當(dāng)作一個透明遮罩(transparency mask)的上下文中舷礼。相反的:你可以強制繪制圖像為透明度遮罩(transparency mask)鹃彻,即使在正常的上下文中。

為了實現(xiàn)這個功能妻献,iOS給每個UIView一個tintColor屬性蛛株,這將對它包含的任何模板圖像進(jìn)行著色。此外育拨,tintColor默認(rèn)是從應(yīng)用程序的最上面window一直貫穿整個視圖層級而繼承的谨履。因此,更改應(yīng)用程序的主windowtintColor可能是你僅有的能對window所做的改變之一;否則熬丧,你的應(yīng)用程序?qū)⒉捎孟到y(tǒng)的藍(lán)色的tint color笋粟。 (如果你使用的是main storyboard,可以在文件檢查器中設(shè)置Global Tint color的顏色析蝴。)可以設(shè)置個別viewtint color, 這會被他的子視圖繼承害捕。下圖顯示了一個應(yīng)用中主windowtintcolor是紅色的,顯示相同的背景圖片的兩個按鈕闷畸,一個正常渲染尝盼,另一個以模板方式渲染:

asset catalog中可以設(shè)置圖像的渲染模式。在asset catalog中選擇圖片腾啥,在屬性檢查器中使用彈出的Render菜單設(shè)置渲染模式為默認(rèn)值(.Automatic)东涡,原始圖像(.AlwaysOriginal)冯吓,或模版圖像(.AlwaysTemplate) 。這是一個很好的辦法疮跑,這使你每次使用相同的渲染模式設(shè)置圖片時少寫很多代碼组贺。而且,當(dāng)調(diào)用init(named:)祖娘,會返回已經(jīng)設(shè)置好渲染模式的圖片失尖。

Reversible Images

新的iOS9系統(tǒng)中,當(dāng)你的app是在一個本地語言是從右到左的系統(tǒng)上運行的時候渐苏,整個用戶界面會自動改變翻轉(zhuǎn)(改變方向)掀潮。一般情況下,這不會影響到你的圖片琼富。runtime會假定你不想反轉(zhuǎn)圖片當(dāng)用戶界面反轉(zhuǎn)的時候仪吧,因此其默認(rèn)行為是讓他們保持原樣。

不過鞠眉,你可能希望圖片隨著系統(tǒng)一起反轉(zhuǎn)薯鼠。例如,可能你已經(jīng)繪制好一個當(dāng)用戶點擊按鈕后指向新界面出現(xiàn)的方向的箭頭械蹋。如果按鈕作用是在導(dǎo)航界面進(jìn)入一個新的視圖控制器出皇,在從左往右的系統(tǒng)圖片箭頭方向指向右邊,但是在從右往左的系統(tǒng)上圖片箭頭方向應(yīng)該指向左邊哗戈。此圖像在應(yīng)用程序的界面上具有方向的信息;因此它需要水平翻轉(zhuǎn)當(dāng)界面反轉(zhuǎn)時郊艘。

通過調(diào)用圖像的imageFlippedForRightToLeftLayoutDirection,并在界面使用新生成的圖像來達(dá)到上面的效果唯咬。在左到右系統(tǒng)中纱注,正常的圖像將被使用;而在右到左的系統(tǒng)上時,將會自動創(chuàng)建并使用對應(yīng)的相反的圖片副渴。您可以重寫此行為奈附,即使圖像是可反轉(zhuǎn)的。對于UIView煮剧,例如UIImageView,通過設(shè)置viewsemanticContentAttribute防止圖片鏡像将鸵。

不幸的是勉盅,在asset catalog中還沒有辦法指定圖片是可反轉(zhuǎn)的。因此顶掉,在界面中出現(xiàn)的圖片 - 例如,在nib中配置的UIImageView - 你必須使用代碼去控制草娜。在下面這個例子中,我在視圖控制器的viewDidLoad方法中取出圖像視圖(self.iv)的圖片痒筒,并用它自身的可反轉(zhuǎn)版本替換它自己:

override func viewDidLoad() {
    super.viewDidLoad()
    self.iv.image = self.iv.image?.imageFlippedForRightToLeftLayoutDirection()
}

Graphics Contexts

你可能想使用代碼繪制一些圖像宰闰,而不是僅僅使用已經(jīng)存在的圖片文件茬贵。要實現(xiàn)這一點,你需要一個圖形上下文(graphics context)移袍。

圖形上下文就是你可以繪制圖像的地方解藻。反之,你不能用代碼繪制葡盗,除非你有一個圖形上下文螟左。有幾種方法可以得到一個圖形上下文;我將主要介紹兩種已經(jīng)被我的經(jīng)驗證明是最常見的方式:

  • You create an image context
    UIGraphicsBeginImageContextWithOptions函數(shù)會創(chuàng)建一個適合用于繪制圖像的上下文。你可以繪制到此上下文并生成圖像觅够。當(dāng)這樣做之后胶背,調(diào)用UIGraphicsGetImageFromCurrentImageContext把上下文變成一個UIImage,然后調(diào)用UIGraphicsEndImageContext清除上下文〈龋現(xiàn)在你有了一個圖片钳吟,你可以讓它顯示到界面上或者繪制到一些其他的圖形上下文或者保存為一個文件。
  • Cocoa hands you a graphics context
    你子類化UIView然后實現(xiàn)drawRect:方法.當(dāng)你的drawRect:方法被調(diào)用的時候窘拯,Cocoa同時會為你創(chuàng)建一個圖形上下文砸抛,并要求你在里面繪制;不管你畫什么UIView都會顯示出來。
    這種情況有一種輕微的變體是你繼承CALayer并在layerdelegate里面實現(xiàn)drawInContext:,或?qū)崿F(xiàn)drawLayer:inContext树枫。

此外直焙,在任何時刻圖形上下文要么是或不是當(dāng)前的圖形上下文:

  • UIGraphicsBeginImageContextWithOptions不僅創(chuàng)造了一個圖形上下文,這也使得上下文成為當(dāng)前的圖形上下文砂轻。
  • 當(dāng)drawRect:被調(diào)用時奔誓,UIView的圖形上下文已經(jīng)是當(dāng)前的圖形上下文。
  • 有一個context:參數(shù)的回調(diào)函數(shù)并不會使任何上下文變成當(dāng)前的圖形上下文;相反搔涝,該參數(shù)是一個想要你在里面繪制的圖形上下文的引用厨喂,但是只有它稱為當(dāng)前的圖形上下文你才能在它里面繪制,是否這么做取決于你庄呈。

初學(xué)者對于繪制感到困惑的原因是有兩套關(guān)于繪制圖像的工具蜕煌,但是它們對于繪制圖像時的context的處理方式一點都不相同。一組需要一個當(dāng)前上下文;另一組只是需要一個上下文:

  • UIKit
    很多Cocoa類知道如何繪制它們自己;其中包括UIImage诬留,NSString(繪制文本)斜纪,UIBezierPath(用于繪制形狀),和UIColor文兑。有些類提供能力有限但是方便的方法;有些類則非常強大盒刚。在許多情況下,UIKit會滿足你所以的需求绿贞。
    你只能繪制到當(dāng)前圖形上下文中當(dāng)你使用UIKit時因块。所以,如果你在一個UIGraphicsBeginImageContextWithOptionsdrawRect:情況下籍铁,你可以直接使用UIKit便利的方法;而且會有一個當(dāng)前的圖形上下文涡上,它就是你想在里面繪制的上下文趾断。另外,如果你有一個context:的參數(shù)吩愧,而且你還想使用UIKit便利的方法芋酌,你只能把上下文變成當(dāng)前的圖形上下文;通過調(diào)用UIGraphicsPushContext做到這一點(并確保用UIGraphicsPopContext來還原)。
  • Core Graphics
    這是一個完整的繪圖API耻警。Core Graphics隔嫡,通常被稱為Quartz ,或Quartz 2D,是iOS繪圖系統(tǒng)的基礎(chǔ) - UIKit的繪制是建立在它之上 - 所以它是低層次的甘穿,包含很多C函數(shù)腮恩。
    使用Core Graphics繪制時,你必須在每個函數(shù)調(diào)用中明確的指定將要繪制的圖形上下文温兼。如果你你有一個context:參數(shù)秸滴,這可能是你想要繪制的圖形上下文。在UIKit中你不需要一個context的引用募判,但是在Core Graphic中你需要一個context的引用荡含。既然你在當(dāng)前的圖形上下文里面繪制,通過調(diào)用UIGraphicsGetCurrentContext來得到當(dāng)前圖形上下文的引用届垫。

你不必分開使用UIKitCore graphics误债。相反寝蹈,你可以在同一個代碼塊中使用UIKit和Core graphics操作相同的圖形上下文黔州。他們只是控制圖形上下文繪制的兩種不同方式。

因此项贺,在一個圖形上下文中我們有兩套工具和三種方法林螃,總共六種方式來繪圖横漏。在下面的例子中赴肚,我將說明這六種方法。首先,我將通過繼承UIView然后實現(xiàn)drawRect:方法,在UIKit為我們準(zhǔn)備好的當(dāng)前圖形上下文中畫一個藍(lán)色圓圈:

override func drawRect(rect: GGRect) {
    let p = UIBezierPath(ovalInRect: CGRectMake(0, 0, 100, 100))
    UIColor.blueColor().setFill()
    p.fill()
}

下面我將會用Core graphics實現(xiàn)相同的功能,但是得先獲得當(dāng)前圖形上下文得引用:

override func drawRect(rect: CGRect) {
    let context = UIGraphicsGetCurrentContext()
    CGContextAddEllipseInRect(context, CGRectMake(0, 0, 100, 100))
    CGContextSetFillColorWithColor(context, UIColor.blueColor().CGColor)
    CGContextFillPath(context)
}

接下來我會實現(xiàn)一個UIView子類的drawLayer:inContext:方法刻像。在這個例子中,會有一個context的引用,但是它并不是當(dāng)前的圖形上下文蹂季,所以為了使用UIKit父能,我得先讓它成為當(dāng)前得圖形上下文:

override func drawLayer(layer: CALayer, inContext context: CGContext) {
    UIGraphicsPushContext(context)
    let p = UIBezierPath(ovalInRect: CGRectMake(0, 0, 100, 100))
    UIColor.blueColor().setFill()
    p.fill()
    UIGraphicsPopContext()
}

然后我引用以下傳進(jìn)來得context就可以在drawLayer:inContext中使用Core graphics:

override func drawLayer(layer, inContext context: CGContext) {
    CGContextAddEllipseInRect(context, CGRectMake(0, 0, 100, 100))
    CGContextSetFillColorWithColor(context, UIColor.blueColor().CGColor)
    CGContextFillPath(context)
}

最后,我將做一個藍(lán)色圓圈得圖片。我們可以在任何時候(我們不需要等待某些特定方法被調(diào)用)跪者,并在任何類(我們不需要繼承UIView)中實現(xiàn)此功能棵帽。由此產(chǎn)生的UIImage適用于可以使用UIImage的任何地方。例如铅搓,你可以把它賦值給UIImageViewimage屬性星掰,從而導(dǎo)致圖像出現(xiàn)在屏幕上像街±杼模或者你可以將其保存為一個文件。
首先我會使用UIKit來繪制我的圖片:

UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), false, 0)
let p = UIBezierPath(ovalInRect: CGRectMake(0, 0, 100, 100))
UIColor.blueColor().setFill()
p.fill()
let im = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

下面是使用Core Graphics實現(xiàn)相同的功能:

UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), false, 0)
let context = UIGraphicsGetCurrentContext()!
CGContextAddEllipseInRect(context, CGRectMake(0, 0, 100, 100))
CGContextSetFillColorWithColor(context, UIColor.blueColor().CGColor)
CGContextFillPath(context)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

你可能想知道UIGraphicsBeginImageContextWithOptions各項參數(shù)的意義畴栖。第一個參數(shù)很明顯是要創(chuàng)建圖片的尺寸随静。第二個參數(shù)聲明圖像是不是應(yīng)該透明;如果這個參數(shù)為true而不是false,繪制的圖片將有一個黑色的背景吗讶,這并不是我想要的效果燎猛。第三個參數(shù)指定圖像的縮放系數(shù);通過傳遞0,告訴系統(tǒng)根據(jù)主屏幕來設(shè)置圖片的縮放系數(shù)照皆,所以我的圖片在單分辨率和高分辨率的設(shè)備上都會顯示很好重绷。

UIImage Drawing

UIImage提供繪制自身到當(dāng)前的圖形上下文的方法。我們知道如何獲得UIImage膜毁,我們知道如何獲得圖形上下文并使其成為當(dāng)前的圖形上下文昭卓,所以我們可以嘗試使用這些方法來繪制圖片。

下面瘟滨,我會做出一張由兩張火星圖片并排組成的新圖片:

let mars = UIImage(named: "Mars")!
let size = mars.size
UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width * 2, size.height * 2), false, 0.0)
mars.drawAtPoint(CGPointMake(0, 0))
mars.drawAtPoint(CGPointMake(size.width, 0))
let im = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

效果如下:


在上面的例子中圖片的縮放控制的很好候醒。如果有原始的火星圖像的多個分辨率版本,系統(tǒng)會根據(jù)當(dāng)前設(shè)備選擇正確的圖片杂瘸,并指定正確的scale值倒淫。調(diào)用UIGraphicsBeginImageContextWithOptions函數(shù)的第三個參數(shù)為0,因此在圖像上下文中繪制的圖片也會有正確的scale值胧沫。而且從UIGraphicsGetImageFromCurrentImageContext得到的圖像也會有正確的scale值昌简。因此,上面的代碼生成的圖片在當(dāng)前設(shè)備上看起來顯示效果很好绒怨,無論它的屏幕分辨率是多少纯赎。

UIImage的其他一些方法,可以讓你在需要繪制的矩形中指定縮放系數(shù)南蹂,并指定和矩形中已經(jīng)存在的東西的混合模式犬金。為了說明這一點,我將創(chuàng)建一個兩倍大的火星圖片顯示在另一個火星圖片的中心,使用.Multiply混合模式:

let mars = UIImage(named: "Mars")!
let size = mras.size
UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width * 2, size.height * 2), false, 0)
mars.drawInRect(CGRectMake(0, 0, size.width * 2, size.height * 2))
mars.drawInRect(CGRectMake(sizw.width / 2, size.height / 2, size.width, size.height),
    blendMode: .Multiply, 
    alpha: 1.0)
let im = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

效果如下圖:


UIImage的繪制方法沒有辦法指定原始矩形-你要提取原始圖像的某已小塊區(qū)域晚顷。您可以通過創(chuàng)建一個較小的圖形上下文然后把圖片的指定區(qū)域繪制到里面峰伙。例如,為了獲得火星的右半部分的圖像该默,你可以創(chuàng)建一個只有火星圖像寬度一半的圖形上下文瞳氓,然后左移繪制火星,這樣火星圖片只有右半部分在圖形上下文中栓袖。這樣做沒有什么壞處匣摘,這個一種非常標(biāo)準(zhǔn)的策略來獲取原始圖片的某部分區(qū)域。代碼如下:

let mars = UIImage(named: "Mars")!
let size = mars.size
UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width / 2.0, size.height), false, 0.0)
mars.drawAtPoint(CGPointMake(-size.width / 2.0, 0))
let im = UIGraphicsGetImageFromCurrenImageContext()
UIGraphicsEndImageContext()

效果如下圖:


CGImage Drawing

UIImageCore graphics的版本是CGImage裹刮。他們很容易相互轉(zhuǎn)換:UIImage有一個訪問其Quartz圖像數(shù)據(jù)的CGImage屬性音榜,你可以在CGImage上調(diào)用init(CGImage:)或更容易配置的init(CGImage:scale:orientation:)方法來得到一個UIImage。

CGImage允許你直接從原始的UIImage的某個矩形區(qū)域創(chuàng)建一個新的圖片捧弃,而UIImage不能這么做赠叼。(CGImage還有其他UIImage不具備的能力;例如,您可以為CGImage應(yīng)用一個圖片遮罩(image mask)违霞。)我會將火星圖片分為兩半然后分開繪制來說明:

let mars = UIImage(named: "Mars")!
let marsCG = mars.CGImage
let size = mars.size
let marsLeft = CGImageCreateWithImageInRect(marsCG, CGRectMake(0, 0, size.width / 2.0, size.height))
let marsRight = CGImageCreateWithImageInRect(marsCG, CGRectMake(size.width / 2.0, 0, size.width / 2.0, size.height))
UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width * 1.5, size.height * 1.5), false, 0)
let context = UIGraphicsGetCurrentContext()!
CGContextDrawImage(context, CGRectMake(0, 0, size.width / 2, size.height), marsLeft)
CGContextDrawImage(context, CGRectMake(sizw.width, 0, size.width / 2.0, size.height), marsRight)
let im = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

效果如下:


但是嘴办,上面例子有個問題:繪圖是上下顛倒的!代碼沒有對它執(zhí)行翻轉(zhuǎn);但是它是上下鏡像的葛家,或使用專業(yè)的技術(shù)術(shù)語:flipped户辞。當(dāng)你創(chuàng)建一個CGImage,然后用CGContextDrawImage繪制它的時候就會出現(xiàn)這種情況癞谒,這是由于原始的上下文和目標(biāo)上下文的坐標(biāo)系統(tǒng)不匹配造成的底燎。

有很多種方式來修復(fù)不同坐標(biāo)系統(tǒng)之間的不匹配。其中一種是繪制CGImage到一個中間的UIImage然后從中間UIImage中提取出另一個CGImage弹砚。下面的例子通過一個函數(shù)來實現(xiàn)這個功能:

func flip(im: CGImage) -> CGImage {
    let size = CGSizeMake(
        CGFloat(CGImageGetWidth(im),
        CGFloat(CGImageGetHeight(im))))
    UIGraphicsBeginImageContextWithOptions(size, false, 0)
    let context = UIGraphicsGetCurrentContext()!
    CGContextDrawImage(context, 
        CGRectMake(0, 0, size.width, size.height), 
        im)
    let result = UIGraphicsGetImageFromCurrentImageContext().CGImage
    UIGraphicsEndImageContext()
    return result!
}

使用上面那個便利的函數(shù)双仍,我們可以修復(fù)上面例子中使用CGContextDrawImage繪制兩半火星的正確方法了:

CGContextDrawImage(context, 
    CGRectMake(0, 0, size.width / 2.0, size.height),
    flip(marsLeft!))
CGContextDrawImage(context, 
    CGRectMake(size.width, 0, size.width / 2.0, sizw.height),
    flip(marsRight!))

但是,我們還有一個問題:一個高分辨率的設(shè)備上桌吃,如果我們的圖片有一個高分辨率的版本朱沃,那么繪制就會出錯。原因是我們使用UIImageinit(named:)方法獲取的火星圖片茅诱,在高分辨率的設(shè)備上它返回一個更大尺寸的火星圖像逗物,通過設(shè)置圖片的scale屬性來匹配原始火星圖片的大小。但CGImage并沒有scale屬性瑟俭,而且也不知道圖片的尺寸增加了翎卓!因此,在一個高分辨率的設(shè)備上摆寄,我們從火星圖片中提取的mars.CGImagemars.size的尺寸要大失暴,并且之后所有的計算都是錯誤的坯门。

處理CGImage最好的解決辦法是,把它封裝為一個UIImage并繪制這個UIImage而不是直接繪制CGImage逗扒。從CGImage生成UIImage時可以調(diào)用init(CGImage:scale:orientation:)來避免圖片的縮放古戴。而且直接繪制UIImage而不是CGImage也避免翻轉(zhuǎn)問題!下面是不使用flip函數(shù)來處理翻轉(zhuǎn)和縮放的方法:

let mars = UIImage(named: "Mars")!
let size = mars.size
let marsCG = mars.CGImage
let sizeCG = CGSizeMake(
    CGFloat(CGImageGetWidth(marsCG),
    CGFloat(CGImageGetHeight(marsCG))))
let marsLeft = CGImageCreateWithImageInRect(
    marsCG,
    CGRectMake(0, 0, sizeCG.width / 2, sizeCG.height))
let marsRight = CGImageCreateWithImageInRect(
    marsCG,
    CGRectMake(sizeCG.width / 2.0, 0, sizeCG.width / 2.0, sizeCG.height))
UIGraphicsBeginImageContextWithOptions(
    CGSizeMake(size.width * 1.5, size.height), 
    false,
    0.0)
//instead of calling flip, pass through UIImage
UIImage(CGImage: marsLeft!, 
    scale: mars.scale,
    orientation: mars.imageOrientation)
    .drawAtPoint(CGPointMake(0, 0))
UIImage(CGImage: marsRight!,
    scale: mars.scale,
    orientation: mars.imageOrientation)
    .drawAtPoint(CGPointMake(sizw.width, 0))
let im = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
翻轉(zhuǎn)發(fā)生的原因
意外翻轉(zhuǎn)發(fā)生的原因是Core graphics移植于OS X矩肩,OS X的坐標(biāo)系的原點默認(rèn)情況下位于左下角和y軸增長方向是向上的现恼,而iOS上的原點默認(rèn)情況下在位于左上角并且y軸正方向是向下的。在大多數(shù)繪圖的情況下蛮拔,因為圖形上下文的坐標(biāo)系統(tǒng)被自動調(diào)整述暂,所以這不會有什么問題。因此在iOS中使用Core Graphics繪制時上下文中的坐標(biāo)系統(tǒng)的原點在左上角,這和你期望的是一樣的建炫。但是,創(chuàng)建和繪制CGImage暴露了兩個坐標(biāo)世界之間的不兼容

另一個解決方案是疼蛾,繪制CGImage之前對圖形上下文做一個變換(transform),直接翻轉(zhuǎn)上下文的內(nèi)部坐標(biāo)系統(tǒng)肛跌。這是非常有效的,但是如果已經(jīng)存在其他變換察郁,這會非逞苌鳎混亂。

Snapshots

整個視圖 -- 從任何一個單一的按鈕到你整個界面包含視圖整個層次結(jié)構(gòu)--可通過調(diào)用UIView的實例方法drawViewHierarchyInRect:afterScreenUpdates:可以被繪制到當(dāng)前的圖形上下文中. (此方法比CALayerrenderInContext:速度快很多;不過皮钠,renderInContext:確實很方便)稳捆。繪制的結(jié)果是原始視圖的快照(snapshot):它看起來像原來的視圖,但它本質(zhì)上只是原來視圖的一個位圖圖像麦轰。

獲得視圖的快照更快的方法是使用的UIView(或UIScreen)的實例方法snapshotViewAfterScreenUpdates:.結(jié)果是一個UIView乔夯,而不是一個UIImage;這很像一個只知道如何繪制一個圖像--即快照--的UIImageView。這樣的快照視圖通常會被用作擴大其邊界和拉伸其圖像款侵。如果你想拉伸快照讓它表現(xiàn)得像一個可調(diào)整大小的圖像末荐,調(diào)用resizableSnapshotViewFromRect:afterScreenUpdates:withCapInsets:

Snapshots非常有用因為IOS界面的動態(tài)性質(zhì)新锈。例如甲脏,你可以在真正的視圖上面放置一個快照從而隱藏真正的視圖上正在發(fā)生的事情,或者在動畫中移動快照而不是真正的視圖妹笆。

下面是我的應(yīng)用程序中的一個例子块请。這是一個紙牌游戲,它的視圖都是紙牌拳缠。我想用動畫把所有的紙牌從屏幕里面移動到屏幕外面墩新。但我不想移動那些視圖本身!他們需要留在原地脊凰,繪制以后出現(xiàn)的紙牌抖棘。所以我為每個紙牌視圖做了一個快照視圖;然后茂腥,我隱藏真正的紙牌視圖,而把快照視圖放在它們的位置上切省,然后用動畫移除這些快照最岗。代碼如下:

for v in views {
    let snapshot = v.snapshotViewAfterScreenUpdates(false)
    let snap = MySnapBehavior(item: snapshot, 
        snapToPoint: CGPointMake(
            self.anim.referenceView!.bounds.midX,
            -self.anim.referenceView!.bounds.height))
    self.snaps.append(snapshot)
    snapshot.frame = v.frame
    v.hidden = ture
    self.anim.referenceView!.addSubview(snapshot)
    self.anim.addBehavior(snap)
}

CIFilter and CIImage

CIFilterCIImage中的CI代表Core Image,通過數(shù)學(xué)變換轉(zhuǎn)化圖像的技術(shù)朝捆。Core Image首先在桌面(OS X)環(huán)境中使用般渡,并且當(dāng)它被遷移到iOS系統(tǒng)時,一些在桌面上可用的濾鏡無法在IOS上使用(大概是因為對于移動設(shè)備來說這么數(shù)學(xué)運算太過于復(fù)雜)芙盘。多年來越來越多的OS X濾鏡被添加到iOS上驯用,而現(xiàn)在在新的iOS9系統(tǒng)中,兩者沒有差別:所有OS X濾鏡在iOS中都是可用的儒老,并且兩個平臺都具有幾乎相同的API 蝴乔。

一個CIFilter就是一個濾鏡⊥苑可用的濾鏡可以分為幾大類:

  • Patterns and gradients
    這些濾鏡創(chuàng)建的CIImage可以與其他的CIImage薇正,諸如單一的顏色,顏色盤,條紋囚衔,或梯度組合挖腰。
  • Compositing
    這些濾鏡使用圖像處理軟件如Photoshop的混合模式來組合圖片。
  • Color
    這些濾鏡調(diào)整或改變圖像的顏色练湿。因此猴仑,你可以改變圖像的飽和度,色調(diào)肥哎,亮度辽俗,對比度,伽瑪值和白平衡贤姆,曝光榆苞,陰影和高光,等等霞捡。
  • Geometric
    這些濾鏡對圖片執(zhí)行基本的幾何變換坐漏,如縮放,旋轉(zhuǎn)和裁剪碧信。
  • Transformation
    這些濾鏡對圖片進(jìn)行變形赊琳,模糊,或風(fēng)格化砰碴。
  • Transition
    這些濾鏡提供了一個圖像和另一個之間轉(zhuǎn)換的幀;通過順序請求幀躏筏,可以設(shè)置過渡動畫效果。
  • Special purpose
    這些濾鏡執(zhí)行高度專業(yè)化的操作呈枉,如人臉檢測和生成的QR碼趁尼。

CIFilter的基本用法相當(dāng)簡單︰

  • 您可以通過提供濾鏡的字符串名稱來指定想使用的濾鏡;想知道都有哪些名字埃碱,查閱Core Image Filter Reference,或調(diào)用CIFilter的類方法filterNamesInCategories:并提供一個nil參數(shù)酥泞。
  • 每個濾鏡具有少量的鍵值對確定其行為砚殿。您可以通過代碼了解全部的鍵,但通常你會參考文檔芝囤。對于你感興趣的每個鍵似炎,你提供的一個鍵 - 值對。在提供值中悯姊,數(shù)字必須被包裝成NSNumber羡藐,并且其他有用的類,如CIVectorCIColor悯许,其使用很容易理解氯析。

CIFilter鍵中輸入的圖像或被濾鏡操作的圖片必須是CIImage活箕“吡唬可以從CGImage中獲取CIImage通過調(diào)用init(CGImage:)或者從UIImage中獲取CIImage通過調(diào)用init(image:)念祭。

不要嘗試直接從UIImageCIImage屬性獲取CIImage。此屬性不會把UIImage轉(zhuǎn)換成CIImage启上!它僅僅只是指向支持(back)UIImageCIImage,如果這個UIImage真的是被CIImage支持(back)的;但是有可能你的圖片不是由CIImage支持的店印,而是由一個CGImage冈在。

然而你可以從濾鏡的輸出中獲取CIImage--這意味著濾鏡可以鏈?zhǔn)秸{(diào)用。
有三種方式來描述和使用濾鏡:

  • 通過CIFilterinit(name:)創(chuàng)建濾鏡按摘。通過重復(fù)調(diào)用setValue:forKey:方法或者通過調(diào)用setValuesForKeysWith- Dictionary: 來為濾鏡添加鍵值對包券。通過濾鏡的outputImage獲取CIImage
  • 通過調(diào)用CIFilterinit(name:withInputParameters:)并提供鍵值對創(chuàng)建濾鏡炫贤。通過濾鏡的outputImage獲取CIImage溅固。
  • 如果CIFilter需要輸入圖像而且你已經(jīng)有了一個CIImage,可以通過調(diào)用CIImage實例方法imageByApplyingFilter:withInputParameters:并提供濾鏡和鍵值對兰珍,然后獲取輸出的CIImage侍郭。

當(dāng)你構(gòu)建一個濾鏡鏈時,實際上什么都沒有發(fā)生掠河。唯一的密集計算會發(fā)生在最后亮元,當(dāng)你變換濾鏡鏈輸出的CIImage為位圖圖形時。這就是所謂的圖像渲染唠摹。有兩種主要方式的方式實現(xiàn)這一點:

  • With a CIContext
    通過調(diào)用init(options:)創(chuàng)建一個CIContext爆捞。然后把最后的CIImage作為第一個參數(shù)傳遞給createCGImage:fromRect:去處理。這會渲染圖像勾拉。這種方式有點輕微棘手的事情是煮甥,CIImage不具有framebounds;它只有一個extent盗温。你會經(jīng)常使用它作為createCGImage:fromRect:的第二個參數(shù).最終輸出的CGImage可以隨便使用,例如在應(yīng)用程序的顯示成肘,或者轉(zhuǎn)換為一個UIImage卖局,或者進(jìn)行進(jìn)一步的繪制。
    這種方法具有一個優(yōu)點是當(dāng)渲染發(fā)生時你有全部的控制權(quán)艇劫。但是要注意:創(chuàng)建一個CIContext是非常昂貴的吼驶!最好的方式是,事先只創(chuàng)建CIContext一次--整個app中--店煞, 并在每次渲染時重用它蟹演。
  • With a UIImage
    在最后的CIImage上調(diào)用的init(CIImage:)init(CIImage:scale:orientation:)直接創(chuàng)建一個UIImage。然后顷蟀,可以把UIImage繪制到某些圖形上下文中酒请。在繪制發(fā)生的時候,圖像就被渲染了鸣个。

蘋果聲稱羞反,你可以簡單地調(diào)用init(CIImage:)創(chuàng)建一個UIImage然后把它賦值給UIImageView的image屬性,那么UIImageView會渲染這個圖像。根據(jù)我的經(jīng)驗囤萤,這是不正確的昼窗。為了渲染它你必須顯式的繪制它。

為了說明這一點涛舍,我會用我自己的一張普通的照片創(chuàng)建一個圓形的帶陰影效果的圖片澄惊。我們會先從圖片中導(dǎo)出CIImage。我們對圖片運用一個白色到黑色的徑向漸變的濾鏡富雅。然后掸驱,我們使用第二個濾鏡,這個濾鏡把上面的逕向漸變當(dāng)做一個遮罩來把我的照片和一個透明背景色混合:其中徑向漸變?yōu)榘咨u變的內(nèi)半徑)的部分没佑,只會看到我的照片毕贼,徑向漸變是黑色(漸變的外半徑)的部分,我們只會看到透明的顏色蛤奢,在它們中間的環(huán)形漸變區(qū)域內(nèi)圖片漸漸消失鬼癣。下面的代碼用兩種方式來設(shè)置濾鏡:

let moi = UIImage(named: "Moi")!
let moiCI = CIImage(image: moi)!
let moiextent = moiCI.extent
let center = CIVector(x: moiextent.width / 2.0, y: moiextent.height / 2.0)
let smallerDimension = min(moiextent.width, moiextent.height)
let largerDimension = max(moiextent.width, moiextent.height)
//first filter
let grad = CIFilter(name: "CIRadialGradient")!
grad.setValue(center, forKey: "inputCenter")
grad.setValue(smallerDimension / 2.0 * 0.85, forKey: "inputRadius0")
grad.setValue(largerDimension / 2.0, forKey: "inputRadius1")
let gradimage = grad.outputImage!
//second filter
let blendImage = moiCI.imageByApplyingFilter("CIBlendWithMask", withInputParameters: ["inputMaskImage": gradImage])

現(xiàn)在我們在濾鏡鏈的最后得到了最終的CIImage(blendimage);注意:此時處理器還沒有進(jìn)行任何渲染。現(xiàn)在我們想要生成最終的位圖并顯示它远剩。例如,我們可以通過UIImageView來顯示它扣溺。有兩種不同的方法可以做到這一點。我們可以通過調(diào)用CIContext(options:nil)CIImage傳給我們已經(jīng)事先準(zhǔn)備好的CIContext(self.context)來創(chuàng)建一個CGImage

let moiCG = self.context.createCGImage(blendImage, fromRect: moiextent)
self.iv.image = UIImage(CGImage: moiCG)

另外瓜晤,我們可以把濾鏡鏈最后輸出的CIImage作為一個UIImage然后把它繪制成一個位圖:

UIGraphicsBeginImageContextWithOptions(moiextent.size, false, 0)
UIImage(CIImage: blendImage).drawInRect(moiextent)
let im = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.iv.image = im

效果如下:


通過繼承CIFilter可以把濾鏡鏈封裝成一個自定義濾鏡锥余。子類只需要重寫outputImage屬性(以及可能的其他方法,如setdefaults)痢掠,并使用額外的屬性使之成為鍵值編碼兼容的對于任何輸入的鍵.驱犹。下面是我們的陰影濾鏡一個簡單的CIFilter子類嘲恍,其中一個輸入的鍵是指定輸入的圖像,另一個輸入鍵是調(diào)整漸變的小半徑的百分比:

class MyVignetterFilter: CIFilter {
    var inputImage: CIImage?
    var inputPercentage: NSNumber? = 1.0
    override var outputImage: CIImage? {
        return self.makeOutputImage()
    }
    private func makeOutputImage() -> CIImage? {
        guard let inputImage = self.inputImage 
        else {return nil}
        guard let inputPercentage = self.inputPercentage 
        else {return nil}
        let extent = inputImage.extent
        let grad = CIFilter(name: "CIRadialGradient")!
        let center = CIVector(x: extent.width / 2.0, y: extent.height / 2.0)
        let smallerDimension = min(extent.width, extent.height)
        let largerDimension = max(extent.width, extent.height)
        grad.setValue(center, forKey: "inputCenter")
        grad.setValue(smallerDimension / 2.0 * CGFloat(inputPercentage), forKey: "inputRadius0")
        grad.setValue(largerDimension / 2.0, forKey: "inputRadius1")
        let blend = CIFilter(name: "CIBlendWithMask")!
        blend.setValue(inputImage, forKey: "inputImage")
        blend.setValue(grad.outputImage, forKey: "inputMaskImage")
        return blend.outputImage
    }
}

下面是如何使用我們的CIFilter子類并顯示其輸出的一個例子:

let vig = MyVignetterFilter()
let moiCI = CIImage(image: UIImage(named: "Moi")!)!
vig.setValuesForKeysWithDictionary([
    "inputImage": moiCI,
    "inputPercentage": 0.7
])
let outim = vig.outputImage!
let outimCG = self.context.createCGImage(outim, formRect: outim.extent)
self.iv.image = UIImage(CGImage: outimCG)

Blur and Vibrancy Views

iOS上的某些視圖雄驹,例如導(dǎo)航欄和控制中心會顯示一種半透明的模糊過渡效果佃牛。iOS提供了UIVisualEffectView類來幫助你實現(xiàn)這種效果。你可以把其他視圖放到UIVisualEffectView前面医舆,但是任何子視圖應(yīng)該放在contentView里面俘侠。通過設(shè)置contentViewbackgroundColor來實現(xiàn)模糊的效果。

使用init(effect:)來創(chuàng)建UIVisualEffectView并使用它;effect:參數(shù)是一個UIVisualEffect子類的實例:

  • UIBlurEffect
    調(diào)用init(style:)來創(chuàng)建一個UIBlurEffect;style參數(shù)(UIBlurEffectStyle)是.Dark蔬将,.Light.ExtraLight爷速。(.ExtraLight特別適合于一小塊界面,例如一個導(dǎo)航欄或工具欄霞怀。):
    let fuzzy = UIVisualEffectView(effect: (UIBlurEffect(style: .Light)))
  • UIVibrancyEffect
    調(diào)用init(forBlurEffect:)來初始化UIVibrancyEffect惫东。Vibrancy讓視圖和它地下的模糊效果相協(xié)調(diào)。這樣做的目的是毙石,高亮效果視圖應(yīng)該在一個模糊效果視圖的前面廉沮,一般在單一的UIViewcontentView里面添加一個高亮效果;告訴高亮效果的下面是什么模糊效果,它們會自己協(xié)調(diào)徐矩。您可以獲取的視圖的模糊效果作為其effect屬性滞时,但是這是一個UIVisualEffect類,所以你必須要轉(zhuǎn)換為UIBlurEffect才能把它傳遞給init(forBlurEffect:)

下面是一個模糊效果并覆蓋整個視圖的視圖滤灯,而且包含一個含有UILabel的高亮視圖:

let blur = UIVisualEffectView(effect: UIBlurEffect(style: .ExtraLight))
blur.frame = mainview.bounds
blur.autoresizingMasks = [.Flexiblewidth, .FlexibleHeight]
let vib = UIVisualEffectView(effect: UIVibrancyEffect(forBlurEffect: blur.effect as! UIBlurEffect))
let lab = UILabel()
lab.text = "Hello, world!"
lab.sizeToFit()
vib.frame = lab.frame
vib.frame = lab.frame
vib.contentView.addSubview(lab)
vib.center = CGPointMake(blur.bounds.midX, blur.bounds.midY)
vib.autoresizingMask = [.FlexibleToMargin, .FlexibleBottomMargin, .FlexibleLeftMargin, .FlexibleRightMargin]
blur.contextView.addSubview(vib)
mainview.addSubview(blur)

效果如下圖:


蘋果似乎認(rèn)為高亮使視圖與模糊底層結(jié)合時更加清晰漂洋,但我不這么認(rèn)為。高亮的視圖的顏色和會模糊的顏色綜合但這會是高亮的視圖更加不清晰力喷。上圖中使用.Dark.ExtraLight模糊效果時label看起來還可以,但是使用.Light就很難看清了演训。
在UIVisualEffectView.h頭文件中有很多值得研究的額外信息弟孟。例如,為了高亮圖片視圖必須使用模版(template)圖片样悟。
在nib編輯器中都可以直接使用模糊和高亮視圖拂募。

==未完待續(xù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市窟她,隨后出現(xiàn)的幾起案子陈症,更是在濱河造成了極大的恐慌,老刑警劉巖震糖,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件录肯,死亡現(xiàn)場離奇詭異,居然都是意外死亡吊说,警方通過查閱死者的電腦和手機论咏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門优炬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人厅贪,你說我怎么就攤上這事蠢护。” “怎么了养涮?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵葵硕,是天一觀的道長。 經(jīng)常有香客問我贯吓,道長懈凹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任宣决,我火速辦了婚禮蘸劈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘尊沸。我一直安慰自己威沫,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布洼专。 她就那樣靜靜地躺著棒掠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪屁商。 梳的紋絲不亂的頭發(fā)上烟很,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機與錄音蜡镶,去河邊找鬼雾袱。 笑死,一個胖子當(dāng)著我的面吹牛官还,可吹牛的內(nèi)容都是我干的芹橡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼望伦,長吁一口氣:“原來是場噩夢啊……” “哼林说!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起屯伞,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤腿箩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后劣摇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體珠移,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了剑梳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唆貌。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖垢乙,靈堂內(nèi)的尸體忽然破棺而出锨咙,到底是詐尸還是另有隱情,我是刑警寧澤追逮,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布酪刀,位于F島的核電站,受9級特大地震影響钮孵,放射性物質(zhì)發(fā)生泄漏骂倘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一巴席、第九天 我趴在偏房一處隱蔽的房頂上張望历涝。 院中可真熱鬧,春花似錦漾唉、人聲如沸荧库。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽分衫。三九已至,卻和暖如春般此,著一層夾襖步出監(jiān)牢的瞬間蚪战,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工铐懊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留邀桑,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓科乎,卻偏偏與公主長得像概漱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子喜喂,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353

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