SwiftUI 对于自定义组件的封装和使用都非常友好,今天我们将以一个可横向滚动的卡片视图为例,介绍一下如何创建自定义组件,如何组装多个自定义组件,以及一点点动画相关的内容。


1. 预览

Xnip2019-08-06_15-00-38

2. 构建卡片组件

通过分析结构,我们可以将卡片视图分解为以下 3 个部分:

我们将照此结构实现CardView.swift,同时我们还会给 Card 添加一个触摸事件,在触摸 Card 时实现展开详情的动作。

1). 定义 @State 变量

为了实现状态切换时重新 layout,我们需定义几个变量:

@State var fold = true /// 是否展开 Card

var image: String = "" /// 卡片图片名称,供外部传入

var text: String = ""  /// 卡片描述文本,供外部传入

2). Image

首先实现 Image 效果。

Image(image)
    .resizable()
    .frame(height: 200, alignment: .center)
    .aspectRatio(contentMode: .fit)
  • .resizable(): 使 Image 能够自适应 frame。

3). 描述 Text

使用一个 HStack 包裹描述 Text。

HStack {
    Text(text)
        .fontWeight(.light)
        .font(.system(size: 13))
        .lineLimit(fold ? 3 : nil)
        .padding()
        .scaledToFit()
    Spacer()
}
  • lineLimit: 设置为 nil 时即不限行数。
  • scaledToFit: Text 在改变行数时能自适应更改高度。

4). 底部 Text

if fold {
    HStack {
        Spacer()
        Text("Read more")
        .font(.system(size: 14))
        .foregroundColor(.red)
    }
    .padding()
}

通过变量fold,实现在展开 Card 时隐藏底部 “Read more” 描述。

5). 动画和触摸手势

为 Card 添加.animation动画,使其在切换状态时能获得平滑的动画效果:

.animation(.easeInOut)

通过.onTapGestur为 Card 添加触摸手势

.onTapGesture {
    self.fold.toggle()
}

最后使用 VStack 将 3 个部分组装起来,完整代码如下:

VStack {
    Image(image)
        .resizable()
        .frame(height: 200, alignment: .center)
        .aspectRatio(contentMode: .fit)
        
    HStack {
        Text(text)
            .fontWeight(.light)
            .font(.system(size: 13))
            .lineLimit(fold ? 3 : nil)
            .padding()
            .scaledToFit()
        Spacer()
    }
    
    if fold {
        HStack {
            Spacer()
            Text("Read more")
            .font(.system(size: 14))
            .foregroundColor(.red)
        }
        .padding()
    }
}
.frame(width: fold ? 280 : 300)
.background(Color(white: 0.99))
.cornerRadius(10)
.shadow(color: .gray, radius: 10, x: 0, y: 3)
.animation(.easeInOut)
.onTapGesture {
    self.fold.toggle()
}

使用 Xcode 预览效果如下:

3. 构建滚动组件

首先实现一个横向的ScrollView,其中使用一个HStack包裹我们的 Card 卡片:

ScrollView(.horizontal, showsIndicators: false) {
    Spacer()
    HStack(alignment: .center, spacing: 10) {
        Spacer()
        CardView(image: "card_cover1", text: lorem).padding()
        CardView(image: "card_cover2", text: lorem).padding()
        CardView(image: "card_cover3", text: lorem).padding()
        Spacer()
    }
    Spacer()
}

使用 Xcode 预览效果如下:

4. 最终效果