做者 | 希德编辑 | 张之栋、王文婧在APP开发中,快速实现效果相当重要,而样式的可复用、易维护能够帮助开发人员作到这一点。本文做者开发的Swift-CSS正是这样一个实现样式系统的库,它将CSS的技术优点引入SwiftUI开发中,不只能够实现SwiftUI里样式属性的复用、解构,还能变化出许多相似Web领域的优秀技术方案。经过了解它的应用方法,或许会对你的平常工做有所助益。本文的主角 SwiftUI-CSS 是我一个多月前实现的一个 SwiftUI 库,它的目的是实如今 Web 开发领域结构样式分离的效果:css
CSS 负责结构样式。html
样式不写在 HTML 的属性里,而是在 CSS 当中,不只仅是为了解耦,更重要的是复用,促使开发者把全部的业务样式需求分解,提炼良好的基础样式,以更系统的方式管理样式。前端
CSS 自然地提供 classname 机制,能够实现样式分组和组合。一个业务样式的最终效果能够由一些基础样式组合而成,不一样组合呈现不一样的效果。git
<div class="fontStyle colorStyle floatStyle">
</div>程序员
本质上讲, CSS 里的一个 classname 封装了一组属性(property)的集合,简称样式。多个 classname 便可组合成为一个样式系统,一个样式系统实现业务上的组件设计,配合具体的 HTML 结构就是一个组件(component)。github
SwiftUI-CSS 将 CSS 的技术优点引入 SwiftUI 开发中,不只能够实现 SwiftUI 里样式属性的复用、解构,还能够变化出不少相似 Web 领域的优秀技术方案。SwiftUI-CSS 的详细使用可参见 SwiftUI-CSS readme: https://github.com/hite/SwiftUI-CSS/blob/master/README.md编程
本文试图探讨 SwiftUI-CSS 为 SwiftUI,乃至 iOS 开发带来的促进做用与影响。bootstrap
(阅读本文须要你对 SwiftUI 有基本的了解)swift
什么是样式系统?样式系统指的是在 UI 设计规范中,提炼出来的一些规范。以 Ant Design 为例。它的“字体使用规范”里指出,主标题的样式是这样的:行高行间距(当文字有可能多行时)line-height。前端工程化
main_title
做为样式系统里的命名):
.main_title{
color: rgb(102, 102, 102);
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", 微软雅黑, SimSun, sans-serif;
font-size: 16px;
font-weight: 500;
}
定义好以后,.main_title
就表明了设计师对 主标题 的视觉要求,能够在后面的界面中反复使用,而不须要字体、字号、字重、颜色再定义一遍。
这是比较基础的样式,稍微复杂点的例子是按钮,按钮的样式不只包含字体的样式,还包括按钮的边距、圆角、背景色等属性。
.buttonStyle
里。
.buttonStyle{
width: 212px;
height: 40px;
border-radius: 20px;
background-color: #dd1a21;
line-height: 40px;
font-family: PingFang-SC-Bold;
font-size: 16px;
color: #FFFFFF;
text-align: center;
}
后续界面里须要这种肯定按钮的时候,只须要引用 .buttonStyle
样式名就能够了。更好的例子可参考 Twitter 出品的 Bootstrap 来学习如何组织管理 CSS 样式: https://getbootstrap.com/
使用样式系统,要求视觉和开发同窗对总体视觉有全局掌握。对于视觉同窗,梳理视觉规范,定义哪些是通用规则,哪些是个性规则,哪些是基础规则,以及如何对基础规则进行运算;开发同窗提供样式接口时,须要在实现视觉要求的基础上,还可以保证扩展性和易读性。在对视觉规范有深刻理解以后,设计出来的视觉规范才有用,更健壮。
可复用。若是视觉稿是按照原有规范实现的,那么新需求里的页面,也可使用已有的样式来快速搭建,就像搭积木同样。
main_title
,
buttonStyle
是基础元素样式。在组件库里,会有一些基础元素样式、基础功能样式。一些复杂的组件须要用这些基础元素样式、基础功能样式组合。
/** 元素样式 **/
.w-seperator{
height: 2px;
width: 100%;
backgroundColor: #ff00ff;
}
/** 功能样式 **/
.f-hide{
display:none;
}
/** 功能样式 **/
.f-clear_both{
clear:both;
}
// 请忽略这个样式的实际意义
<div class="w-seperator f-clear_both f-hide"></div>
这里w-seperator f-clear_both f-hide
便是这个分割线的样式名称。
w-seperator f-clear_both f-hide
并非那么简洁。如借助预编译,还可使用变量、继承等特性来简化 CSS 的定义工做。比方使用 sass: https://sass-lang.com/
.w-seperator{
height: 2px;
width: 100%;
backgroundColor: #ff00ff;
}
.f-hide{
display:none;
}
.f-clear_both{
clear:both;
}
.seperator_in_list{
@extend .w-seperator;
@extend .f-hide;
@extend .f-clear_both;
}
这样.seperator_in_list
这个名字就是咱们在后面界面里可用的样式名,比起 CSS 是否是更见文知意,更易用呢?
// 黑色中空,中间是 clear color
+ (instancetype)yx_BlackHollowClearButton {
YXButton* button = [YXButton new];
button.titleLabel.font = [UIFont systemOfSize:14];
[button setTitleColor:YXColorGray4 forState:UIControlStateNormal];
[button setTitleColor:YXColorWhite forState:UIControlStateHighlighted];
[button setTitleColor:YXColorGray10 forState:UIControlStateDisabled];
button.layer.borderWidth = YX_ONE_PIXEL;
button.layer.borderColor = YXColorGray4;
button.layer.cornerRadius = YXButtonCornerRadius;
button.layer.masksToBounds = YES;
return button;
}
UILabel *label = [UILabel new];简单的对照,发现复用只能复用属性,如举例中的
[NYQSpec setLabelStyle:label withNYQCode:NYQCode_18_blk_med];
label.textAlignment = NSTextAlignmentCenter;
label.text = @"请确认如下信息";
YXColorGray4
和
NYQCode_18_blk_ med
,若是要设置一组属性,须要再次设置,没有一个对象如
importantStyle
来表明颜色和字体等,使得下一个 button 能够直接设置
importantStyle
的。
// 不存在这样的系统接口
UIStyle *importantStyle = [UIStyle styleWithColor: [UIColor redColor] font: YX_Button_Font];
// 确认按钮
UIButton *confirm = [UIButton new];
[confirm setStyle: importantStyle];
// 提示按钮
UIButton *prompt = [UIButton new];
[prompt setStyle: importantStyle];
我想缘由就是 Cocoa touch 设计之初就没有考虑用对象来表示一组属性,没有设计样式系统的概念,致使在封装实现样式系统时比较困难。
补充提示[button setStyle:]
这个接口其实可使用
category
技术来实现,
UIStyle
能够用自定义封装,只要 UIStyle 实现了接口,任何样式的属性均可以封装到一个 UIStyle 的实例中。这种方式和下面即将介绍的 SwiftUI-CSS 的封装本质的不一样在于,UIStyle 里的属性不能运算,
[button setStyle:]
本质是把属性挂在一个全局变量下,而后遍历,在性能方面没有提高,充其量是一种语法糖。
UIStyle *importantStyle = [UIStyle styleWithColor: [UIColor redColor] font: YX_Button_Font];
// 一种 setStyle 内部实现
- (void)setStyle:(UIStyle *)style{
if(style.font){
self.titleLabel.font = style.font
}
if(style.color){
[self setTitleColor:style.color forState:UIControlStateNormal];
}
}
// 理想中的,目前没法实现
- (void)setStyle:(UIStyle *)style{
if (style.computedStyle == nil) {
[style compute];
}
// computedStyle 包含了字体和颜色
[self setFinalStyle:style.computedStyle];
}
理想中的 computedStyle 真正使用到样式上,才对全部属性进行一次计算,这样在后续其余 button 设置时,直接使用计算结果,而不是再次使用遍历的方式去一一设置。属性计算带来的性能提高,相似在 JS 模板引擎中经常使用的字符串模板编译成 function 带来的效果,甚至更高。
使用 storyboard 的界面开发使用代码实现样式系统,至少还可使用所有变量、宏、函数封装来达到某种意义上的复用、维护。可是若是使用 storyboard 实现的界面,则须要面对更多的问题。storyboard 在快速搭建单个界面时效率很是高。假设须要更新品牌色时,至少还能够用 asset catalog
来实现全局的颜色修改,可是涉及到如“主标题”字号修改时,则显得无能为力,只能一个一个 storyboard 去修改,更不要说一块儿修改多个属性的组合了。
storyboard 最多能够在小组件层复用,向上到 ViewController 粒度太多,不容易复用;向下只能使用 xib 复用组件—— storyboard 不存在样式系统。
直到 SwiftUI 横空出世,把描述性界面开发体验带到 iOS,它的函数式语法和属性对象方式,使得人们能够用 Swift-CSS 来实现 SwiftUI 里的样式系统。
SwiftUI SwiftUI 里的链式语法,是函数式函数调用的体现。SwiftUI 实体分为View
和
ContentModifier
。
Text("g_kumar")
负责视图结构;
.font(.title)
添加属性样式。简单的实例以下:
Text("g_kumar")
.bold()
.font(.title)
Text("Notifications: \(self.profile.prefersNotifications ? "On": "Off" )")
Text("Seasonal Photos: \(self.profile.seasonalPhoto.rawValue)")
Text("Goal Date: \(self.profile.goalDate, formatter: Self.goalFormat)")
g_kumar
文字组件为例,咱们应用函数式编程里的运算规律 - 结合律推导一番:
v
表示。cm1
表示。cm2
表示。C
。C = v * cm1 * cm2
// =>
C = (v * cm1) * cm2
// =>
C = v *( cm1 * cm2)
// =>
cm = cm1 * cm2
C = v * cm
// 假设 v1 是另一个 Text,则
C1 = v1 * cm
因此,上面公式里的 cm
表明了样式的计算结果,在这里是指字形和字号的运算结果。利用这个计算结果,在后面的样式设置 v1
,v2
等视图时,能够直接使用 cm
来设置样式。它带来的性能提高,取决于 Apple 对 cm
这个计算变量的内部优化程度。鉴于目前 SwiftUI 闭源,咱们还没法得知这种优化带来多大的提高。退一步讲,将计算结果封装为一个变量,当 Apple 后续对 ContentModifier 计算进行优化后,调用者可透明地享受到优化提高。
以上就是属性运算的原理,因此有了 SwiftUI-CSS。
SwiftUI-CSS 的样式系统SwiftUI 的原理很简单,就是使用CSSStyle
对象来封装样式对象,而后经过
addClassName
这个 modifier 将样式插入函数运算中,和其余事件、通知、样式(.frame\ .resizable)一块儿无缝协做。以 SwiftUI-CSS example 工程为例:
// without SwiftUI-CSS
Image("image-swift")
.resizable()
.scaledToFit()
.frame(width:100, height:100)
.cornerRadius(10)
.padding(EdgeInsets(top: 10, leading: 0, bottom: 15, trailing: 0))
// with SwiftUI-CSS
let languageLogo_clsName = CSSStyle([
.width(100),
.height(100),
.cornerRadius(10),
.paddingTLBT(10, 0, 15,0)
])
Image("image-swift")
.resizable()
.scaledToFit()
.addClassName(languageLogo_clsName)
其中,languageLogo_clsName
就是 logo 的样式名,在页面其余 logo,能够直接复用这个样式。更多使用示例请查看 SwiftUI-CSS example 工程: https://github.com/hite/SwiftUI-CSS_example
-- ProductDetail |----ProductDetailView.swift |----ProductDetailStyle.swift
ProductDetailView.swift 负责构建界面的结构,里面只有 view 元素、事件逻辑、数据流等,保持简洁。而 ProductDetailStyle.swift 里面是一些样式的定义。两个文件分离有助于 diff 、review 及与他人协做。
复用
当有视觉规范后,按照规范,在公用的样式文件里,预先定义好全部基础样式,如“主标题”文字样式等,而后定义若干公用的业务样式,如出错弹窗。理想状况下,业务样式和组件样式均可以像搭积木同样,由这些基础样式拼凑而成。
性能提高
按照理论,CSSStyle 这样的计算结果,是一种相似编译后的缓存(compiled code),老是有提高的。具体的测试数据,待 iPhone 11 上市和 macOS 10.15 发布以后再作评测。请关注 SwiftUI-CSS,后续会补充。
样式继承
CSS 里的样式系统
的例子),SwiftUI-CSS 也内置了:
let fontStyle = CSSStyle([.font(.caption)])
let colorStyle = CSSStyle([.backgroundColor(.red)])
let finalStyle = fontStyle + colorStyle
button.addClassName(finalStyle)
利用CSSStyle
提供的+
运算,将多个样式合并实现继承效果。
以上只是我我的实践中遇到的场景,在别人的手里可能还会迸发出不同的火花,如下是个人一些构想:
SwiftUI zen garden 计划在 Web 开发早期,人们对 CSS 在 Web 开发中扮演的角色定位不是很清晰。2003 年,Dave Shea 发起了 CSS zen garden 计划。这个网站提供一套固定的带样式名,可是没有样式实现的 .html 文件,而后参与者提供不一样的 CSS 文件,对相同的 HTML 结构进行 stylize
,试图探索 CSS 对 HTML 结构可定制能力的极限。时至今日,已经有 218 个五花八门的设计位列 Design List 其中,不少充满想象力的设计让人叹为观止。
参与者提供对这些样式名的实现文件,如 style.swift,和 html.swift 一块儿生成不一样的界面设计。让咱们一块儿探索使用 SwiftUI 可定制能力的极限。
以上方案被称为 SwiftUI zen garden(待实施)。
设计师和程序员协做——storyboard 的夙愿xib(storyboard 前身)早在 iOS1.0 以前就被 Apple 用在 iOS 的开发工做流中:设计师用 Interface Builder 编写 xib 文件,以后程序员用 xcode,在 xib 的基础上继续编写事件、数据等业务逻辑。可是由于 xib 变动后较难,diff 和 xib 并非程序员使用的 oc 语言,不能无缝复用,致使设计师和程序员分离开发的目的没有实现。
大部分设计师用 xib 完成的 App prototype,都不能直接让程序员继续开发。
更多时候 xib 的工程只是为了作 App 原型,程序员还须要按照 prototype,彻底或者部分用代码重写。
有了 SwiftUI,设计师可使用 SwiftUI 编写 prototype,验证完毕以后,程序员拿 SwiftUI 源码继续开发,由于都是 swift 文件啊。设计师后续的样式调整,能够直接修改 style.swift 文件,不须要和程序员去竞争 html.swift 文件使用权,避免冲突。设计师和程序员无缝协做的大和谐,在 SwiftUI 中得以实现!
也许你还能想到更多用法,是否是?
后记SwiftUI-CSS 1 个月前就写好了,当我发布到 Twitter、Hacknews 等地方,邀请各位大 V 宣传时,并无激起多少浪花,我认为它的重要性被低估了,故做此文。
参考连接: https://sass-lang.com https://en.wikipedia.org/wiki/CSS_Zen_Garden
活动推荐前端工程化是前端业务以及技术架构复杂度提高的必行之路,但工程化面临的挑战始终很大。在这个超级 APP 割据,又有大量后继挑战者的移动互联网后半场,没有最好的工程化方案,只有最适合的工程化方案。
QCon 日程已上线,点击「阅读原文」或识别二维码来 QCon 上海 2019 看各个互联网公司一系列经历实战考验的前端实践。大会 报名倒计时,团购享优惠,有任何问题欢迎联系票务小姐姐 Ring:17310043226(微信同号)