本系列主要基於Apple官方文檔,更多的是對參考文檔重點內容的翻譯與補充。該系列中的每篇文章會持續更新與補充。如有問題,歡迎通過微博告訴我,我將及時進行更正,謝謝!!!
UIImage對象是iOS中用來顯示圖像數據的高級接口。我們可以從文件,NSData,Quartz圖片對象中創建UIImage對象。可以說這個類是我們接觸頻率非常高的一個類。
UIImage的不可變性
UIImage對象是不可變的,所以一旦創建後,我們就不能再改變它的屬性。這也就意味著,我們只能在初始化方法中提供屬性值或依賴於圖片自身的屬性值。同樣,由於其不可變,所以在任何線程中都可以安全地使用它。
如果我們想修改UIImage對象的一些屬性,則可以使用便捷方法和自定義的參數值來創建圖像的一份拷貝。
另外,由於UIImage對象是不可變的,所以它沒有提供訪問底層圖片數據的方法。不過我們可以使用UIImagePNGRepresentation或UIImageJPEGRepresentation方法來獲取包含PNG或JPG格式的數據的NSData對象。如下代碼所示:
let image = UIImage(named: "swift"); let imageData:NSData? = UIImageJPEGRepresentation(image!, 1.0)
創建UIImage對象
對於一個UIImage對象來說,它的數據源主要有以下幾種:
文件:我們可以使用init(contentsOfFile:)方法來從指定文件中創建對象。
純圖片數據(NSData):如果在內存中有圖片的原始數據(表示為NSData對象),則可以使用init(data:)來創建。需要注意的是這個方法會對象圖片數據做緩存。
CGImage對象:如果我們有一個CGImage對象,則可以使用init(CGImage:)或init(CGImage:scale:orientation:)創建UIImage對象。
CIImage對象:如果我們有一個CIImage對象,則可以使用init(CIImage:)或init(CIImage:scale:orientation:)創建UIImage對象。
需要注意的是,如果是從文件或者純圖片數據中創建UIImage對象,則要求對應的圖片格式是系統支持的圖片類型。
對於Objective-C來說,UIImage對象也提供了這些初始化方法對應的便捷類方法來創建對象。
內存管理
在實際的應用中,特別是圖片類應用中,我們可能需要使用大量的圖片。我們都知道,圖片通常都是非常占內存的。如果同一時間加載大量的圖片,就可能占用大量的系統內存。
為此,Apple采用了一種比較巧妙的策略。在低內存的情況下,系統會強制清除UIImage對象所指向的圖片數據,以釋放部分內存。注意,這種清除行為影響到的只是圖片數據,而不會影響到UIImage對象本身。當我們需要繪制那些圖片數據已經被清除的UIImage對象時,對象會自動從源文件中重新加載數據。當然,這是以時間換空間的一種策略,會導致一定的性能損耗。
說到這裡,我們不得不提一下init(named:)方法了。可以說我們平時創建UIImage對象用得最多的應該就是這個方法。這個方法主要是使用bundle中的文件創建圖片的快捷方式。關於這個方法,有幾點需要注意:
緩存:這個方法會首先去系統緩存中查找是否有圖片名對應的圖片。如果有就返回緩存中的圖片;如果沒有,則該方法從磁盤或者asset catalog中加載圖片並返回,同時將圖片緩存到系統中。緩存的圖片只有在收到內存警告時才會釋放。因此,如果圖片的使用頻率比較低,則可以考慮使用imageWithContentsOfFile:方法來加載圖片,這樣可以減少內存資源的消耗。當然,這需要權衡考慮,畢竟讀寫磁盤也是有性能消耗的,而且現在的高端機內存已經不小了。
多分辨率圖片處理:在iOS 4.0後,該方法會根據屏幕的分辨率來查找對應尺寸的圖片。即我們使用時,只需要寫圖片名,而不需要指定是1x, 2x還是3x圖,該方法會自己判斷。
png圖片後綴:在iOS 4.0以後,如果圖片是png格式的,則圖片文件名不需要附帶擴展名。
線程安全性:該方法在iOS 9.0之前並不是線程安全的,在二級線程中調用可能會導致崩潰。在iOS 9.0之後,Apple作了優化處理,將其改為線程安全的方法。為了避免不必要的麻煩,盡量在主線程中調用這個方法。
圖片拉伸
當我們的圖片比所要填充的區域小時,會導致圖片變形。如以下圖片,原始大小為100*30,將其放到一個300*50的UIImageView中時,整個圖片被拉伸。
原始圖片
拉伸後的圖片
這時我們就需要做特殊的處理。
Android的同學應該都知道.9圖,這種圖片可以只拉伸中間的部分,而保持四個角不變形。在iOS中也支持這種操作。在早期的iOS版本中,UIImage提供了如下方法來執行此操作:
func stretchableImageWithLeftCapWidth(_ leftCapWidth: Int, topCapHeight topCapHeight: Int) -> UIImage
這個方法通過leftCapWidth和topCapHeight兩個參數來定義四個角的大小。不過這個方法在iOS 5中就被Deprecated了,對應的兩個屬性leftCapWidth和topCapHeight也是相同的命運。所以現在不建議使用它們。另外,對於如何解釋leftCapWidth和topCapHeight,大家可以參考一下@M了個J的iOS圖片拉伸技巧。
在iOS 5中,我們可以使用以下方法來執行相同的操作:
func resizableImageWithCapInsets(_ capInsets: UIEdgeInsets) -> UIImage
這個方法通過一個UIEdgeInsets來指定上下左右不變形的寬度或高度。它會返回一個新的圖像。而如果圖像被拉伸,則會以平鋪的方式來處理中間的拉伸區域。
我們對上面的圖片做如下處理:
let resizedButtonImageView = UIImageView(image: normalButtonImage?.resizableImageWithCapInsets(UIEdgeInsets(top: 15, left: 15, bottom: 15, right: 15))) resizedButtonImageView.frame = CGRectMake(0, 60, 300, 50)
其得到的結果如下所示:
在iOS 6,Apple又為我們提供了一個新的方法,相較於上面這個方法,只是多一個resizingMode參數,允許我們指定拉伸模式。
func resizableImageWithCapInsets(_ capInsets: UIEdgeInsets, resizingMode resizingMode: UIImageResizingMode) -> UIImage
這個方法的拉伸模式分兩種:平鋪(Tile)和拉伸(Stretch)。如果是平鋪模式,則跟前一個方法是一樣的效果。
動效圖片對象
如果我們有一組大小和縮放因子相同的圖片,就可以將這些圖片加載到同一個UIImage對象中,形成一個動態的UIImage對象。為此,UIImage提供了以下方法:
class func animatedImageNamed(_ name: String, duration duration: NSTimeInterval) -> UIImage?
這個方法會加載以name為基准文件名的一系列文件。如,假設我們的name參數值為”swift”,則這個方法會加載諸如”swift0”, “swift1”,…, “swift1024”這樣的一系列的文件。
這裡有兩個問題需要注意:
文件的序號必須是從0開始的連續數字,如果不從0開始,則在Playground中是會報錯的。而如果中間序號有斷,而中斷後的圖片是不會被加載的。
所有文件的大小和縮放因子應該是相同的,否則顯示時會有不可預期的結果,這種結果主要表現為播放的順序可能是雜亂的。
如果我們有一組基准文件名不同的文件,但其大小和縮放因子相同,則可能使用以下方法:
class func animatedImageWithImages(_ images: [UIImage], duration duration: NSTimeInterval) -> UIImage?
傳入一個UIImage數組來拼裝一個動效UIImage對象。
另外,UIImage也提供了resizable版本的動效方法,如下所示:
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?
第一個方法的UIImageResizingMode默認是UIImageResizingModeTile,所以如果想對圖片做拉伸處理,可以使用第二個的方法,並傳入UIImageResizingModeStretch。
圖片大小的限制
UIImage對象使用的圖片大小盡量小於1024*1024。因為這麼大的圖片消耗的內存過大,在將其作為OpenGL中的貼圖或者是繪制到view/layer中時,可以會出現問題。如果僅僅是代碼層面的操作的話,則沒有這個限制。比如,將一個大於1024*1024的圖片繪制到位圖圖形上下文中以重新設定其大小。事實上,我們需要通過這種操作來改變圖片大小,以將其繪制到視圖中。
支持的圖片格式
UIImage支持的圖片格式在UIImage Class Reference中列出來了,大家可以直接參考。
需要注意的一點是RGB-565格式的BMP文件在加載時會被轉換成ARGB-1555格式。
示例代碼
本文的示例代碼已上傳到github,可點擊這裡查看。
參考
UIImage Class Reference
iOS圖片拉伸技巧
iOS 處理圖片的一些小 Tip