
開發(fā)了App這么久,是不是覺得主題功能很繁瑣规个,那么接下來良辰我將動用北京的勢力來指導你如何為你的App增加主題功能。
原文:http://www.raywenderlich.com/108766/uiappearance-tutorial
轉(zhuǎn)載請注明出處:http://www.rockerhx.com/2015/09/07/2015-09-07-UIAppearanceTutorial/
主題教程:現(xiàn)在開始
雖然擬物化在iOS里已是過去式烧董,這并不意味著你的iOS應用控件就限于原始外觀督暂。
雖然你可以從頭開始開發(fā)自己自定義控件,Apple官方還是建議您使用標準UIKit控件并且利用這各種自定義技術優(yōu)勢皇拣。這是因為严蓖,UIKit的控制更高效,并且這種自定義控件是在給未來鋪路氧急。
在本UIAppearance教程中颗胡,您將使用一些基本的UI自定義技術來定制一個普通的寵物搜索應用,并使其脫穎而出态蒂! :]
那就開始吧
本教程已經(jīng)為你準備好了初始工程杭措,該應用包含許多標準的UIKit控件费什,看起來非常絲滑钾恢。手素。。
打開項目瘩蚪,了解一下它的工程結(jié)構目錄泉懦。讓他跑起來,看看寵物搜索的主要用戶界面元素:

這里我們利用了導航控欄(navigation bar)和標簽欄(tab bar)疹瘦,主屏幕的列表展示小寵物們崩哩;隨意點擊一個可以進入查看詳情。除了有一個搜索頁言沐,你的應用也應該能有主題切換邓嘹,難道不是嗎?聽起來就有一個非常良好的開端险胰!
主題支持
許多應用都不允許用戶選擇主題汹押,沒有主題的應用讓人看起來感覺怪怪的,挺煞筆起便,處女座有點不能忍棚贾。如果你對你應用的內(nèi)容控制的不夠好,那么你會發(fā)現(xiàn)內(nèi)容與主題的沖突讓人看起來有多l(xiāng)ow逼榆综。但是妙痹,您可能希望在開發(fā)過程中測試不同的主題來看看哪些跟你的應用最搭配,也許是雨天跟巧克力鼻疮∏右粒或者給不同的用戶測試,看看哪種風格才能潮爆墨西哥判沟。
在本UIAppearance教程中震贵,我們將打造一批主題,來讓你的Application看起來有夠絲滑水评。
選擇File\New\File…
并選中iOS\Source\Swift
文件猩系。點擊下一步,然后輸入Theme
作為文件名中燥。最后點擊下一步寇甸,然后創(chuàng)建。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
。現(xiàn)在咱扣,所有的主題都通過mainColor具體到特定的主題绽淘。
接下來,添加下面的struct
:
struct ThemeManager {
}
馬山你的應用就會擁有主題了闹伪。雖然它現(xiàn)在沒代碼沪铭,接下來我們就把它擼進去壮池!
接著,添加以下行enum
聲明:
let SelectedThemeKey = "SelectedTheme"
現(xiàn)在杀怠,把下列方法加到ThemeManager
里:
static func currentTheme() -> Theme {
if let storedTheme = NSUserDefaults.standardUserDefaults().valueForKey(SelectedThemeKey)?.integerValue {
return Theme(rawValue: storedTheme)!
} else {
return .Default
}
}
這里并沒有什么過于復雜的東西:這個方法就是為了搞定你App風格椰憋,它使用NSUserDefaults
來持久化當前主題數(shù)據(jù),以便每次啟動的時候好使用它赔退。
來測試一下看能不能行橙依,打開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
的更多知識漆枚。
現(xiàn)在慧域,你需要做的唯一一件事就是調(diào)用此方法。沒有哪里比在AppDelegate.swift
里這么做更好了浪读。
覆蓋掉println()
并錄入下面的代碼:
let theme = ThemeManager.currentTheme()
ThemeManager.applyTheme(theme)
讓我們跑起來昔榴,就會看到如下鳥樣:

