UIImage對(duì)象是iOS中用來(lái)顯示圖像數(shù)據(jù)的高級(jí)接口。我們可以從文件凫海,NSData呛凶,Quartz圖片對(duì)象中創(chuàng)建UIImage對(duì)象⌒刑埃可以說(shuō)這個(gè)類(lèi)是我們接觸頻率非常高的一個(gè)類(lèi)漾稀。
UIImage的不可變性
UIImage對(duì)象是不可變的,所以一旦創(chuàng)建后建瘫,我們就不能再改變它的屬性崭捍。這也就意味著,我們只能在初始化方法中提供屬性值或依賴(lài)于圖片自身的屬性值啰脚。同樣殷蛇,由于其不可變,所以在任何線程中都可以安全地使用它。
如果我們想修改UIImage對(duì)象的一些屬性粒梦,則可以使用便捷方法和自定義的參數(shù)值來(lái)創(chuàng)建圖像的一份拷貝亮航。
另外,由于UIImage對(duì)象是不可變的匀们,所以它沒(méi)有提供訪問(wèn)底層圖片數(shù)據(jù)的方法缴淋。不過(guò)我們可以使用UIImagePNGRepresentation或UIImageJPEGRepresentation方法來(lái)獲取包含PNG或JPG格式的數(shù)據(jù)的NSData對(duì)象。如下代碼所示:
let image = UIImage(named: "swift");let imageData:NSData? = UIImageJPEGRepresentation(image!, 1.0)
創(chuàng)建UIImage對(duì)象
對(duì)于一個(gè)UIImage對(duì)象來(lái)說(shuō)泄朴,它的數(shù)據(jù)源主要有以下幾種:
文件:我們可以使用init(contentsOfFile:)方法來(lái)從指定文件中創(chuàng)建對(duì)象重抖。
純圖片數(shù)據(jù)(NSData):如果在內(nèi)存中有圖片的原始數(shù)據(jù)(表示為NSData對(duì)象),則可以使用init(data:)來(lái)創(chuàng)建祖灰。需要注意的是這個(gè)方法會(huì)對(duì)象圖片數(shù)據(jù)做緩存钟沛。
CGImage對(duì)象:如果我們有一個(gè)CGImage對(duì)象,則可以使用init(CGImage:)或init(CGImage:scale:orientation:)創(chuàng)建UIImage對(duì)象夫植。
CIImage對(duì)象:如果我們有一個(gè)CIImage對(duì)象讹剔,則可以使用init(CIImage:)或init(CIImage:scale:orientation:)創(chuàng)建UIImage對(duì)象油讯。
需要注意的是详民,如果是從文件或者純圖片數(shù)據(jù)中創(chuàng)建UIImage對(duì)象,則要求對(duì)應(yīng)的圖片格式是系統(tǒng)支持的圖片類(lèi)型陌兑。
對(duì)于Objective-C來(lái)說(shuō)沈跨,UIImage對(duì)象也提供了這些初始化方法對(duì)應(yīng)的便捷類(lèi)方法來(lái)創(chuàng)建對(duì)象。
內(nèi)存管理
在實(shí)際的應(yīng)用中兔综,特別是圖片類(lèi)應(yīng)用中饿凛,我們可能需要使用大量的圖片。我們都知道软驰,圖片通常都是非常占內(nèi)存的涧窒。如果同一時(shí)間加載大量的圖片,就可能占用大量的系統(tǒng)內(nèi)存锭亏。
為此纠吴,Apple采用了一種比較巧妙的策略。在低內(nèi)存的情況下慧瘤,系統(tǒng)會(huì)強(qiáng)制清除UIImage對(duì)象所指向的圖片數(shù)據(jù)戴已,以釋放部分內(nèi)存。注意锅减,這種清除行為影響到的只是圖片數(shù)據(jù)糖儡,而不會(huì)影響到UIImage對(duì)象本身。當(dāng)我們需要繪制那些圖片數(shù)據(jù)已經(jīng)被清除的UIImage對(duì)象時(shí)怔匣,對(duì)象會(huì)自動(dòng)從源文件中重新加載數(shù)據(jù)握联。當(dāng)然,這是以時(shí)間換空間的一種策略,會(huì)導(dǎo)致一定的性能損耗金闽。
說(shuō)到這里永部,我們不得不提一下init(named:)方法了∧欧可以說(shuō)我們平時(shí)創(chuàng)建UIImage對(duì)象用得最多的應(yīng)該就是這個(gè)方法苔埋。這個(gè)方法主要是使用bundle中的文件創(chuàng)建圖片的快捷方式。關(guān)于這個(gè)方法蜒犯,有幾點(diǎn)需要注意:
緩存:這個(gè)方法會(huì)首先去系統(tǒng)緩存中查找是否有圖片名對(duì)應(yīng)的圖片组橄。如果有就返回緩存中的圖片;如果沒(méi)有罚随,則該方法從磁盤(pán)或者asset catalog中加載圖片并返回玉工,同時(shí)將圖片緩存到系統(tǒng)中。緩存的圖片只有在收到內(nèi)存警告時(shí)才會(huì)釋放淘菩。因此遵班,如果圖片的使用頻率比較低,則可以考慮使用imageWithContentsOfFile:方法來(lái)加載圖片潮改,這樣可以減少內(nèi)存資源的消耗狭郑。當(dāng)然,這需要權(quán)衡考慮汇在,畢竟讀寫(xiě)磁盤(pán)也是有性能消耗的翰萨,而且現(xiàn)在的高端機(jī)內(nèi)存已經(jīng)不小了。
多分辨率圖片處理:在iOS 4.0后糕殉,該方法會(huì)根據(jù)屏幕的分辨率來(lái)查找對(duì)應(yīng)尺寸的圖片亩鬼。即我們使用時(shí),只需要寫(xiě)圖片名阿蝶,而不需要指定是1x, 2x還是3x圖雳锋,該方法會(huì)自己判斷。
png圖片后綴:在iOS 4.0以后羡洁,如果圖片是png格式的玷过,則圖片文件名不需要附帶擴(kuò)展名。
線程安全性:該方法在iOS 9.0之前并不是線程安全的焚廊,在二級(jí)線程中調(diào)用可能會(huì)導(dǎo)致崩潰冶匹。在iOS 9.0之后,Apple作了優(yōu)化處理咆瘟,將其改為線程安全的方法嚼隘。為了避免不必要的麻煩,盡量在主線程中調(diào)用這個(gè)方法袒餐。
圖片拉伸
當(dāng)我們的圖片比所要填充的區(qū)域小時(shí)飞蛹,會(huì)導(dǎo)致圖片變形谤狡。如以下圖片,原始大小為10030卧檐,將其放到一個(gè)30050的UIImageView中時(shí)墓懂,整個(gè)圖片被拉伸。
原始圖片
拉伸后的圖片
這時(shí)我們就需要做特殊的處理霉囚。
Android的同學(xué)應(yīng)該都知道.9圖捕仔,這種圖片可以只拉伸中間的部分,而保持四個(gè)角不變形盈罐。在iOS中也支持這種操作榜跌。在早期的iOS版本中,UIImage提供了如下方法來(lái)執(zhí)行此操作:
func stretchableImageWithLeftCapWidth(_ leftCapWidth: Int, topCapHeight topCapHeight: Int) -> UIImage
這個(gè)方法通過(guò)leftCapWidth和topCapHeight兩個(gè)參數(shù)來(lái)定義四個(gè)角的大小盅粪。不過(guò)這個(gè)方法在iOS 5中就被Deprecated了钓葫,對(duì)應(yīng)的兩個(gè)屬性leftCapWidth和topCapHeight也是相同的命運(yùn)。所以現(xiàn)在不建議使用它們票顾。另外础浮,對(duì)于如何解釋leftCapWidth和topCapHeight,大家可以參考一下@M了個(gè)J的iOS圖片拉伸技巧奠骄。
在iOS 5中豆同,我們可以使用以下方法來(lái)執(zhí)行相同的操作:
func resizableImageWithCapInsets(_ capInsets: UIEdgeInsets) -> UIImage
這個(gè)方法通過(guò)一個(gè)UIEdgeInsets來(lái)指定上下左右不變形的寬度或高度。它會(huì)返回一個(gè)新的圖像戚揭。而如果圖像被拉伸诱告,則會(huì)以平鋪的方式來(lái)處理中間的拉伸區(qū)域撵枢。
我們對(duì)上面的圖片做如下處理:
let resizedButtonImageView = UIImageView(image: normalButtonImage?.resizableImageWithCapInsets(UIEdgeInsets(top: 15, left: 15, bottom: 15, right: 15)))
resizedButtonImageView.frame = CGRectMake(0, 60, 300, 50)
其得到的結(jié)果如下所示:
在iOS 6民晒,Apple又為我們提供了一個(gè)新的方法,相較于上面這個(gè)方法锄禽,只是多一個(gè)resizingMode參數(shù)潜必,允許我們指定拉伸模式。
func resizableImageWithCapInsets(_ capInsets: UIEdgeInsets, resizingMode resizingMode: UIImageResizingMode) -> UIImage
這個(gè)方法的拉伸模式分兩種:平鋪(Tile)和拉伸(Stretch)沃但。如果是平鋪模式磁滚,則跟前一個(gè)方法是一樣的效果。
動(dòng)效圖片對(duì)象
如果我們有一組大小和縮放因子相同的圖片宵晚,就可以將這些圖片加載到同一個(gè)UIImage對(duì)象中垂攘,形成一個(gè)動(dòng)態(tài)的UIImage對(duì)象。為此淤刃,UIImage提供了以下方法:
class func animatedImageNamed(_ name: String, duration duration: NSTimeInterval) -> UIImage?
這個(gè)方法會(huì)加載以name為基準(zhǔn)文件名的一系列文件晒他。如,假設(shè)我們的name參數(shù)值為”swift”逸贾,則這個(gè)方法會(huì)加載諸如”swift0”, “swift1”,…, “swift1024”這樣的一系列的文件陨仅。
這里有兩個(gè)問(wèn)題需要注意:
文件的序號(hào)必須是從0開(kāi)始的連續(xù)數(shù)字津滞,如果不從0開(kāi)始,則在Playground中是會(huì)報(bào)錯(cuò)的灼伤。而如果中間序號(hào)有斷触徐,而中斷后的圖片是不會(huì)被加載的。
所有文件的大小和縮放因子應(yīng)該是相同的狐赡,否則顯示時(shí)會(huì)有不可預(yù)期的結(jié)果撞鹉,這種結(jié)果主要表現(xiàn)為播放的順序可能是雜亂的。
如果我們有一組基準(zhǔn)文件名不同的文件颖侄,但其大小和縮放因子相同孔祸,則可能使用以下方法:
class func animatedImageWithImages(_ images: [UIImage], duration duration: NSTimeInterval) -> UIImage?
傳入一個(gè)UIImage數(shù)組來(lái)拼裝一個(gè)動(dòng)效UIImage對(duì)象。
另外发皿,UIImage也提供了resizable版本的動(dòng)效方法崔慧,如下所示:
class func animatedResizableImageNamed(_ name: String, capInsets capInsets: UIEdgeInsets, duration duration: NSTimeInterval) -> UIImage?
class func animatedResizableImageNamed(_ name: String, capInsets capInsets: UIEdgeInsets, resizingMode resizingMode: UIImageResizingMode, duration duration: NSTimeInterval) -> UIImage?
第一個(gè)方法的UIImageResizingMode默認(rèn)是UIImageResizingModeTile,所以如果想對(duì)圖片做拉伸處理穴墅,可以使用第二個(gè)的方法惶室,并傳入U(xiǎn)IImageResizingModeStretch。
圖片大小的限制
UIImage對(duì)象使用的圖片大小盡量小于10241024玄货。因?yàn)檫@么大的圖片消耗的內(nèi)存過(guò)大皇钞,在將其作為OpenGL中的貼圖或者是繪制到view/layer中時(shí),可以會(huì)出現(xiàn)問(wèn)題松捉。如果僅僅是代碼層面的操作的話夹界,則沒(méi)有這個(gè)限制。比如隘世,將一個(gè)大于10241024的圖片繪制到位圖圖形上下文中以重新設(shè)定其大小可柿。事實(shí)上,我們需要通過(guò)這種操作來(lái)改變圖片大小丙者,以將其繪制到視圖中复斥。
支持的圖片格式
UIImage支持的圖片格式在UIImage Class Reference中列出來(lái)了,大家可以直接參考械媒。
需要注意的一點(diǎn)是RGB-565格式的BMP文件在加載時(shí)會(huì)被轉(zhuǎn)換成ARGB-1555格式目锭。
示例代碼
本文的示例代碼已上傳到github,可點(diǎn)擊這里查看纷捞。
參考
UIImage Class Reference