UIAppearanceMattt Chester Liu 🚩🌱

美观还是实际?直接还是间接?感情还是理智?

美,究竟是肤浅的一个概念,还是深层事实的一种表层体现?当我们说一个东西具有好的设计的时候,我们到底在谈论什么?审美的标准是相对的还是绝对的?

这些深刻的问题,哲学家,艺术家和手工艺者们已经深思了近千年。

在我们继续追寻美的意义和对于宇宙更深的理解的同时,应用市场对于这个话题的反映倒很明确:

用户愿意为好看的软件花大价钱。

当一个人买了一台 iPhone 的时候,他同时也是“买”了苹果的哲学——所有好用的东西也应该是好看的。同样的道理也适用于开发领域,粗糙的界面会让人们感觉代码也不会好到哪里去。

在以前的 iOS 开发中,一些很细小的界面定制工作也需要用到一些会影响到 AppStore 审核的办法,例如 method swizzling。幸运的是,在 iOS 5 上,出现一种新的解决办法:UIAppearance


UIAppearance 允许我们对视图和控件的样式在整个应用范围内进行统一的定义。

为了让这个机制融入已有的 UIKit 体系,苹果设计了一个很聪明的办法:UIAppearance 是一个协议,它返回一个 proxy,这个 proxy 会把所有的配置都转发给某个特定类的实例们。为什么使用 proxy 而不是在直接在 UIView 里添加一个属性或者方法呢?因为有一些不是 UIView 的对象也会渲染自己的组合视图,例如 UIBarButtonItemUIAppearance 可以定制一个类所有的实例,也可以限定在具体的视图结构中(译者注:下面文档来自苹果官方文档,不再做翻译):

  • +appearance: Returns the appearance proxy for the receiver.
  • +appearanceWhenContainedIn:(Class <UIAppearanceContainer>)ContainerClass,...: Returns the appearance proxy for the receiver in a given containment hierarchy.

To customize the appearance of all instances of a class, you use appearance() to get the appearance proxy for the class. For example, to modify the tint color for all instances of UINavigationBar:

UINavigationBar.appearance().tintColor = myColor
[[UINavigationBar appearance] setTintColor:myColor];

To customize the appearances for instances of a class when contained within an instance of a container class, or instances in a hierarchy, you use appearanceWhenContainedInInstancesOfClasses(_:) to get the appearance proxy for the class (the older, variadic appearanceWhenContainedIn method has been deprecated):

UIBarButtonItem.appearanceWhenContainedInInstancesOfClasses([UINavigationBar.self])
				.tintColor = myNavBarColor
UIBarButtonItem.appearanceWhenContainedInInstancesOfClasses([UINavigationBar.self, UIPopoverController.self])
				.tintColor = myNavBarColor
UIBarButtonItem.appearanceWhenContainedInInstancesOfClasses([UIToolbar.self])
				.tintColor = myNavBarColor
UIBarButtonItem.appearanceWhenContainedInInstancesOfClasses([UIToolbar.self, UIPopoverController.self])
				.tintColor = myNavBarColor
[[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UINavigationBar class]]]
       setTintColor:myNavBarColor];
[[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UINavigationBar class], [UIPopoverController class]]]
        setTintColor:myPopoverNavBarColor];
[[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UIToolbar class]]]
        setTintColor:myToolbarColor];
[[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UIToolbar class], [UIPopoverController class]]]
        setTintColor:myPopoverToolbarColor];

判断 UIAppearance 可以应用于哪些属性

UIAppearance 使用 proxy 的办法带来的一个主要问题是,很难知道 selector 是不是真正可以用的。

因为 +appearance 返回的是 id, Xcode 不能提供任何代码补全信息,这个造成困惑和问题的主要原因

在 iOS 7 上, UIAppearance 现在返回的是 instancetype,使得代码补全可以正常工作了。 万岁!

要想知道哪些方法可以应用于 UIAppearance,你得去头文件中找

$ cd /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/
  Developer/SDKs/iPhoneOS*.sdk/System/Library/Frameworks/UIKit.framework/Headers
$ grep -H UI_APPEARANCE_SELECTOR ./* | sed 's/ __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_5_0) UI_APPEARANCE_SELECTOR;//'

UIAppearance 会在方法签名中查找 UI_APPEARANCE_SELECTOR 宏。任何有这个注解的方法都可以被 appearance proxy 使用。

这里是 iOS 7.0 上所有的属性列表,方便你参考使用。

在自定义的 UIView 子类中实现 <UIAppearance>

就像 NSLocalizedString#pragma是高质量的 Objective-C 代码的标志一样,让自定义的界面类支持 UIAppearance,不仅仅是一种最佳实践,同时也展示了开发者在代码实现上花费了一定程度的心血。

Peter Steinberger 撰写了 这篇优秀的博文,其中描述了在自定义视图中实现 UIAppearance 时需要注意的一些地方。对于在开源 UI 控件中追求极致实现的人来说,这篇文章是必读的。

替代品

UIAppearance 另一个主要的不足之处在于,样式的规则是 命令式 的,而不是 描述式 的。也就是说,样式是在应用执行时在代码中进行应用,而不是从一个规则列表中获取。

是的,如果我们可以 Web 开发中偷过来一个主意的话,那一定就是内容和表现分离了。不管你对 CSS 有什么看法,样式表实在是 美妙之极

到目前为止,样式表的狂热爱好者在 iOS 上有几个可选的替代品。Pixate 是一个商业框架,支持使用 CSS 来定制应用样式。NUI,一个由 Tom Benner 发起的开源项目,使用类似 CSS/SCSS 的语言,做的差不多是同样的事情。和它同一战线的另一个开源项目是 UISS,由 Robert Wijas 发起,允许我们从 JSON 中读取 UIAppearance 规则。


Cocoa 开发者痴迷于视觉审美的历史可以追溯到很久之前,他们经常会通过很极致的方法来实现想要的效果。回忆一下 Mac 开发者的 美味时代,以及诸如 Disco 这样的 App,它在烧录光盘时会释放出虚拟的烟雾效果

这种决心让东西看起来好看的精神,在 iOS 平台上同样蔚然成风。作为社区和生态系统的一员,在用户对于应用的期望上,我们不懈地努力,去挑战极限。在这个过程中,我们的工作变得更加具有挑战性,也使得开发 iOS 的体验更加像是一种享受。

不要止步于金玉其外败絮其中(反之亦然)。让你的应用从界面到实现都变得优美。


除非另有声明,本文采用知识共享「署名-非商业性使用 3.0 中国大陆」许可协议授权。