放眼望去,滿臉綠碘橘!但是互订,你沒有改變?nèi)魏蔚目刂破骰蛞晥D。一秒變綠痘拆,真是神奇的一逼仰禽!
應用Tint Color
自從iOS7
開始,UIView
就開始暴露tintColor
屬性纺蛆,它通常被用來定義在整個應用中用于指示應用程序界面元素的選擇和交互的狀態(tài)的原始色吐葵。
當您為視圖指定的色彩時,色調(diào)會自動傳播到該視圖層次中的所有子視圖桥氏。因為UIWindow
繼承自UIView
的原因温峭,你可以調(diào)用applyTheme()
方法來設置窗口的tintColor
,從而調(diào)整整個應用程序的色調(diào)字支。
點擊你的應用程序的左上角的齒輪圖標凤藏;用分段控制幻燈片表格視圖,但是當你選擇一個不同的主題堕伪,點擊應用揖庄,也并沒卵用。那么接下來我們就來解決這個問題欠雌。
打開SettingsTableViewController.swift
并把下列代碼添加到applyTheme()
函數(shù)內(nèi)蹄梢,dismiss()
上面:
if let selectedTheme = Theme(rawValue: themeSelector.selectedSegmentIndex) {
ThemeManager.applyTheme(selectedTheme)
}
這里選擇的主題顏色設置到根視圖
的tintColor
屬性上調(diào)用的函數(shù)已經(jīng)添加到了ThemeManager
里,
接著富俄,在viewDidLoad()
的底部添加下面這句禁炒,用于第一次加載視圖控制器時把主題數(shù)據(jù)寫入NSUserDefaults
內(nèi):
themeSelector.selectedSegmentIndex = ThemeManager.currentTheme().rawValue
運行起來看看而咆,選擇setting
按鈕,再選擇Dark
齐苛,最后點擊Apply
翘盖,主題顏色就會從綠變藍:

眼尖的讀者可能已經(jīng)注意到這些顏色早就定義在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
中有一個非正式的協(xié)議稱為UIAppearance
控制大部分外觀对省。當你在UIKit
上調(diào)用appearance()
- 并非實例化 - 它返回一個UIAppearance
代理類。當您更改此代理的屬性晾捏,該類的所有實例自動獲得相同的值蒿涎。這是非常方便,因為您不必再需要手動去控制樣式惦辛。
跑起來劳秋,選擇黑暗的主題和導航欄現(xiàn)在應該更加黑暗:

這看起來好一點,但是還沒完胖齐。
接下來玻淑,將定制返回的箭頭。 iOS雖然默認采用了chevron
效果呀伙,不過不要怕补履,良辰我會動用北京的勢力來幫你。剿另。干像。
自定導航欄的返回圖標
繼續(xù)在Themes.swift
的applyTheme()
最后添加下面兩行,方便此更改適用于所有的主題:
UINavigationBar.appearance().backIndicatorImage = UIImage(named: "backArrow")
UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "backArrowMask")
在這里驰弄,我們簡單地設置圖像和過渡掩模圖像用作返回圖標麻汰。
跑起來,隨便點擊只寵物戚篙,你就可以看到新的返回圖標:

打開Images.xcassets
并在導航組中找到backArrow
圖像五鲫。該圖像是全黑的,不過沒事岔擂,他只是配合主題色位喂。

如何才能只改變按鈕項的圖像顏色浪耘,在iOS里有三種渲染模式:
Original:務必使用圖像“原樣”,和原來的顏色塑崖。
Template:忽略顏色七冲,只需使用圖像作為模板。在這種模式下规婆,iOS使用僅圖像的形狀澜躺,因此只會通過主題顏色和您提供的圖像形狀在屏幕進行渲染。
Automatic:這取決于你使用的圖像環(huán)境抒蚜,再由系統(tǒng)決定來該使用original
還是template
模式掘鄙。對于返回圖標,導航控制器按鈕項和標簽選擇項嗡髓,iOS會默認忽略圖像的顏色操漠,除非你改變渲染模式。
回到應用程序饿这,隨便點擊個寵物浊伙,然后點擊Adopt
。仔細觀察導航欄中返回按鈕的動畫长捧。你能看到這個問題嚣鄙?

當返回文本轉(zhuǎn)換到左邊,箭頭重疊了唆姐,這樣看起來很糟糕:
為了解決這個問題拗慨,你必須改變的過渡掩模圖像。
在applyTheme()
里更新backIndicatorTransitionMaskImage
設置:
UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "backArrow")
再次運行奉芦,點擊Adopt
赵抢,這樣是不是看起來更好了呢:

這樣文本和圖標就不會再轉(zhuǎn)換重疊,那這里面到底發(fā)生了什么呢声功?
iOS使用非轉(zhuǎn)換像素經(jīng)行返回圖標繪制時烦却,它與過渡掩模圖像完全不同:它掩蓋與過渡屏蔽圖像的非透明像素的箭頭,使得當文本向左移動先巴,箭頭僅在這些區(qū)域中可見其爵。
在最開始實現(xiàn)時,要提供背面箭頭圖像覆蓋在整個表面上伸蚯,以便在文本過渡時仍然可見圖像摩渺。但現(xiàn)在你正在使用的箭頭圖像本身,文本消失在最右邊剂邮,而不是在其下面摇幻。
在Images.xcassets
里看看這個箭頭圖像修復版本,你將看到完美覆蓋:

讓黑色部分顯示,紅色部分隱藏起來绰姻。
再次更新applyTheme()
函數(shù)最后的代碼:
UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "backArrowMaskFixed")
再次運行枉侧,再點擊Adopt
返回的時候,看起來是不是好多了狂芋,沒有重疊也沒有占位的效果:

