本文由CocoaChina譯者 @唧唧歪歪 翻譯,作者:Hector Matos
原文:4 XCODE ASSET CATALOG SECRETS YOU NEED TO KNOW
惡夢
想象你正在干活,你的上級要求你在工程中加入一些圖片。你找到團隊中的設計師(如果你們有一個的話),鼓起勇氣,問他要這些圖片。不過,通常他都會忙得根本沒時間幫你。因為他要做的活兒比你還多。說實話,你到底見過一個設計師有多少活兒要做嗎?!簡直多到令人發指啊!所以你最不想做的就是成為那個再給他加活兒的人了。特別是那些活兒對設計師來說簡單得讓人厭煩。更不用說,設計師也要按順序干活,你拿到那些圖片也是幾天之後了。所以我們還是來看看如何用Xcode 的Asset Catalog來處理這些麻煩事吧。
麻煩事No.1:“能改一下這個圖片的顏色嗎?”
目前為止iOS已經提供了一些相當復雜的方法來處理工程中的圖片,但很不幸的是,卻沒有用來處理這種情況的方法。我已經數不清楚有多少次我們因為什麼便捷性支持或者是客戶不喜歡而改變工程的顏色了。現在在工程中全局改變UIColor已經不是什麼難題了,但是我們還要改變工程中圖片的顏色。這樣我們就不得不回去找已經忙得要死的設計師了,就因為我們太懶而不去學學怎麼用Photoshop。當然我們也可以寫代碼來完成,不過看起來相當復雜,而且還容易造成洩漏。在iOS7 之前,寫代碼改變圖片的顏色和下面差不多(還有很多其他的方法):
class func image(name: String, withColor color: UIColor) -> UIImage? { if var image = UIImage(named: name) { // begin a new image context, to draw our colored image onto. Passing in zero for scale tells the system to take from the current device's screen scale. UIGraphicsBeginImageContext(image.size, false, 0) // get a reference to that context we created let context = UIGraphicsGetCurrentContext() // set the context's fill color color.setFill() // translate/flip the graphics context (for transforming from CoreGraphics coordinates to default UI coordinates. The Y axis is flipped on regular coordinate systems) CGContextTranslateCTM(context, 0.0, image.size.height) CGContextScaleCTM(context, 1.0, -1.0) // set the blend mode to color burn so we can overlay our color to our image. CGContextSetBlendMode(context, kCGBlendModeColorBurn) let rect = CGRect(origin: CGPointZero, size: image.size) CGContextDrawImage(context, rect, image.CGImage) // set a mask that matches the rect of the image, then draw the color burned context path. CGContextClipToMask(context, rect, image.CGImage) CGContextAddRect(context, rect) CGContextDrawPath(context, kCGPathFill) // generate a new UIImage from the graphics context we've been drawing onto image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image } return nil }
在兩個大版本升級之後,我還能看到這樣的代碼。在iOS7中,我們有了imageWithRenderingMode,這是UIImage的一個方法,參數是有三個選項的枚舉值UIImageRenderingMode。
typedef NS_ENUM(NSInteger, UIImageRenderingMode) { UIImageRenderingModeAutomatic, // Use the default rendering mode for the context where the image is used UIImageRenderingModeAlwaysOriginal, // Always draw the original image, without treating it as a template UIImageRenderingModeAlwaysTemplate, // Always draw the image as a template image, ignoring its color information } NS_ENUM_AVAILABLE_IOS(7_0);
UIImageRenderingModeAlwaysOriginal 就和字面的意思一樣,這個模式告訴系統按照圖片文件原來的樣子渲染圖片。
UIImageRenderingModeAlwaysTemplate 這是最有意思的模式。首先會掃描你的圖片,然後從圖片中所有不透明的像素創建一個模板。這同時也會忽略圖片的所有顏色信息。你可以使用UIView子類的tintColor屬性來給圖片填充你選擇的顏色。
UIImageRenderingModeAutomatic 這個模式由系統根據圖片的使用環境來決定如何渲染圖片。如果你的圖片是用在比如UITabBar、UINavigationBar、UIToolbar 和UISegmentedControl這些地方,圖片使用AlwaysTemplate渲染模式。圖片用在其他的地方則會使用AlwaysOriginal渲染模式。
在了解了上面的內容之後,之前我們改變圖片顏色的代碼就可以簡化成下面這樣的了:
if var imageToChange = imageView.image?.imageWithRenderingMode(.AlwaysTemplate) { imageView.image = imageToChange imageView.tintColor = .redColor() //Setting the tint color is what changes the color of the image itself! }
是不是很神奇?用代碼改變圖片的顏色,現在變得簡單多了。
等等,還沒完呢!其實不需要代碼也可以改變圖片的顏色。
從Xcode 6開始,imageWithRenderingMode已經集成到Asset Catalog裡了。如果你在Asset Catalog裡選擇了一個圖片,在右邊的Attributes Inspector裡,就可以像下圖那樣把Render As選項改成Template Image。
就是這麼簡單。甚至你還可以通過在Storyboard中,在Attributes Pane中改變UIImageView的tintColor屬性,來改變imageView中的圖片的顏色。
麻煩事No.2:“能給我這個的3X分辨率的圖嗎?”
這個的確很煩,因為每個app的每個設計師被問這個問題都至少一年了。貌似蘋果每年都會增加一種新的屏幕分辨率,今年我也持懷疑態度。隨著硬件技術的發展,蘋果總是走在前沿,總是在盡可能地提高屏幕的ppi。很不幸,這意味著我們不能直接在“預覽”中放大已有的圖片,因為這會造成諸如圖片像素化和產生鋸齒等問題。通俗點,就是我們的圖片變丑了,噁!每次你叫設計師出一張已有圖片的3x分辨率圖,某個地方就又要死一只獨角獸了。這實際上也解釋了為什麼現在看不到這種神奇的生物了。
所以去年我在WWDC上提到的最好的消息,就是Xcode 6 及以上版本支持在Asset Catalog中使用矢量PDF了。你的設計師知道這是什麼意思,但是大致上,PDF是矢量元素的事實標准。矢量文件包含一個元素的很多元數據,用來告訴系統如何渲染這些內容,而這些和屏幕分辨率無關。舉個通俗易懂例子,一個圓形的矢量PDF圖,當它渲染成5像素寬和渲染成5000000像素寬時是一樣清晰的。
在iOS平台,Xcode是在編譯時,根據你的矢量PDF圖的大小,生成1x、2x和3x圖。如果你的PDF圖是45*45px,那麼Xcode會在編譯時生成下面3個PNG:
45*45px :1x設備用的(iPhone 3G and 3GS)
90*90px :2x或Retina顯示設備用的(iPhone 4, 4S, 5, 5S, and 6)
135*135px :3x設備用的(iPhone 6 Plus 及以上)
這也意味著當有更高的屏幕分辨率時,Xcode可以根據已有的矢量PDF放大圖片,這樣自動就支持以後的設備了。還有,如果你是OS X開發者,那麼矢量PDF就更好用了,OS X app完全支持矢量PDF,你可以用代碼縮放圖片而不會失真。
而你需要做的就是,找你的好基友設計師拿到這些矢量PDF文件,然後在Asset Catalog的Attribtues Pane中,在Scale Factor的下拉框中選擇Single Vector就行了。
你可以直接把PDF拖到Asset Catalog中,然後進行設置。
麻煩事No.3:“能給我新設備的啟動圖嗎?”
啟動圖對於app來說還是蠻重要的。這是啟動app後最先看到的,它會給用戶一個app其余部分是如何設計的第一印象。如果我看到一個設計得很糟糕的啟動圖,我會認為app其他地方也好不到哪去,當然這只是我的情況。對於我們那可憐的設計師來說,每次有新設備出來時,他們都知道要放大啟動圖來支持新設備的分辨率。對於iPhone 6 和 iPhone 6 Plus,如果你沒有為這兩個設備准備對應的啟動圖,那麼app就會工作在放大模式。啟動圖還在Asset Catalog中,但是我建議把它拆出來,因為啟動圖也升級了。現在,你可以使用LaunchScreen xibs。
在工程文件中,你可以指定app在啟動時加載的xib,這樣你就不需要准備9張啟動圖了。LaunchScreen.xib還支持自動布局,這樣我們就能分塊構建啟動屏幕了。按如下這樣設置:
首先創建一個xib文件。你可以在如下圖所示的地方選擇Launch Screen類型的xib。
然後打開工程文件,選擇app的target,在Launch Screen file處選擇你的Launch Screen .xib文件。
盡可能地利用Launch Screen吧。你肯定不想被抓到在問設計師要一年後新出的手機平板的8x啟動圖。
麻煩事No.4:“能把這些按鈕的圖片拉長一點嗎?”
這種情況發生的概率比你想象的要高得多。對於一張pattern image或者是有圓角的圖片,考慮到有更大的屏幕,你需要重新調整圖片的大小,以免圖片拉伸出現失真。Natasha發布了一篇很棒的文章來說明如何編程解決這個問題,但是我們也可以在Xcode 6的Asset Catalog中搞定它。順便說一下,我強烈建議你在繼續往下讀之前,看一下Natasha的文章,這樣你就能理解到底發生了什麼。免責聲明:下面的圖片等是直接從Natasha的文章中拷貝過來的。Sorry!
好了,我們繼續。
在之前,一般用類似下面的代碼來獲得可改變大小的圖片:
let edgeInsets = UIEdgeInsets(top: 8.0, left: 8.0, bottom: 8.0, right: 8.0) let backgroundButtonImage = UIImage(named:"purple_button")?.resizableImageWithCapInsets(edgeInsets) purpleButton.setBackgroundImage(backgroundButtonImage, forState: .Normal)
這將會得到一張和下面類似的圖片:
在運行時,會拉伸距離UIImageView的container的邊框8像素的中間部分,這樣就能保留圓角,得到下面這樣的:
多虧了Xcode中Asset Catalog的slice和dice,我們不需要代碼也能拉伸圖片。首先在Xcode中選中圖片,然後點擊右下角的Show Slicing:
你現在應該能看到slicing 面板和一個按鈕"Start Slicing"。
在你點擊按鈕之後,會顯示下面的三個選項:
左邊的按鈕用於horizontal edge insets,右邊的按鈕用於vertical edge insets,中間的則是兩個都有。在我們的例子中要保留圓角,所以我們按中間的按鈕,告訴系統我們想要按鈕的中間在水平和垂直方向拉伸。在按下按鈕之後,就能看到一些可以拖動的細條,這可以設置從哪裡開始拉伸圖片。
系統會保留深紫色的區域,淺紫色的區域會被拉伸。
更厲害的是,Xcode自動找到了圓角,所以我們不需要設置從哪裡開始拉伸圖片。最後別忘了在Attribtues pane中設置圖片是可拉伸的。
如果我是你的話,我就會嘗試並習慣這個功能。有了這個無價之寶,你就不用再在resizableImageWithCapInsets方法中填寫那些神奇的數字了,也能幫助你分離view邏輯和app邏輯。
結論
我很確定,我們開發者幾乎每天都還會做很多其他事去麻煩設計師們,但至少我們能多用用這些功能,讓他們稍微休息一會兒。畢竟編程能解決一切問題,何樂而不為呢?