Components and Visual Effects

教程地址:传送门 本篇主要内容是SwiftUI里面的组件和视觉效果(一些动画比如旋转,缩放,位移,扭曲,或者高斯模糊等)。

创建组件

接着上篇的内容,现在需要把卡片的代码封装成一个组件,以便于重复使用。

按住command,然后点下面的一个VStack,然后选择抽取子视图(Extract Subview)

ios swift 本地推送 ios swift ui_ios


然后就能看到,刚才的整个VStack被抽取成为一个单独的struct,并被抽取到当前结构体的下方:

ios swift 本地推送 ios swift ui_缩放_02


这个时候上面和下面的ExtractedView都是处于高亮状态,修改上面,下面也会跟着改,这里改成CardView:

ios swift 本地推送 ios swift ui_ios swift 本地推送_03


抽取组件的时候,要特别注意的一点事,抽取出来的组件将可以在整个应用编写过程中使用,而无需导入包之类的操作,所以要给抽取的组件一个尽可能独特的名字。

上面的背景卡片,因为要重复使用, 抽成组件也是必要的,这里把上面的背景卡片抽取成BackCardView:

ios swift 本地推送 ios swift ui_ci_04


这里就能看出组件化的必要性了,可以使主界面看上去很简洁,还可以复用。

modifier顺序的影响

SwiftUI里面,Modifier的编写顺序是很重要的,因为modifier无论表面还是本质都是调用方法,调用一个modifier返回self,然后又可以连续调用其他modifier。

而Flutter修改属性的方式是用可选的构造器参数,所以无论什么顺序都是都所谓的。在这一点上,我感觉Flutter的方式比较好。

这里的例子是调用conerRadius会影响Shadow效果。前面说过,conerRadius会裁剪边框,阴影实质上属于组件边界之外的,如果conerRadius在shadow之后调用,会将阴影效果直接裁减掉:

ios swift 本地推送 ios swift ui_缩放_05


上面的例子中可以看到BackCardView的阴影效果消失了(被裁剪掉了)。因为这个地方需要多个背景卡片,所以需要的位移是不一样的,所以要把**位移(offset)**这个modifier放到上面来(当然也可以把位移的参数设置成BackCardView的参数,然后在使用BackCardView的时候传入位移参数):

ios swift 本地推送 ios swift ui_ci_06


多张卡片,只要复制粘贴,然后修改offset的参数就行了:

ios swift 本地推送 ios swift ui_ios_07

视觉效果

只是让卡片堆叠起来还是不够的,由近及远,卡片的大小有一些变化,层次感会更好,这时候就要用到缩放效果(scaleEffect)

func scaleEffect(_ s: CGFloat, anchor: UnitPoint = .center) -> some View

两个参数中,s是缩放的比例,小于1为缩小,大于1为放大。anchor参数为缩放的锚点,默认为中心。

ios swift 本地推送 ios swift ui_ci_08


可以看到三张堆叠在一起的卡片渐次变小。但是尺寸的变化有点突然,最上面的卡片比第二张大的多,但是第二张比最下面一张大的没那么明显。

因为附加了缩放效果,其实这个时候三张卡片的尺寸完全可以设定成一样的,大小由缩放的参数来决定:

struct BackCardView: View {
    var body: some View {
        VStack {
            Spacer()
        }
        .frame(width : 340,height : 220)
        .background(Color.blue)
        .cornerRadius(20)
        .shadow(radius: 20)
        
    }
}

这次看起来效果就缓和得多:

ios swift 本地推送 ios swift ui_ios swift 本地推送_09


接下来给背景卡片添加一个角度,要用到旋转效果(rotationEffect)

func rotationEffect(_ angle: Angle, anchor: UnitPoint = .center) -> some View

上面是rotationEffect函数的定义,参数有两个,angle是一个Angle类型的变量,很容易理解这是一个角度值,第二个参数是anchior,类型为UnitPoint,按照字面意思来解读,可以知道这是旋转的锚点,即旋转的中心点,默认值为中心点。

将rotationEffect 分别应用于两个背景卡片:

ios swift 本地推送 ios swift ui_缩放_10


这里的angle参数是使用.degree(10)来生成的。这是swift里面的一个简便写法,因为在函数定义的时候,angle参数就确定为Angle类型,所以.degree(10)就相当于调用了Angle.degree(10)。

如果要表现得更有层次感,这个时候就要用到3D旋转效果(rotation3DEffect)

ios swift 本地推送 ios swift ui_ios swift 本地推送_11


函数的第一个参数依然是angle角度,第二个参数是旋转轴,因为是3D效果,所以需要三个参数。

为了看上去更自然,要用到渲染模式(blendMode)

func blendMode(_ blendMode: BlendMode) -> some View

只有一个类型为枚举类型BlendMode的参数。

这里使用的是hardLight:

ios swift 本地推送 ios swift ui_ios swift 本地推送_12


跟上面的offset同理,这里的背景卡片要定制不同的背景,需要把一些与背景相关的modifier挪到上面:

ios swift 本地推送 ios swift ui_阴影效果_13


改一下颜色以区分不同的卡片:

ios swift 本地推送 ios swift ui_ios_14


然后要创建一个卡片的具体介绍界面。效果图如下:

ios swift 本地推送 ios swift ui_ios_15

在根视图的ZStack最底层写一个Text,然后放入HStack,并且嵌套布局,使文字显示在左上角:

ios swift 本地推送 ios swift ui_缩放_16


在HStack和Spacer中间插入一张背景图,然后抽取为TitleView:

ios swift 本地推送 ios swift ui_ci_17


在CardView上层,创建一个介绍卡片具体内容的Text:

ios swift 本地推送 ios swift ui_缩放_18


用VStack包裹起来,然后改一下背景色,增加一个内边距:

ios swift 本地推送 ios swift ui_ios_19


然后是圆角和阴影:

ios swift 本地推送 ios swift ui_阴影效果_20


加一个Spacer,把文字推到最上面:

ios swift 本地推送 ios swift ui_ci_21


用偏移(offset)将将这个界面推下来:

ios swift 本地推送 ios swift ui_ios_22


使简介的文字居中对齐,用到多行文字对齐(multilineTextAlignment)

ios swift 本地推送 ios swift ui_缩放_23


改一下字体大小(font)和行距(lineSpacing)

ios swift 本地推送 ios swift ui_ios_24


然后是绘制上面的一个深色小长条。

直接可以用长方形(Rectangle),然后限制尺寸:

ios swift 本地推送 ios swift ui_ci_25


再加上圆角(conerRadius),改一下透明度(opacity)

ios swift 本地推送 ios swift ui_ios_26


改一下内边距(padding)

ios swift 本地推送 ios swift ui_ci_27


使用重载的frame来保证界面被渲染成最大宽度,然后给VStack的子View加入间隙:

ios swift 本地推送 ios swift ui_ios_28


然后将这个VStack抽取为BottomCardView:

ios swift 本地推送 ios swift ui_阴影效果_29


给TitleView和BottomCardView添加高斯模糊(blur),可以使背景虚化:

ios swift 本地推送 ios swift ui_阴影效果_30


下一篇介绍动画。