一次性搞定 iOS UI 颜色设置烦恼

前端开发专家 @ 雪球

图片

作者:少华

引言

日常工作中,涉及到 UI 层的开发需求,就避免不了和颜色交互。如果要支持夜间模式,一个页面又涉及到两套颜色,情况更为复杂,需要开发工程师比对两套设计稿的颜色编写代码。

图片

使用颜色的难易一定程度上影响开发效率。如何打通工程师和设计师之间的通道,让工程师能够高效率的使用设计文稿落地颜色代码也成为团队关注的重点。
雪球 iOS 团队在实践中摸索出一套工具和方法,在本文中分享出来,希望对同样想要提高颜色使用效率问题的团队提供一些思路。

方案介绍

统一颜色来源

建立统一的颜色来源:色板。
色板是设计输出中所有颜色的来源,现有近50组颜色,由设计团队维护。

图片

色板的作用:

  1. 维护严格对应的日夜间颜色,大多数设计输出不需要单独输出夜间模式,提高效率;

  2. 设计和开发对接过程中,如果遇到颜色不对的问题,能够使用颜色代号快速定位,方便修改,提高沟通效率;

  3. 保证设计输出中颜色的统一性和准确性。

工程师把色板抽象成工程可用的 json 格式保存在项目中,内容为颜色命名编号及对应的日夜间颜色编码。
色板 json 举例:

{  
  "T010": {  
    "default": "#333333",
    "night": "#C2C2C2"
  },
  "T020": {
    "default": "#666666",
    "night": "#888888"
  },
  "T030": {
    "default": "#AAAAAA",
    "night": "#666666"
  }
}

复制代码

打通颜色同步通道

图片

当色板增加颜色配对时,需要设计侧和工程侧进行同步操作,在过往的实践中会出现下面的问题:

  1. 人工同步修改色板文件造成的各种失误;

  2. 设计师不了解规则或根据个人喜好随意添加颜色配对,打破色板统一输出颜色规则;

  3. 出现情况2后,开发使用不在色板的新颜色需要自行修改开发色板 json,增加颜色常量声明,导致设计和开发色板出现分歧;

  4. 开发直接使用十六进制代码编写颜色代码;

为解决上面的问题,我们将色板维护到远端配置中心,采取自动同步策略。
设计侧通过特定管理人员更新色板(保证色板统一输出规则)。
工程师通过脚本自动拉取同步到项目文件中(手动的修改将被自动同步覆盖修正,避免分歧)。

同步时机

颜色相关资源及代码逻辑维护在独立的 pod 组件中,色板文件的变更要单独提交到这个 pod 仓库。
想要同步色板,需要先修改该 pod 依赖方式,使用 path 方式集成,然后执行 pod install ,让其以 Development pod 的形式存在。
所以,当执行完 pod install 后,如果色板有变化就直接在该 pod 中完成同步变更,是个不错的时机。

post_integrate Hook

CocoaPods 在安装 pod 的生命周期中提供各种 hook,允许开发者自定义安装过程并对项目进行更改。
CocoaPods 1.10 提供了一个新的 post_integrate hook,这个 hook 在 CocoaPods 集成步骤完成后执行,恰好满足了我们的需求。
在 Podfile 中:

post_integrate do |installer|
    # 下载并同步色板
end

复制代码

定位同步文件

同步色板操作要先解决几个问题:

  1. 如何判断色板所在 pod 是否以 path 方式集成,只有在 Development Pods 目录下才会尝试进行色板同步;

  2. 如何定位 pod 中的色板 json 资源及其他代码文件路径。

我们使用 Ruby 的 Xcodeproj 库来操作项目工程及寻找相关文件路径来解决上述问题。
Xcodeproj 在安装 Cocoapods 的时候已经作为核心组件安装到了系统中。关键代码举例:

require 'xcodeproj'
 
 
# 初始化 pod project
project = Xcodeproj::Project.open(pod_project_path)
 
 
# 拿到所有 project 中所有 target ,这里注意在 podspec 中定义在 resource_bundles 中的资源文件有自己单独的 target
targets = project.targets
 
 
# 对应 target 中所有 .h .m 文件,swift 文件包含在 source_file 中。
headers_files = target.headers_build_phase.files
source_files = target.source_build_phase.files
 
 
# 所有资源文件,色板 json 在这里
resources_files = resource_target.resources_build_phase.files
 
 
# 拿到你想要的文件,输出路径
path = file.file_ref.real_path.to_s

复制代码

通过上述方法,拿到我们需要的文件和资源路径,就可以进行后续操作了。

自动生成代码

色板 json 同步到项目后,为配合项目中已有的主题管理器的使用,还需要生成对应的颜色常量声明:
Color.h

