2017-07-24
作者:Ron Kliffer,原文链接,原文日期:2017-04-25
译者:Chenghui Bai;校对:Chenghui Bai;定稿:Chenghui Bai
更新日志:由Ron Kliffer更新至Xcode 8 和 Swift 3.原教程作者是Essan Parto.
尽管借壳在 iOS 应用是老生常谈的事情,但这并不意味着您的iOS应用程序中控件仅限于库存(我的理解是默认)外观。
是的,你可以从头开发你自己的控件和应用程序样式,但是在 iOS 中,苹果推荐你使用标准的 UIKit 控件,并使用各种自定义技术.这是因为 UIKit 控件更加高效,并且你自定义的控件应该是面向未来的.
在这个 UIApprearance 课程中,你将使用一些最基本的 UI 和自定义技术去自定义 Pet Finder app,是他脱颖而出.
作为额外的福利,你将学到在晚上打开 app后,它如何自动切换你 app 的 dark 主题.
入门指南
在here下载本课程的启动项目.这个 app 有很多标准的 UIKit 控件,看起来非常精致(香草).
打开工程,看看项目结构,编译运行,你将看见Pet Finder 主界面的元素:
有一个 navigation bar 和一个 tab bar,主界面是一个宠物列表,点击某一宠物去看关于他的详情.还有一个搜索页面.这个页面允许你为你的 app 选择一个主题.这听起来是一个很好的开始的地方.
UIAppearance :支持主题
许多的 app 不允许用户去选择一个主题,并且为 app 提供主题切换方法不是总是明智的.然而,有些情况主题非常有用.在开发期间,你可能想测试不同的主题去看哪个在你的 app 中最好.你可能测试 a/b那种样式是最合适的.或者你可能想让你的眼睛更舒适的时候通过添加一个 dark 主题.
在这个 UIAppearance 教程中,你将会为你的 app 创建一些主题,可以试试哪一个是最好的.
选择File\New\File… 然后选择 iOS\Source\Swift File. 点击 Next ,以Theme作为文件名,最后点击 Create.
用下面的代码替换文件的内容:
import UIKit
enum Theme: Int {
//1
case `default`, dark, graphical
//2
private enum Keys {
static let selectedTheme = "SelectedTheme"
}
//3
static var current: Theme {
let storedTheme = UserDefaults.standard.integer(forKey: Keys.selectedTheme)
return Theme(rawValue: storedTheme) ?? .default
}
}
让我们看看这些代码做了什么:
1.定义3种主题类型,default(默认), dark,graphical.
2.定义一个厂常量,帮助存储选择的主题.
3.为选中的主题定义一个只读的计算型属性(获取方法).使用 Userdefaults 持久化当前主题,返回删一次选中的主题,如果没有上次选择的主题则返回默认主题.
现在你设置你的主题枚举,给他添加一些样式.在Theme最后(最后的关闭括号之前)添加下面的代码:
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: 255.0/255.0, green: 115.0/255.0, blue: 50.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)
}
}
这为每一个特定的主题定义了定义了一个主题色mainColor.
让我们看看怎么使用的.打开AppDelegate.swift ,在
application(_:didFinishLaunchingWithOptions:)方法添加下面这行代码:
print(Theme.current.mainColor)
编译运行 app,在控制台将会打印:
UIExtendedSRGBColorSpace 0.341176 0.737255 0.372549 1
现在你有3个主题,并且能通过Theme管理.现在是时候在你的 app中使用它 了.
把主题应用到你的控件上
打开Theme.swift, 添加下面方法到Theme底部:
func apply() {
//1
UserDefaults.standard.set(rawValue, forKey: Keys.selectedTheme)
UserDefaults.standard.synchronize()
//2
UIApplication.shared.delegate?.window??.tintColor = mainColor
}
在这去快速说明上面代码:
1.使用 UserDefaults持久化选择的主题.
2.把 mainColor 应用到你的应用的 window 中的 tintColor 属性上,待会,你将学习更多关于 tintColor 内容.
现在你只需要调用下这个方法.
打开AppDelegate.swift ,使用下面代码替换print(),
Theme.current.apply()
编译运行你的 app, 你将会看见新 app, 看起来是绿色的:
浏览app,到处都是绿色色调,但是你不能改变所有的控件和视图.这是黑...绿…魔法?
应用 tintColor
从iOS 7开始, UIView 就暴露了 tintColor 属性.他通常用于定义整个 app 界面元素的基本颜色.
当你为一个 view 指定色调时,他将自动传播到所有继承自该视图的子视图. 由于UIWindow继承自 UIView,所以你可以为整个 app指定一个色调,通过设置 tintColor.这就是上面 apply()方法做的所有事情.
点击你的app 左上角 Gear 图标.一个 TableView 上有一个 Segmendt 控件.当你选择不同主题,点击Apply,不会发生任何事情.现在去修复.
打开SettingsTableViewController.swift 添加下面一行代码到 applyTheme(_:), 方法,位于dismissAnimated()方法上面:
if let selectedTheme = Theme(rawValue: themeSelector.selectedSegmentIndex) {
selectedTheme.apply()
}
现在调用方法去添加主题,在 root window中设置所选主题的mainColor.
下一步,添加下面这行代码到 viewDidLoad()方法中.当控制器第一次加载时,他将选择一个主题,并持久化到 UserDefaults.
themeSelector.selectedSegmentIndex = Theme.current.rawValue
编译运行 app, 点击 setting 按钮,选择 Dark,点击 Apply, 你 app 的色调将会改变,从绿色到橘黄色:
眼尖的读者可能注意到这些颜色是在Theme中定义的mainColor,但是,你选择 Dark 的话,看起来不是 dark 的.要有这种效果,你必须自定义更多的东西.
自定义NavigationBar
打开Theme.swift 添加下面2个属性到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
}
这2方法为每个主题,返回了namvigation bar一个适当的样式和背景图
下一步,添加下面代码到 apply()底部:
UINavigationBar.appearance().barStyle = barStyle
UINavigationBar.appearance().setBackgroundImage(navigationBackgroundImage, for: .default)
OK,为什么在这里工作?难道不需要存储UINavigationBar的实例对象?
UIKit有一个非正式协议叫做UIAppearance ,大多数控件都遵循这个协议。当你调用UIKit控件的appearance() 方法(不是实例),他会返回一个UIAppearance 代理。当你改变这个代理的属性,这个类的所有实例都会自动改变他们的属性值。这是非常便利的,你不需要手动为每一个被实例化的控件改变他们的属性值。
编译运行app,选择 Dark 主题,navigation bar 现在将会非常暗。
这样看起来好了一点,但是你忍忍还有一些工作要做。
下一步,你讲自定义返回指示器。iOS默认使用一个人字形图标作为返回指示器,但是你可以用代码改变他,使他更棒!
自定义navigationbar bar返回按钮指示器
这个需要让所有主题都得到改变,因此你需要去添加下面代码到Themes.swift 的 apply() 方法中:
UINavigationBar.appearance().backIndicatorImage = UIImage(named: "backArrow")
UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "backArrowMask")
现在你只设置了一张图片和一个过度图到返回指示器上。
编译运行app. 点击一个宠物,你将会看见一个新的指示器:
打开Images.xcassets 在Navigation组中找到名为 backArrow 的图片。这里的图片都是黑色的,但是你的app在window中设置了tint color起了作用。
但是iOS怎么能改变工具栏按钮的图像颜色呢?为什么不到处都是呢?
事实证明,iOS中的图像有3中呈现模式:
Original:始终使用图像其原有颜色。
Template:忽略颜色,使用图像作为模板。这种模式,iOS只使用图像的形状,并在屏幕上渲染之前将图像本身着色。所有当一个控件有 tint color,iOS会使用图片提供的这个形状并且使用tint color为他上色。
Automatic:依靠你使用的图片的上下文,系统决定是否将图像绘制为“Original”或“Template”。对于诸如“back指示器”、“导航控件”,tab bar图像等项,IOS默认情况下忽略了图像颜色。你可以手动设置模式覆盖这个默认行为。
回到app,点击一个宠物,然后点击Adopt.仔细看导航栏上的返回指示器,你发现什么问题了吗?
当你Back 文字往左边过度时,他与指示器重叠了,看起来非常不好:
去修复他,你需要去改版这个过度图片。
更新属性backIndicatorTransitionMaskImage 在Theme.swift 的 apply() 中设置的。用下面的代码:
UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "backArrow")
编译运行app,再一次点击宠物,点击Adopt. 现在这个过度看起来非常好了:
这个文本不再被切断,看起来像是在指示器下面。所以,这发生了什么?
iOS 使用的是非透明像素的返回指示器图片去话指示器。然而,他与过度图片做了一些完全不一样的工作。他使用了不透明像素的过度图片来屏蔽指示器。所以当文本左移,这指示器就成龙唯一可见的了。
在这个原始实现中,你会提供一个图片,覆盖在返回指示器的整个表面。这就是为什么文本通过过渡仍然可见的原因。现在你使用指示图自己作为mask,看起来不错,但是如果你仔细看,你会看到文字消失在最右边的mask,而不是在指示器。
看指示器图片和这个被固定的版本的面具在你的image assets 中,你将会发现他们之间显示的很好:
这个黑色形状作为返回指示器,红色形状作为你的 mask,您希望文本只有在红色区域下可见在其他地方隐藏。
这么做,再次改变apply()方法的最后一行代码:
UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "backArrowMaskFixed")
编译运行app,最后一次点击一个宠物,然后点击Adopt.你将看见文本现在小时在图片下面,正式你期望的:
现在你的navigation bar已经完美展示了,是时候去给你的tab bar送去一些爱了。
自定义Tab Bar
依然是 Theme.swift,添加下面属性到Theme:
var tabBarBackgroundImage: UIImage? {
return self == .graphical ? UIImage(named: "tabBarBackground") : nil
}
这个属性将会为每个主题的tab bar提供适当的背景图。引用下他们的样式,添加下面代码到apply()方法:
UITabBar.appearance().barStyle = barStyle
UITabBar.appearance().backgroundImage = tabBarBackgroundImage
let tabIndicator = UIImage(named: "tabBarSelectionIndicator")?.withRenderingMode(.alwaysTemplate)
let tabResizableIndicator = tabIndicator?.resizableImage(
withCapInsets: UIEdgeInsets(top: 0, left: 2.0, bottom: 0, right: 2.0))
UITabBar.appearance().selectionIndicatorImage = tabResizableIndicator
设置 barStyle 和 backgroundImage 应该很熟悉了现在。这个和之前设置navigationBar的方式一样。
在上面的最后3行代码,你到检索下asset目录检索一个指示器图片像,并将其呈现模式设置为.AlwaysTemplate。这是一个不使用自动设置渲染模式的例子。
最后,你创建一个可调整大小的图片,并设置为tab bar的selectionIndicatorImage.
编译运行app,你将会看见一个全新的tab bar主题.
开启Dark 主题去查看更多。
看见选中的tab bar下面的线条吗?这是一个指示器图片,尽管他只有6个点的高度和49个点的宽度,iOS在运行时拉伸这个tab宽度。
下一部分是可拉伸的图片,以及怎么工作的。
自定义分段(Segmented)控件
有一个控件没做任何改变,Segment控件,选中显示当前主题的。是时候给他带来一个精彩的主题了。
未完待续。。。