現(xiàn)在你的導航欄就完美了榨馁,那么接下來就需要把精力放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()
里添加如下代碼蛙讥,就可以統(tǒng)一風格了:
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并沒有自動使用模板渲染模式劈愚。
最后瞳遍,創(chuàng)建一個可調(diào)整大小的圖像,并將其設置為標簽欄的selectionIndicatorImage
菌羽。
跑起來掠械。你會看到你的新主題的標簽欄:

暗黑主題看起來是不是好多了! :]
看看Tabbar
最下面的橫線注祖?這是你的指示器圖像猾蒂。雖然它的高只有6點和49點寬像素,iOS會幫你自動延生是晨。
接下來我們討論調(diào)整圖像大小肚菠,以及它們?nèi)绾喂ぷ鳌?/p>
自定義Segmented Control
現(xiàn)在唯一沒發(fā)生變化的就是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
的邊界烙荷,因為它是被預先切分好來調(diào)整大小。
什么是sliced
檬寂?看看下面的放大模型:

有四個3×3
的正方形终抽,一個在每個角落。這些正方形在調(diào)整圖像大小時原封不動,但水平和垂直方向按要求的灰色像素被拉伸拿诸。
在你的素材當中扒袖,所有像素為黑色,并承擔著你控件的著色亩码。你通過使用UIEdgeInsets()
來指導iOS如何從上下左右四個方向拉伸圖像季率。
運行一盤。點擊Gear
圖標描沟,你會看到UISegmentedControl
邊框有了新的造型:

圓角消失了飒泻,被你3x3
像素的邊角取締了。
現(xiàn)在你已經(jīng)有了獨特風格的分段控件吏廉,接著我們就來搞定剩下的泞遗。
關閉設置屏幕的應用,點擊右上角的放大鏡圖標席覆;你會看到另一個已經(jīng)被你自定義好的分段控件史辙,但是UIStepper
,UISlider
和UISwitch
仍需要添加主題佩伤。
接下來我們就來畫畫吧聊倔! :]
自定義Steppers, Sliders, and 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
邊框,并且使用素材來自定義它生巡,這樣就展示更形象而不是每次都看到一成不變的+``-
按鈕了耙蔑。
運行。打開搜索孤荣,看看到底發(fā)生了變化:

接著繼續(xù)搞定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
有三個需要定制的地方:滑塊圖標,最小指示點的軌跡和最大指示點的軌跡盐股。
滑塊圖標直接使用項目內(nèi)的素材钱豁,最大指示點的軌跡因為不需要改變顏色,所以使用原來的渲染模式遂庄。而最小的則需要制定Template
渲染模式寥院,以便跟主題色一致。
UISwitch
則是通過你修改thumbTintColor
和onTintColor
這兩個屬性來定制涛目。
運行秸谢。點擊搜索,看看滑塊與開關:

正如你看到的UISegmentedControl
一樣霹肝,appearance
控制著所有實例估蹄。但是某些時候你只是想控制其中某一個 - 在這種情況下,你可以單獨自定義沫换。
自定義單一實例
打開SearchTableViewController.swift
臭蚁,在viewDidLoad()
內(nèi)添加如下代碼:
speciesSelector.setImage(UIImage(named: "dog"), forSegmentAtIndex: 0)
speciesSelector.setImage(UIImage(named: "cat"), forSegmentAtIndex: 1)
在這里,我們只是簡單地設置分段控件的每個分段圖像。
運行垮兑。點擊搜索冷尉,分段控件就會如下所示:

iOS會自動為分段控件著色,而不需要您額外的做任何事系枪;因為這是Template
模式下自動搞定的雀哨。
怎么樣選擇性地改變你控件的字體候生?這很容易辦到脆诉。
打開PetTableViewController.swift
并在viewWillAppear()
內(nèi)添加如下代碼:
view.backgroundColor = ThemeManager.currentTheme().backgroundColor
tableView.separatorColor = ThemeManager.currentTheme().secondaryColor
接著在tableView(_:cellForRowAtIndexPath:)
方法內(nèi),return之前加上下面這句:
cell.textLabel!.font = UIFont(name: "Zapfino", size: 14.0)
這里我們只改變了寵物名字的字體逝慧。
運行看一下效果:

我們再來看看前后對比衬浑,素不素超級贊:

何去何從
教程就講到這里捌浩,你也可以下載完成的項目。
在Objective-C
里工秩,你可以指定特定的自定義設置應用到只有當他們包含在特定類的其他控件里尸饺。例如,您可以將自定義的UITextField
放到UINavigationBar
里拓诸。
但悲劇的是侵佃,swift
不能這么干麻昼。不過有個好消息是奠支,iOS9將添加此功能,請期待稍后更新本教程抚芦。
希望你喜歡這篇UIAppearance教程
倍谜,并學會了如何輕松調(diào)整你的UI。如果您有任何關于本教程的意見或問題叉抡,請加入論壇討論下面尔崔!