#ifndef Color_h
#define Color_h
 
typedef NSString *SNBColor NS_EXTENSIBLE_STRING_ENUM;
/// default: #333333, night: #C2C2C2
FOUNDATION_EXTERN SNBColor const SNBColorT010;
/// default: #666666, night: #888888
FOUNDATION_EXTERN SNBColor const SNBColorT020;
/// default: #AAAAAA, night: #666666
FOUNDATION_EXTERN SNBColor const SNBColorT030;
 
#endif /* Color_h */

复制代码

Color.m

#import "Color.h"
 
SNBColor const SNBColorT010 = @"T010";
SNBColor const SNBColorT020 = @"T020";
SNBColor const SNBColorT030 = @"T030";

复制代码

NS_EXTENSIBLE_STRING_ENUM 字符串枚举,桥接到 Swift 中时可以进行枚举扩展使用

以上举例我们看到,颜色常量声明代码与色板 json 文件高度关联,完全可以使用工具自动生成。
在 Ruby 中,使用 ERB 是一个不错的选择。

ERB

ERB(全称 Embedded Ruby)是一个 Ruby 模板引擎。
ERB 为 Ruby 提供了一个易于使用并且功能强大的模板系统。使用 ERB,可以将 Ruby 代码添加到任何纯文本文档中,自定义所需要的各种输出。
一个简单的例子:

require 'erb'
 
x = 42
template = ERB.new <<-EOF
  The value of x is: <%= x %>
EOF
puts template.result(binding)

复制代码

输出: The value of x is: 42

再举一个稍微复杂些的例子:

require 'erb'
 
# 构建模板数据类
class SNBColor
  attr_accessor :name, :default, :night
   
  def initialize(name, default, night)
    @name = name
    @default = default
    @night = night
  end
end
 
# 创建模板
template = %{
#ifndef Color_h
#define Color_h
 
typedef NSString *SNBColor NS_EXTENSIBLE_STRING_ENUM;
<% colors.each do |c| %>
/// default: <%= c.default%>, night: <%= c.night%>
FOUNDATION_EXTERN SNBColor const SNBColor<%= c.name%>;
<% end %>
 
#endif /* Color_h */
}
 
color_h = ERB.new(template)
# 设置模板数据
colors = [SNBColor.new("T010", "#333333", "#C2C2C2"),
          SNBColor.new("T020", "#666666", "#888888"),
          SNBColor.new("T030", "#AAAAAA", "#666666")]
 
 
# 打印结果
puts color_h.result(binding)


复制代码

模板里的标签的含义:<% Ruby 代码 -- 内联输出 %>, <%= Ruby 表达式 -- 替换为结果 %>

运行后,打印结果为:

#ifndef Color_h
#define Color_h
 
typedef NSString *SNBColor NS_EXTENSIBLE_STRING_ENUM;
/// default: #333333, night: #C2C2C2
FOUNDATION_EXTERN SNBColor const SNBColorT010;
/// default: #666666, night: #888888
FOUNDATION_EXTERN SNBColor const SNBColorT020;
/// default: #AAAAAA, night: #666666
FOUNDATION_EXTERN SNBColor const SNBColorT030;
 
#endif /* Color_h */

复制代码

这个例子的输出正好是我们 Color.h 文件的内容。

与上面的例子类似,色板颜色常量声明代码全部使用 ERB 自动生成。实际操作中的伪代码举例:

# 设置模板数据
colors = 从色板 json 解析得到 snbcolor 实例数组
 
# 创建模板,我们将各类模板保存到以 .erb 结尾的文件中
template = File.read(File.expand_path('ColorH.erb', __dir__))
 
#构造 ERB 对象
message = ERB.new(template)
 
# 拿到渲染后结果
result = message.result(binding)
 
# 写入 Color.h 文件
File.open('Color.h 文件路径', 'w') { |file|
    file.write(result)
}

复制代码

当拉取到的色板 json 有新增颜色时,文件是以覆盖的形式重新生成写入,在 git diff 中看到的只有新增颜色的变动。

目前为止,色板 json,颜色常量声明代码文件都已经自动同步到颜色 pod 中。

颜色 Code Snippet

设计稿以 Sketch 和蓝湖等工具输出,所有颜色的标注都是以十六进制编码体现。
实际落地代码的时候,项目内封装的主题管理器需要我们键入对应颜色的常量声明才能使用对应的实际颜色。

图片

