UIAppearance入门教程
Didn't you wish you could customize your pets? 想知道怎么样自定义你的宠物界面吗?译者注:这篇文章是使用UIAppearance定义UI界面的入门教程,介绍了一些具体的控件的自定义方法。
虽然iOS的拟物设计已经成为了过去式,但是这并不代表你的iOS App的控件的外观被限制在系统自有的那几个。 当然,你可以重新定义自己的控件和App的外观,在这个时候,Apple推荐开发者使用标准的UIKit控件并利用iOS提供的各种自定义的方法来达到你的效果。这样做的原因是因为UIKit控件运行效率非常高, 另外你的这些自定义的控件也会在未来的版本中表现更好的兼容性。 在这个UIAppearance的教程中,你会用一些基本的UI自定义技术美化一款寻找宠物的App使它由平常变得与众不同。
那么开始
点击这里 下载本教程的所用到的源码。这个App使用了很多标准UIKit控件,配色是默认的vanilla。 打开这个项目,然后看看它的结构,编译运行后,宠物查找器的主界面是这样子的:
plain-600x500 这儿有一个导航栏和一个标签栏。主界面显示了宠物列表;点击一个宠物会显示他的详情。同时这里有一个查找界面-啊哈!一个可以让用户选择主题的界面好像是个不错的主意,我们可以从这里开始。支持主题
很多Apps并不能让用户选择主题,在大多数时候也并不推荐开发者去发布一个具有主题选择功能的App。如果你的app显示得内容控制不好的话,你会很快发现你的App中的某些主题已经生成了并分析了某些东西,然而另外一个主题却与之冲突。然而,你也许在开发阶段会想对不同 的主题进行测试来看看哪个更适合你的App,或者做一个A/B 测试来看Beta版本的用户更喜欢哪种主题。 在这个UIAppearance 教程中,你会创建几个不同的主题,然后可以看看哪个更加符合你的审美。 选择 File\New\File…然后选择 _iOS\Source\Swift File。 _点击Next然后将文件命名为Theme。最后点击Next并点击Create,Xcode会自动打开你创建的新文件,这个文件里只有一行代码。 删除这行代码并且用如下代码替换:
<pre class="">import UIKit
enum Theme {
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)
}
}
}</pre>
这段代码为你的App添加了一个包含不同的主题的枚举值。从现在起,所有的主题只有特定一个mainColor
然后,添加下面的struct:
<pre class="">struct ThemeManager {
}</pre>
这段代码让你可以在App中使用主题。它暂时是空的,但是马上会对他进行修改。 然后,将下面这行代码添加到enum
的声明前。
<pre>let SelectedThemeKey = "SelectedTheme"</pre>
现在,给下面ThemeManager
添加如下方法:
<pre >static func currentTheme() -> Theme {
if let storedTheme = NSUserDefaults.standardUserDefaults().valueForKey(SelectedThemeKey)?.integerValue {
return Theme(rawValue: storedTheme)!
} else {
return .Default
}
}</pre>
这儿也没有太过复杂的代码:这是你将用到的控制app的样式的主方法。它使用NSUserDefaults
来持久化存储你的当前主题,每次启动App时它都会被读取。 要测试这个是否可用,可以打开AppDelegate.swift然后将下面代码添加到 application(_:didFinishLaunchingWithOptions)
:
<pre>println(ThemeManager.currentTheme().mainColor)</pre>
Build and run。你应该会在控制台看到下面的输出结果:
<pre>UIDeviceRGBColorSpace 0.94902 0.396078 0.133333 1</pre>
那么从现在起,你已经有三个主题,并且你可以通过ThemeManager
对他们进行管理。现在是时候在App中使用他们了。
将主题添加入控件
回到Theme.swift,添加下面的方法到ThemeManager
:
<pre>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
}</pre>
这里我们过一遍上面的代码:
- 先使用NSUserDefaults持久化存储选中的主题
- 然后获取你选择的主题并且将主题的main color赋给 application's window的
tintColor
属性。关于tintColor稍后会详细讲解
然后你需要做的就是调用这个方法,在AppDelegate.swift中调用那必须是极好的。 将之前加的 println()
语句替换成下面的代码:
<pre>let theme = ThemeManager.currentTheme()
ThemeManager.applyTheme(theme)</pre>
Build and run。你会发现你的App明显看起来偏绿色调多了:
theme_applied1 在App中到处点点;会发现各种地方都已经是这种颜色了。但是你并没有在controller或者view上面做任何修改,这个黑 — 额,绿魔法到底是什么呢?使用 Tint Colors
从iOS7开始,UIView
就已经暴露了tintColor
属性,一般这个属性被用在app的主色调选择器和某些可交互的界面元素的交互状态。 如果你为某个view指定一个tint,那么这个tint将会自动应用到这个view的所有的子view中,因为UIWindow继承于UIView,你可以通过设定window的tintColor来定义整个app的色彩。 点击左上角的齿轮图标;一个有分段控制的 table view将会显示出来,但是当你选择不同的主题并提交时,没有东西会发生改变,是时候修改它了。 打开SettingsTableViewController.swift 然后添加下面的代码到 applyTheme()
,就在dismiss()
方法上面;
<pre>if let selectedTheme = Theme(rawValue: themeSelector.selectedSegmentIndex) {
ThemeManager.applyTheme(selectedTheme)
}</pre>
然后你就可以调用你在 ThemeManager
中加入的方法了,这个方法会通过设置UIWindow的tintColor属性来设定主题的主色调。 下一步,添加下面这些代码到viewDidLoad()方法的底部,这样做了之后,选中的主题会在view controller第一次加载时就被存储到 NSUserDefaults
中
<pre>themeSelector.selectedSegmentIndex = ThemeManager.currentTheme().rawValue</pre>
Build and run.点击设置按键,选择Dark主题然后提交修改。App的主色调将瞬间会从绿色变为橙色。
theme_applied2 眼尖的读者可能会注意到这些颜色是在ThemeType
中就定义了这些颜色。 但是,当你选择了Dark主题,但是App的颜色看起来并不是特别的深。想要主题生效,你需要自定义一些更多的东西。
Customizing the Navigation Bar 自定义导航栏
打开 Theme.swift 然后添加下面的两个方法到Theme中:
<pre>var barStyle: UIBarStyle {
switch self {
case .Default, .Graphical:
return .Default
case .Dark:
return .Black
}
}
var navigationBackgroundImage: UIImage? {
return self == .Graphical ? UIImage(named: "navBackground") : nil
}</pre>
这些方法简单的为导航栏获取了获取了主题对应的bar style和背景图 然后,将下面语句添加到applyTheme()
:
<pre>UINavigationBar.appearance().barStyle = theme.barStyle
UINavigationBar.appearance().setBackgroundImage(theme.navigationBackgroundImage, forBarMetrics: .Default)</pre>
好了——为什么这些现在生效了,但是之前设置UINavigationBar的barStyle却没有呢? UIKit有一个通知机制叫UIAppearance,大部分的控件都使用了它。当你调用通过UIKit的类(非实例)调用appearance()
,它就会返回一个UIAppearance代理。当你修改了这个代理的属性,所有的这个类的实例都会被赋予相同的值。这个机制就使得你无须在修改主题时手动修改所有控件的样式。 Build and run,选择Dark主题,导航栏现在的颜色就深多了。
自定义后退按钮
这个修改会对所有主题生效,所有你只需要将下面的几行加入到Themes.swift 的applyTheme()方法中:
<pre>UINavigationBar.appearance().backIndicatorImage = UIImage(named: "backArrow")
UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "backArrowMask")</pre>
在这里你设置了后退按钮的图片和遮罩图。 Build and run.选择一个宠物你会看见新的后退按钮:
back_button 打开Images.xcassets 然后在Navigation组里面找到backArrow的图片,这个图片是纯黑色的,但是在App里面他会采取你的window的色板颜色然后并使之生效(it just works)。 just_works 但是为什么iOS只是修改了工具栏按钮的图片颜色,为什么其他地方它不这么做呢? 原来,iOS中得图像有3种渲染方式- Original:总是使用图片的原来的颜色。
- Template:忽略颜色,只是把图片看做一个模板。在这个模式下,iOS只用到图片的形状,然后自己渲染图片的颜色。所以当一个控件有tint color时,iOS取了你提供的图片的形状,然后用tint color绘制他了。
- Automatic:看你在什么环境下使用图片,系统会自己决定使用“original”或者“template”方式。对后退按钮,导航栏上的按钮和标签栏上的按钮,iOS会默认的忽略他们的图片颜色除非你修改了他们的渲染模式。
回到App,点击一个宠物并点击Adopt进行领养。仔细看这个后退按钮的动画。有看出问题吗?
mask1 当Back文件往左移动时,文字和按钮的重叠看起来非常怪: 为了改正这个,你需要修改遮罩图: 在_Themes.swift 中的_applyTheme()方法中,修改设置backIndicatorTransitionMaskImage的代码为以下这一行:<pre>UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "backArrow")</pre>
Build and run. 重新点击宠物并点击Adopt进行领养。这次效果看起来好多了。
mask2 这些文字没有后退标切断,看起来就像藏在了后退按钮下一样。这中间到底发生了什么呢? 当iOS使用非透明后退图片绘制图标,如果添加了过渡遮罩层,它将给非透明的后退图标添加一个蒙版层。这个图标也只在那个区域可见。 在最开始的实现方法中,你提供了一个覆盖整个后退按钮的图片,那样的话文字将会一致可见。现在你将图片设置成了蒙版,然后文件将在蒙版的右侧就不见了,而不是在重叠在图标下方显示。 看看这个箭头图和它在图片assets目录下得原始蒙版图;你就会发现他们配合得非常棒。 indicator_mask 黑色的图形是后退箭头,红色的是蒙版。你会希望文字在红色区域下面能够显示而在该图标的其他地方隐藏。 修改applyTheme的最后一行,并使用这个修改过的蒙版:<pre>UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "backArrowMaskFixed")</pre>
Build and run. 最后一次点击宠物并点击Adopt进行领养,你会发现文字在蒙版层才显示,达到了想要的效果。
mask3 现在你的导航栏已经是像素级别的效果了,现在是时候给标签栏一点点爱了(- -!)Customizing the Tab Bar自定义标签栏
还是在Theme.swift,添加下面的属性到Theme中:
<pre>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)
}
}</pre>
这些属性提供了与主题相配的背景图,背景色和次要的颜色。 要应用这些样式,添加项目的这几行代码到applyTheme()
.
<pre class="">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</pre>
设置barStyle和backgroundImage大家应该很熟悉了;这和之前在UINavigationBar所做的是一样的。 在最后三行代码中,你使用了asset目录下得一个图片作为标签图片然后设置他的渲染模式为.AlwaysTemplate
。这是一个iOS没有自动使用template rendering mode的例子。 最后,你创建了一个可以拉伸的图片,并且设置它为标签栏的selectionIndicatorImage Build and run. 你会看见最新带主题的标签栏:
自定义分段控件
还有一个组件没有被修改就是选中主题的分段控件还没有被修改。是时候将它也带入精彩的主题世界了。 添加下面的代码到Theme.swift的applyTheme()方法的底部:
<pre>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)</pre>
为了理解上面的代码,首先看一下asset目录下的controlBackground图片。这个图片也许很小,但是iOS知道如何使用它来绘制UISegmentedControl的边框,就像它预先切好展好的那样。 切好是什么意思呢?看一看下面的放大的图吧:
slicing 这有4个33的正方形,每个角一个。这些正方形将会在拉升图片时被保留原样。然而灰色的部分将会被拉升。 在图片中,黑色部分将呈现为控件的配色。你将通过UIEdgeInsets()
告诉iOS如何拉升这个图片然后会给top、left、bottom和right4个参数传值为3,因为你的4个角是33。 Build and run.点击设置图片,你将会发现UISegmentedControl有了新的样式:
segmented
圆角被替换成了3*3的方形角。 现在已经给分段控件配了色,剩下的工作就是绘制其他控件的样式了。 关闭设置界面,点击右上角的放大镜;你将看见另外的分段控件,还有UIStepper
, UISlider
, and UISwitch需要加上主题。
抓起刷子,穿上废旧的衣服开始画画吧!
自定义累加器、滑块和开关
将下面行加入到Theme.swift的applyTheme()
<pre>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)</pre>
你使用了和UISegmentedControl相同可以拉升的图片;唯一的不同是UIStepper会在值达到上限或下限时变成不可用,所以也需要为这种情况做准备。简单起见这里用之前的图。 这里不仅修改了累加器的颜色,还替换了单调的+和-的符号按钮 Build and run.打开查找界面去看看了累加器变成什么样了。
stepperUISlider
和 UISwitch
同样也缺爱 将下面代码加入到applyTheme()
:
<pre>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</pre>
与你看到的UISegmentedControl一样,外观代理自定义了所有的代理类的实体。但是有个时候你并不想要所有控件一个样--这样的话,你可以自定义某一个控件实体。
自定义控件实体
打开SearchTableViewController.swift然后添加下面的行到viewDidLoad()
:
<pre>speciesSelector.setImage(UIImage(named: "dog"), forSegmentAtIndex: 0)
speciesSelector.setImage(UIImage(named: "cat"), forSegmentAtIndex: 1)</pre>
在这里你只是设置了种类选择器的segment的图片。 Build and run. 打开搜索界面,种族选择器会变成这样
species 你无须做任何事情,iOS便会倒反选中项的图片的颜色,这是因为这里的图片会自动以Template模式进行渲染。 如何选择性的修改控件的字体?这个也很简单 打开 PetTableViewController.swift 并添加下面两行到viewWillAppear()的底部
:
<pre>view.backgroundColor = ThemeManager.currentTheme().backgroundColor
tableView.separatorColor = ThemeManager.currentTheme().secondaryColor</pre>
然后, 将下面这行添加到 tableView(_:cellForRowAtIndexPath:)
的末尾,在return之前:
<pre>cell.textLabel!.font = UIFont(name: "Zapfino", size: 14.0)</pre>
这样就会修改显示动物名字的label的字体了。 Build and run. 进行一下比较。 theme_applied5-580x500 下面的图片显示除了查找界面的不同效果;新的版本就没有用到iOS的vanilla主题,并且显得更加有趣: theme_applied6-580x500下面还有些什么?
点击这里可以下载这篇教程的完成版的项目。 在Objective-C里,你可以指定特定的自定义设置应用到只有当他们包含在特定类的其他控件里。例如,您可以将自定义的UITextField放到UINavigationBar里。 但悲剧的是,swift不能这么干。不过有个好消息是,iOS9将添加此功能,请期待稍后更新本教程。 我希望你喜欢这篇UIAppearance的教程,并且学习到调整你的UI的方法。其实它真的很简单。 via Ray Wenderlich. 由创意应用翻译,转载请注明出处。 http://www.appccc.com/uiappearance-tutorial-below/