Section 03 : Components and Visual Effects - HH-Ge/BuildAnAppWithSwiftUI GitHub Wiki
Apply transforms, blend modes, blur in SwiftUI and turn views to components.
在 SwiftUI 中使用 transforms, blend modes, blur 将视图转化为组件
在代码区按住⌘ 并单击显示第一张卡片的 VStack,在菜单中选择 Extract Subview,Xcode 会将整个 VStack 抽取成一个新的结构体并等待用户命名,这里命名为 CardView。
struct CardView: View {
var body: some View {
VStack {
HStack {
VStack(alignment: .leading) {
Text("UI 设计")
.font(.title)
.fontWeight(.semibold)
.foregroundColor(.white)
Text("证书")
.foregroundColor(Color("accent"))
}
Spacer()
Image("Logo1")
}
.padding(.horizontal, 20)
.padding(.top, 20)
Spacer()
Image("Card1")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 300, height: 110, alignment: .top)
}
.frame(width: 340.0, height: 220.0)
.background(Color.black)
.cornerRadius(20)
.shadow(radius: 20)
}
}
- 抽取代码只能是最外层的布局控件内,换句话说,按住⌘ 并单击最外层 Stack 不会显示
Extract Subview菜单。- 抽取出来的视图应该按照其实现的功能命名,如 CardView 表示这是个显示 Card 的 View。这是好的编程习惯。
- 苹果提出的命名公约是:自定义名称 + 返回值类型
使用同样的方法将下层的卡片视图也抽取出来,命名为 BackCardView。
struct BackCardView: View {
var body: some View {
VStack {
Spacer()
}
.frame(width: 340.0, height: 220.0)
.background(Color.blue)
.cornerRadius(20)
.shadow(radius: 20)
}
}抽取之后的主视图代码如下,看起来清爽了很多。
struct ContentView: View {
var body: some View {
ZStack {
BackCardView()
CardView()
}
}
}在代码区输入时,如果光标离开了主视图的部分(比如编辑 CardView 或者 BackCardView 两个视图)或者切换到其他应用的窗口后,预览区的预览是会暂停的,需要点击右上角的
Resume来恢复预览。而如果一直在编辑主视图的代码,则预览会跟随编辑一起发生变化。
(1)各层的卡片是靠偏离量来区分的,所以将 BackCardView 中的 offset 修饰器剪切,这样每个新的 BackCard 都可以自己设定偏移量。
(2)在 BackCardView 后面粘贴,这样第一张 BackCard 就显示出来了。
(3)现在,再复制一个带有同样偏移量修饰符的 BackCardView,修改偏移量 y 为 -40。第三张卡片增加好了。
注意:
可以看出,在 ZStack 中描述各张卡片的视图顺序刚好与预览图中的显示
相反,表示第一张卡片的 CardView 的代码在最后,而第一个 BackCardView 则表示处于最下层的卡片。
(1)为了保证卡片都是基于同一个尺寸缩放而来,先在 BackCardView 结构体中修改 frame 修饰器的 width 为 340,使其大小与 CardView 一致。
(2)分别给两个 BackCardView 加上 scaleEffect 修饰器,(按实际显示)从上至下设定缩放比例为 0.95 和 0.9。
注意:
在元素选择窗口中,输入关键字 Scale,会有多个结果,注意区分不同修饰器修饰的对象。这里我们使用的是scaleEffect。
(1)给两个 BackCardView 加上 rotationEffect 修饰器,参数都选择为 5°。
注意:
同样的,输入关键字 rotation 同样会在元素选择窗口中得到多个结果,注意区分。这里我们还是使用rotationEffect。
ZStack {
BackCardView()
.offset(x: 0, y: -40)
.scaleEffect(0.9)
.rotationEffect(.degrees(5))
BackCardView()
.offset(x: 0, y: -20)
.scaleEffect(0.95)
.rotationEffect(Angle.degrees(5))
CardView()
}代码中两种写法的效果是一样的,因为 Swift 可以推测参数类型(或者说此处必为 Angle?),因此可以使用简化的写法。
如果对参数的意义不是很清楚,可以查看文档,也可以先输入点号 (
.)后根据自动完成提示查看有什么参数可以用。查看文档的方法:
- 常用:按住 ⌥ 键点击要查看的代码。
- 在元素选择窗口中查看。
- 在按住 ⌘点击后的菜单里选择
Jump to Definition。
(2)修改最底层的卡片旋转角度为 10°。
(3)给中间的卡片增加 3D 旋转效果。
视频说了一大堆如何有趣好玩和流行……
混合模式是图像处理技术中的一个技术名词,不仅用于广泛使用的Photoshop中,也应用于AfterEffect、llustrator 、 Dreamweaver、 Fireworks等软件。主要功效是可以用不同的方法将对象颜色与底层对象的颜色混合。当您将一种混合模式应用于某一对象时,在此对象的图层或组下方的任何对象上都可看到混合模式的效果。
SwiftUI 的 BlendMode 修饰器的枚举值包括:color、colorBurn、colorDodge、darken、destinationOut、destinationOver、difference、exclusion、hardLight、hue、lighten、luminosity、multiply、normal、overlay、plusDarker、plusLighter、saturation、screen、softLight、sourceAtop
百度百科词条混合模式中对这些枚举值做了部分说明。
在三张卡片的最后增加 BlendMode 修饰 .blendMode(.hardLight)。
因为是三张卡片都加这个修饰,我尝试加在 ZStack 上,这样可以减少两行代码。看看👁👁后续是否会有问题。
这明显有问题,除非 ZStack 中只有这些内容!!!
虽然我们已经提取了 BackCardView,但具体应用到每张卡片时,所关心的一致性内容应该是布局控件(容器)的内部,而非其所带的修饰符。
(1)把底色、圆角和阴影的修饰从 BackCardView 中剪切出来,粘贴到每个 BackCard 上。**注意:**这些修饰应该是在最前面的,前面说过修饰的应用是有顺序的。
(2)修改两张卡片的背景色,这里我们分别使用自定义的card3和card4 。
struct ContentView: View {
var body: some View {
ZStack {
BackCardView()
.background(Color("card4"))
.cornerRadius(20)
.shadow(radius: 20)
.offset(x: 0, y: -40)
.scaleEffect(0.9)
.rotationEffect(.degrees(10))
.rotation3DEffect(.degrees(10), axis: (x: 10, y: 0, z: 0))
BackCardView()
.background(Color("card3"))
.cornerRadius(20)
.shadow(radius: 20)
.offset(x: 0, y: -20)
.scaleEffect(0.95)
.rotationEffect(Angle.degrees(5))
.rotation3DEffect(.degrees(5), axis: (x: 10, y: 0, z: 0))
CardView()
}.blendMode(.hardLight)
}
}
struct BackCardView: View {
var body: some View {
VStack {
Spacer()
}
.frame(width: 340.0, height: 220.0)
}
}缩进快捷键:
⌃ + I自动缩进⌘ + [减少缩进⌘ + ]增加缩进
先直接在主视图的 ZStack 中编辑,再抽取为子视图 TitleView,注意观察各个修饰器的用法。
struct TitleView: View {
var body: some View {
VStack {
HStack {
Text("证书册")
.font(.largeTitle)
.fontWeight(.bold)
Spacer()
}
.padding()
Image("Background1")
Spacer()
}
}
}因为 TitleView 在实际显示中位于最底层,所以在主视图的 VStack 中,它应该第一个出现。
同样的,编辑并抽取 BottomCardView。因为要在最上层显示,所以,编码应该在 VStack 中的 CardView 之后进行。
struct BottomCardView: View {
var body: some View {
VStack(spacing: 20) {
Rectangle()
.frame(width: 40, height: 6)
.cornerRadius(3)
.opacity(0.1)
Text("Howard Ge 已经通过本机构《UI设计》课程学习并通过考核,特此证明。")
.multilineTextAlignment(.center)
.font(.subheadline)
.lineSpacing(4)
Spacer()
}
.padding(.top, 8)
.padding(.horizontal, 20)
.frame(maxWidth: .infinity) // 撑满
.background(Color.white)
.cornerRadius(30)
.shadow(radius: 20)
.offset(x: 0, y: 500)
}
}分别为 TitleView 和 BottomCardView 加上模糊效果 .blur(radius: 20)。现在,除去卡片之外的内容都有了模糊的效果。
补充知识:
SwiftUI 允许向父容器中添加不多于 10 个子视图,超过时编译不会通过。这也是抽取视图的原因之一吧。
看一下主视图代码部分,会发现由于抽取了子视图,主视图的代码内容很少,更多侧重在修饰器(即展示效果)上。如果要修改某个子视图,只需按住 ⌘点击呼出快捷菜单,选择 Jump to Definition 即可跳转到子视图的定义部分,非常方便。有点像拼乐高积木哈,先关注了每个零件,再关注如何组合。
- 不同于 CSS 等网页设计,SwiftUI 不是从最外面的容器开始设计,而是从每个部件开始,逐渐逐渐将整个界面丰富起来。这些部件在整个开发的过程中都是共享的。
- 将具有一致性的部件抽取为子视图,可以加强代码复用。这么觉着和 CSS 中的 class 还是很类似的。
- VStack 中各个部件的代码顺序与预览中的视觉层级顺序是相反的。
- 修饰器的应用
顺序是有讲究的。应用时可能需要反复实践。- 对容器部件使用修饰器,可能会影响到容器内所有的部件。
- 使用
.frame(maxWidth: .infinity)可将 VStack 在水平方向撑满屏幕(无论是什么设备)。- 修饰器:缩放、旋转、混合模式、模糊。
- 控件:rectangle
准备制作动画啦~~~