这里有原有开发流程中非常影响效率的一个问题:
工程师看到设计输出颜色标注是  #333333, 夜间模式是 #C2C2C2,这时需要先到色板 json 中找到颜色配对的名字 T010,然后在Xcode写入上图中的实际代码。
如果直接在代码中输入设计稿上的十六进制编码,就直接生成右面实际的代码,效率上将会得到非常大的提升。

目前设计团队使用 Figma 进行设计输出,也可在颜色位置直接备注颜色常量了,不再需求去色板 json 查找配色对应的名字。

Code Snippet

Code Snippet 代码模板,是一种快速生成代码的快捷方式,使用它可以节省大量输入并加快开发过程。
开发中可以使用 Xcode 提供的预先设置好的 Code Snippet,相信大家在日常开发中都会经常用到:

图片

除了 Xcode 为我们提前预设好的 Code Snippet,我们还可以进行自定义创建自己的 Code Snippet,来实现代码快速生成功能。
在自定义页面需要配置以下字段:

图片

  • Title 标题 - 名称。(出现在代码补全和代码块库列表中)

  • Summary 简介 - 简单描述下代码块作用。(只出现在代码块库列表中)

  • Platform 平台 - 限制可访问该代码块的平台。

  • Language 语言 - 限制可访问该代码块的语言。

  • Completion  输入码 - 快捷输入码。

  • Availability 有效范围 - 限制可访问该代码块的范围。

在了解每个字段的意义后,我们就可以创建“使用 颜色十六进制代码 当做快捷输入码,自动补全颜色代码” 的 Code Snippet 了。

图片

在实际开发中达到的效果:

图片

Completion 快捷码字段我们用字符 c 开头,是因为纯数字的输入不会触发 Xcode 自动补全机制。

自动生成 Code Snippet

Code Snippet 解决了设计稿到实际代码的转换问题,但是为色板中每组颜色配对去生成 Code Snippet 是一件工作量巨大的事情。
当色板有新增颜色时还需要工程师及时增加对应的 Code Snippet(每一组配色需要 Objective-C/Swift  两套 Code Snippet )。

Xcode 的所有 Code Snippet 都存放在  ~/Library/Developer/Xcode/UserData/CodeSnippets/ 目录下,以 XML 形式存在。
T010.codesnippet 举例:

图片

其中大量代码都是重复的模板,只有部分字段内容需要自定义。

这时 ERB 又该登场了。
我们同样使用 ERB 模板引擎进行渲染,自动生成 Code Snippet。
( IDECodeSnippetIdentifier 字段需要生成 uuid 填充,脚本里引入了 Ruby 的 uuidtools,相关工具依赖都放到了 Gemfile 中)

触发时机同样放到 post_integrate 中:

post_integrate do |installer|
    # 下载并同步色板
    # 自动生成同步 Code Snippet
end

复制代码

在生成颜色 Code Snippet 后,copy 到当前用户的  ~/Library/Developer/Xcode/UserData/CodeSnippets/ 目录下,同步 Code Snippet 的任务也完成了。

通过 Xcode 创建的 Code Snippet 使用 UUID 命名。直接通过 XML 源码形式创建可以指定任意名称,我们用 颜色名+开发语言 进行命名,防止重复生成。

总结

整套流程工具无需开发团队成员特殊配置,有新颜色需要同步时,在需求开发前的 pod install 过程中就完成了色板 json,颜色名称声明代码文件,配套 Code Snippet 的同步与更新。
在开发时,如果通过输入十六进制代码无法触发 Code Snippet 自动补全,则说明设计稿颜色不是出自色板,也避免了设计师不遵循色板统一输出颜色规则的做法。

图片

通过流程成对比我们可以看到,在新方案中:

  • 增加了有日夜间颜色配对的色板;

  • 色板自动更新同步,避免在同步过程中人为修改的失误;

  • 自动生成工程可用的颜色常量声明代码文件;

  • 自动生成 Code Snippet 帮助开发快速落地代码;

打通了从设计到工程的颜色转换过程,减少团队成员操作及学习成本,增加了 UI 开发中颜色使用的效率, 使 UI 开发再不用为支持夜间模式的颜色设置而烦恼。

参考资料

post_integrate Hook DSL:
www.rubydoc.info/github/Coco…

Xcodeproj:
www.rubydoc.info/gems/xcodep…

ERB:
www.rubydoc.info/gems/erb

Xcode Snippet:
nshipster.com/xcode-snipp…

还有一件事

雪球业务正在突飞猛进的发展,工程师团队期待牛人的加入。如果你对「做中国人首选的在线财富管理平台」感兴趣,希望你能一起来添砖加瓦,点击「阅读原文」查看热招职位,就等你了。
热招岗位:Android/iOS/FE 技术专家、推荐算法工程师、Java 开发工程师。

文章分类
iOS
文章标签