本文是投稿文章,譯者:Andy矢倉
開發了App這麼久,是不是覺得主題功能很繁瑣,那麼接下來良辰我將動用北京的勢力來指導你如何為你的App增加主題功能。
原文:http://www.raywenderlich.com/108766/uiappearance-tutorial
作者:Essan Parto
主題教程:現在開始
雖然擬物化在iOS裡已是過去式,這並不意味著你的iOS應用控件就限於原始外觀。
雖然你可以從頭開始開發自己自定義控件,Apple官方還是建議您使用標准UIKit控件並且利用這各種自定義技術優勢。這是因為,UIKit的控制更高效,並且這種自定義控件是在給未來鋪路。
在本UIAppearance教程中,您將使用一些基本的UI自定義技術來定制一個普通的寵物搜索應用,並使其脫穎而出! :]
那就開始吧
本教程已經為你准備好了初始工程,該應用包含許多標准的UIKit控件,看起來非常絲滑。。。
打開項目,了解一下它的工程結構目錄。讓他跑起來,看看寵物搜索(Pet Finder)的主要用戶界面元素:
這裡我們利用了導航控欄(navigation bar)和標簽欄(tab bar),主屏幕的列表展示小寵物們;隨意點擊一個可以進入查看詳情。除了有一個搜索頁,你的應用也應該能有主題切換,難道不是嗎?聽起來就有一個非常良好的開端!
主題支持
許多應用都不允許用戶選擇主題,沒有主題的應用讓人看起來感覺怪怪的,挺煞筆,處女座有點不能忍。如果你對你應用的內容控制的不夠好,那麼你會發現內容與主題的沖突讓人看起來有多low逼。但是,您可能希望在開發過程中測試不同的主題來看看哪些跟你的應用最搭配,也許是雨天跟巧克力。或者給不同的用戶測試,看看哪種風格才能潮爆墨西哥。
在本UIAppearance教程中,我們將打造一批主題,來讓你的Application看起來有夠絲滑。
選擇File\New\File…並選中iOS\Source\Swift文件。點擊下一步,然後輸入Theme作為文件名。最後點擊下一步,然後創建。Xcode會自動打開新的文件,可以看到其中只包含一行代碼。
刪除並替換為下面的代碼:
import UIKit enum Theme: Int { case Default, Dark, Graphical var mainColor: UIColor { switch self { case .Default: return UIColor(red: 87.0/255.0, green: 188.0/255.0, blue: 95.0/255.0, alpha: 1.0) case .Dark: return UIColor(red: 242.0/255.0, green: 101.0/255.0, blue: 34.0/255.0, alpha: 1.0) case .Graphical: return UIColor(red: 10.0/255.0, green: 10.0/255.0, blue: 10.0/255.0, alpha: 1.0) } } }
給你的應用增加一個包含不同主題的enum。現在,所有的主題都通過mainColor具體到特定的主題。
接下來,添加下面的struct:
struct ThemeManager { }
馬上你的應用就會擁有主題了。雖然它現在沒代碼,接下來我們就把它撸進去!
接著,添加以下行enum聲明:
let SelectedThemeKey = "SelectedTheme"
現在,把下列方法加到ThemeManager裡:
static func currentTheme() -> Theme { if let storedTheme = NSUserDefaults.standardUserDefaults().valueForKey(SelectedThemeKey)?.integerValue { return Theme(rawValue: storedTheme)! } else { return .Default } }
這裡並沒有什麼過於復雜的東西:這個方法就是為了搞定你App風格,它使用NSUserDefaults來持久化當前主題數據,以便每次啟動的時候好使用它。
來測試一下看能不能行,打開AppDelegate.swift在application(_:didFinishLaunchingWithOptions):裡面加上
println(ThemeManager.currentTheme().mainColor)
Let’s it 跑起來,控制台會輸出一下信息:
UIDeviceRGBColorSpace 0.341176 0.737255 0.372549 1
這個時候你可以管理你擁有的三套主題來改變你的App了。
主題應用
返回到Theme.swift, 添加下列方法到ThemeManager:
static func applyTheme(theme: Theme) { // 1 NSUserDefaults.standardUserDefaults().setValue(theme.rawValue, forKey: SelectedThemeKey) NSUserDefaults.standardUserDefaults().synchronize() // 2 let sharedApplication = UIApplication.sharedApplication() sharedApplication.delegate?.window??.tintColor = theme.mainColor }
我們來快速浏覽下代碼:
使用NSUserDefaults來記錄你選擇的主題
接著獲取當前主題並把main color應用到整個應用窗口,接下來我們學習關於tintColor的更多知識。
現在,你需要做的唯一一件事就是調用此方法。沒有哪裡比在AppDelegate.swift裡這麼做更好了。
覆蓋掉println()並錄入下面的代碼:
let theme = ThemeManager.currentTheme() ThemeManager.applyTheme(theme)
讓我們跑起來,就會看到如下鳥樣:
放眼望去,滿臉綠!但是,你沒有改變任何的控制器或視圖。一秒變綠,真是神奇的一逼!
應用Tint Color
自從iOS7開始,UIView就開始暴露tintColor屬性,它通常被用來定義在整個應用中用於指示應用程序界面元素的選擇和交互的狀態的原始色。
當您為視圖指定的色彩時,色調會自動傳播到該視圖層次中的所有子視圖。因為UIWindow繼承自UIView的原因,你可以調用applyTheme()方法來設置窗口的tintColor,從而調整整個應用程序的色調。
點擊你的應用程序的左上角的齒輪圖標;用分段控制幻燈片表格視圖,但是當你選擇一個不同的主題,點擊應用,也並沒卵用。那麼接下來我們就來解決這個問題。
打開SettingsTableViewController.swift並把下列代碼添加到applyTheme()函數內,dismiss()上面:
if let selectedTheme = Theme(rawValue: themeSelector.selectedSegmentIndex) { ThemeManager.applyTheme(selectedTheme) }
這裡選擇的主題顏色設置到根視圖的tintColor屬性上調用的函數已經添加到了ThemeManager裡,
接著,在viewDidLoad()的底部添加下面這句,用於第一次加載視圖控制器時把主題數據寫入NSUserDefaults內:
themeSelector.selectedSegmentIndex = ThemeManager.currentTheme().rawValue
運行起來看看,選擇setting按鈕,再選擇Dark,最後點擊Apply,主題顏色就會從綠變藍:
眼尖的讀者可能已經注意到這些顏色早就定義在ThemeType的mainColor()裡了。
別急,你選擇了Dark,但這並沒有變暗。為了得到這種效果,你必須自定義更多的工作。
自定義導航欄
打開Theme.swift,並把下面兩個方法加到Theme裡:
var barStyle: UIBarStyle { switch self { case .Default, .Graphical: return .Default case .Dark: return .Black } } var navigationBackgroundImage: UIImage? { return self == .Graphical ? UIImage(named: "navBackground") : nil }
這些方法只會返回一個合適的bar的風格和主題所對應的背景圖像給導航欄。
把下面兩行添加到applyTheme()的底部:
UINavigationBar.appearance().barStyle = theme.barStyle UINavigationBar.appearance().setBackgroundImage(theme.navigationBackgroundImage, forBarMetrics: .Default)
好了 - 為什麼在這裡做這項工作,而不是更早的時候呢?
UIKit中有一個非正式的協議稱為UIAppearance控制大部分外觀。當你在UIKit上調用appearance() - 並非實例化 - 它返回一個UIAppearance代理類。當您更改此代理的屬性,該類的所有實例自動獲得相同的值。這是非常方便,因為您不必再需要手動去控制樣式。
跑起來,選擇黑暗的主題和導航欄現在應該更加黑暗:
這看起來好一點,但是還沒完。
接下來,將定制返回的箭頭。 iOS雖然默認采用了chevron效果,不過不要怕,良辰我會動用北京的勢力來幫你。。。
自定導航欄的返回圖標
繼續在Themes.swift的applyTheme()最後添加下面兩行,方便此更改適用於所有的主題:
UINavigationBar.appearance().backIndicatorImage = UIImage(named: "backArrow") UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "backArrowMask")
在這裡,我們簡單地設置圖像和過渡掩模圖像用作返回圖標。
跑起來,隨便點擊只寵物,你就可以看到新的返回圖標:
打開Images.xcassets並在導航組中找到backArrow圖像。該圖像是全黑的,不過沒事,他只是配合主題色。
如何才能只改變按鈕項的圖像顏色,在iOS裡有三種渲染模式:
Original:務必使用圖像“原樣”,和原來的顏色。
Template:忽略顏色,只需使用圖像作為模板。在這種模式下,iOS使用僅圖像的形狀,因此只會通過主題顏色和您提供的圖像形狀在屏幕進行渲染。
Automatic:這取決於你使用的圖像環境,再由系統決定來該使用original還是template模式。對於返回圖標,導航控制器按鈕項和標簽選擇項,iOS會默認忽略圖像的顏色,除非你改變渲染模式。
回到應用程序,隨便點擊個寵物,然後點擊Adopt。仔細觀察導航欄中返回按鈕的動畫。你能看到這個問題?
當返回文本轉換到左邊,箭頭重疊了,這樣看起來很糟糕:
為了解決這個問題,你必須改變的過渡掩模圖像。
在applyTheme()裡更新backIndicatorTransitionMaskImage設置:
UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "backArrow")
再次運行,點擊Adopt,這樣是不是看起來更好了呢:
這樣文本和圖標就不會再轉換重疊,那這裡面到底發生了什麼呢?
iOS使用非轉換像素經行返回圖標繪制時,它與過渡掩模圖像完全不同:它掩蓋與過渡屏蔽圖像的非透明像素的箭頭,使得當文本向左移動,箭頭僅在這些區域中可見。
在最開始實現時,要提供背面箭頭圖像覆蓋在整個表面上,以便在文本過渡時仍然可見圖像。但現在你正在使用的箭頭圖像本身,文本消失在最右邊,而不是在其下面。
在Images.xcassets裡看看這個箭頭圖像修復版本,你將看到完美覆蓋:
讓黑色部分顯示,紅色部分隱藏起來。
再次更新applyTheme()函數最後的代碼:
UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "backArrowMaskFixed")
再次運行,再點擊Adopt返回的時候,看起來是不是好多了,沒有重疊也沒有占位的效果:
現在你的導航欄就完美了,那麼接下來就需要把精力放Tabbar上了。
自定義Tabbar
還是在Theme.swift,在Theme裡添加如下屬性:
var tabBarBackgroundImage: UIImage? { return self == .Graphical ? UIImage(named: "tabBarBackground") : nil } var backgroundColor: UIColor { switch self { case .Default, .Graphical: return UIColor(white: 0.9, alpha: 1.0) case .Dark: return UIColor(white: 0.8, alpha: 1.0) } } var secondaryColor: UIColor { switch self { case .Default: return UIColor(red: 242.0/255.0, green: 101.0/255.0, blue: 34.0/255.0, alpha: 1.0) case .Dark: return UIColor(red: 34.0/255.0, green: 128.0/255.0, blue: 66.0/255.0, alpha: 1.0) case .Graphical: return UIColor(red: 140.0/255.0, green: 50.0/255.0, blue: 48.0/255.0, alpha: 1.0) } }
這些屬性提供了相應的標簽欄背景圖片,背景顏色,和每個主題對應的二級顏色。
在applyTheme()裡添加如下代碼,就可以統一風格了:
UITabBar.appearance().barStyle = theme.barStyle UITabBar.appearance().backgroundImage = theme.tabBarBackgroundImage let tabIndicator = UIImage(named: "tabBarSelectionIndicator")?.imageWithRenderingMode(.AlwaysTemplate) let tabResizableIndicator = tabIndicator?.resizableImageWithCapInsets( UIEdgeInsets(top: 0, left: 2.0, bottom: 0, right: 2.0)) UITabBar.appearance().selectionIndicatorImage = tabResizableIndicator
前面看了那麼就,設置barStyle和backgroundImage應該不用在熬訴了吧;做法就和設置UINavigationBar類似。
最後三行代碼的意思就是,加載一個指示器圖像,並以.AlwaysTemplate設置其渲染模式。這個例子iOS並沒有自動使用模板渲染模式。
最後,創建一個可調整大小的圖像,並將其設置為標簽欄的selectionIndicatorImage。
跑起來。你會看到你的新主題的標簽欄:
暗黑主題看起來是不是好多了! :]
看看Tabbar最下面的橫線?這是你的指示器圖像。雖然它的高只有6點和49點寬像素,iOS會幫你自動延生。
接下來我們討論調整圖像大小,以及它們如何工作。
自定義Segmented Control
現在唯一沒發生變化的就是Segmented Control了,接著我們就來搞定它。
打開Theme.swift在applyTheme()底部添加如下代碼:
let controlBackground = UIImage(named: "controlBackground")?.imageWithRenderingMode(.AlwaysTemplate).resizableImageWithCapInsets(UIEdgeInsets(top: 3, left: 3, bottom: 3, right: 3)) let controlSelectedBackground = UIImage(named: "controlSelectedBackground")?.imageWithRenderingMode(.AlwaysTemplate).resizableImageWithCapInsets(UIEdgeInsets(top: 3, left: 3, bottom: 3, right: 3)) UISegmentedControl.appearance().setBackgroundImage(controlBackground, forState: .Normal, barMetrics: .Default) UISegmentedControl.appearance().setBackgroundImage(controlSelectedBackground, forState: .Selected, barMetrics: .Default)
要理解上面的代碼,先來看看資源目錄裡的controlBackground圖像。該圖像可能比較小,但iOS能明確地知道如何使用它來繪制UISegmentedControl的邊界,因為它是被預先切分好來調整大小。
什麼是sliced?看看下面的放大模型:
有四個3×3的正方形,一個在每個角落。這些正方形在調整圖像大小時原封不動,但水平和垂直方向按要求的灰色像素被拉伸。
在你的素材當中,所有像素為黑色,並承擔著你控件的著色。你通過使用UIEdgeInsets()來指導iOS如何從上下左右四個方向拉伸圖像。
運行一盤。點擊Gear圖標,你會看到UISegmentedControl邊框有了新的造型:
圓角消失了,被你3x3像素的邊角取締了。
現在你已經有了獨特風格的分段控件,接著我們就來搞定剩下的。
關閉設置屏幕的應用,點擊右上角的放大鏡圖標;你會看到另一個已經被你自定義好的分段控件,但是UIStepper,UISlider和UISwitch仍需要添加主題。
接下來我們就來畫畫吧! :]
自定義Steppers, Sliders和Switches
打開Theme.swift在applyTheme()底部添加如下代碼:
UIStepper.appearance().setBackgroundImage(controlBackground, forState: .Normal) UIStepper.appearance().setBackgroundImage(controlBackground, forState: .Disabled) UIStepper.appearance().setBackgroundImage(controlBackground, forState: .Highlighted) UIStepper.appearance().setDecrementImage(UIImage(named: "fewerPaws"), forState: .Normal) UIStepper.appearance().setIncrementImage(UIImage(named: "morePaws"), forState: .Normal)
這裡我們用和UISegmentedControl一樣的邊框來構建UIStepper邊框,並且使用素材來自定義它,這樣就展示更形象而不是每次都看到一成不變的+``-按鈕了。
運行。打開搜索,看看到底發生了變化:
接著繼續搞定UISlider和UISwitch。
還是在applyTheme()最後添加如下代碼:
UISlider.appearance().setThumbImage(UIImage(named: "sliderThumb"), forState: .Normal) UISlider.appearance().setMaximumTrackImage(UIImage(named: "maximumTrack")?.resizableImageWithCapInsets(UIEdgeInsets(top: 0, left: 0.0, bottom: 0, right: 6.0)), forState: .Normal) UISlider.appearance().setMinimumTrackImage(UIImage(named: "minimumTrack")?.imageWithRenderingMode(.AlwaysTemplate).resizableImageWithCapInsets(UIEdgeInsets(top: 0, left: 6.0, bottom: 0, right: 0)), forState: .Normal) UISwitch.appearance().onTintColor = theme.mainColor.colorWithAlphaComponent(0.3) UISwitch.appearance().thumbTintColor = theme.mainColor
UISlider有三個需要定制的地方:滑塊圖標,最小指示點的軌跡和最大指示點的軌跡。
滑塊圖標直接使用項目內的素材,最大指示點的軌跡因為不需要改變顏色,所以使用原來的渲染模式。而最小的則需要制定Template渲染模式,以便跟主題色一致。
UISwitch則是通過你修改thumbTintColor和onTintColor這兩個屬性來定制。
運行。點擊搜索,看看滑塊與開關:
正如你看到的UISegmentedControl一樣,appearance控制著所有實例。但是某些時候你只是想控制其中某一個 - 在這種情況下,你可以單獨自定義。
自定義單一實例
打開SearchTableViewController.swift,在viewDidLoad()內添加如下代碼:
speciesSelector.setImage(UIImage(named: "dog"), forSegmentAtIndex: 0) speciesSelector.setImage(UIImage(named: "cat"), forSegmentAtIndex: 1)
在這裡,我們只是簡單地設置分段控件的每個分段圖像。
運行。點擊搜索,分段控件就會如下所示:
iOS會自動為分段控件著色,而不需要您額外的做任何事;因為這是Template模式下自動搞定的。
怎麼樣選擇性地改變你控件的字體?這很容易辦到。
打開PetTableViewController.swift並在viewWillAppear()內添加如下代碼:
view.backgroundColor = ThemeManager.currentTheme().backgroundColor tableView.separatorColor = ThemeManager.currentTheme().secondaryColor
接著在tableView(_:cellForRowAtIndexPath:)方法內,return之前加上下面這句:
cell.textLabel!.font = UIFont(name: "Zapfino", size: 14.0)
這裡我們只改變了寵物名字的字體。
運行看一下效果:
我們再來看看前後對比,素不素超級贊:
何去何從
教程就講到這裡,你也可以下載完成的項目。
在Objective-C裡,你可以指定特定的自定義設置應用到只有當他們包含在特定類的其他控件裡。例如,您可以將自定義的UITextField放到UINavigationBar裡。
但悲劇的是,swift不能這麼干。不過有個好消息是,iOS9將添加此功能,請期待稍後更新本教程。