第三章:SwiftUI 构建CardView,一个列表视图cell

第一部分

创建颜色主题

为了在应用程序中创建一致的外观,您将创建一个主题,其中包含两个对比的颜色属性。您将使用主颜色作为视图的背景颜色,使用强调颜色作为视图的文本颜色。


SUI020_010-intro@2x.png

注意:

请确保您使用的是本页面顶部链接处提供的起始项目。下面的步骤使用该项目中定义的颜色。

步骤1

在项目导航器中,创建一个名为Models的新组。

步骤2

在Models组中,创建一个名为Theme.swift的新的Swift文件。

步骤3

导入SwiftUI框架而不是Foundation框架。
尽管在本节中您并没有创建一个视图,但您可以从SwiftUI框架中添加颜色属性。SwiftUI将颜色视为可以直接添加到视图层次结构中的视图实例。

步骤4

创建一个名为Theme的枚举,其原始值类型为String。
Swift会自动创建与每个添加到Theme中的case名称匹配的字符串。
注意事项
编译器会生成错误,因为枚举必须有case来声明原始类型。您将在下一步中修复此错误。
起始项目包括资源目录,并为每个颜色定义了RGBA值。

步骤5

为资产目录中的Themes文件夹中列出的每种颜色添加case。
资源目录中的命名颜色必须与代码中的引用匹配,因此请确保正确拼写每个case的名称。

步骤6

添加一个名为accentColor的Color属性,根据self的值返回.black或.white。
强调颜色为主题的主色提供了高对比度的补充,确保您的视图保持可访问性。

步骤7

添加一个名为mainColor的Color属性,使用枚举的rawValue创建一个颜色。
此属性从资源目录中初始化一个颜色。

Theme.swift
import SwiftUI


enum Theme: String {
    case bubblegum
    case buttercup
    case indigo
    case lavender
    case magenta
    case navy
    case orange
    case oxblood
    case periwinkle
    case poppy
    case purple
    case seafoam
    case sky
    case tan
    case teal
    case yellow
    
    var accentColor: Color {
        switch self {
        case .bubblegum, .buttercup, .lavender, .orange, .periwinkle, .poppy, .seafoam, .sky, .tan, .teal, .yellow: return .black
        case .indigo, .magenta, .navy, .oxblood, .purple: return .white
        }
    }
    var mainColor: Color {
        Color(rawValue)
    }
}

Section 2

创建每日站会模型

DailyScrum模型将包含以下四个属性,均为简单值类型:title、attendees、lengthInMinutes和theme。因为DailyScrum主要用于携带值数据,所以您将它声明为结构体,使其成为值类型。

Step 1

在Models组中添加一个名为DailyScrum的新Swift文件。

Step 2

创建一个DailyScrum结构体,包含title、attendees、lengthInMinutes和theme属性。

Step 3

添加一个扩展,提供一些示例数据。

DailyScrum.swift
import Foundation


struct DailyScrum {
    var title: String
    var attendees: [String]
    var lengthInMinutes: Int
    var theme: Theme
}


extension DailyScrum {
    static let sampleData: [DailyScrum] =
    [
        DailyScrum(title: "Design",
                   attendees: ["Cathy", "Daisy", "Simon", "Jonathan"],
                   lengthInMinutes: 10,
                   theme: .yellow),
        DailyScrum(title: "App Dev",
                   attendees: ["Katie", "Gray", "Euna", "Luis", "Darla"],
                   lengthInMinutes: 5,
                   theme: .orange),
        DailyScrum(title: "Web Dev",
                   attendees: ["Chella", "Chris", "Christina", "Eden", "Karla", "Lindsey", "Aga", "Chad", "Jenn", "Sarah"],
                   lengthInMinutes: 5,
                   theme: .poppy)
    ]
}

Section 3

创建卡片视图

CardView将总结DailyScrum模型的数据,并显示标题、参与人数和持续时间。您将从较小的视图中组合CardView,每个视图显示DailyScrum结构中的一部分数据。
您将更新CardView_Previews结构,以便在开发视图时获得即时的可视反馈。


SUI020_030-intro@2x.png

Step 1

添加一个名为CardView的新的SwiftUI视图文件。
如果预览被暂停了,您可以恢复它,以观察您在代码中进行的更改在预览中的反映。

Step 2

添加一个类型为DailyScrum的常量scrum。

注意

编译器会生成一个错误,因为CardView()初始化程序现在需要一个DailyScrum参数。您将在下一步中解决此问题。

Step 3

在CardView_Previews中,创建一个名为scrum的静态变量,并将其传递给CardView的初始化程序。

Step 4

使用scrum.theme.mainColor为预览指定一个背景颜色。

Step 5

使用previewLayout修饰符将预览的宽度设置为400,高度设置为60。
设置预览布局和背景颜色将CardView呈现为它将在List视图中显示的样子。在下一个教程中,您将将该视图嵌入到列表中。

Step 6

将画布设置从Live更改为Selectable。

Step 7

更新Text视图以显示scrum的标题,并将字体样式设置为headline。

Step 8

将Text视图嵌入到具有leading对齐的VStack中。
VStack在垂直线上排列子视图,并使用对齐参数将视图沿水平轴的位置。

Step 9

在Spacer后面添加一个HStack,其中嵌套了一个Label用于显示与会人数。

注意

Label和Image对于SF Symbol使用稍微不同的参数标签。在使用堆栈排列视图中创建的Image使用的是Image(systemName:),而不是Label(:systemImage:)。

Step 10

在HStack中添加另一个Label,并将其系统图像设置为“clock”。

Step 11

修改标签以显示会议持续时间。
您可以使用属性检查器自定义视图,代码会相应更新。或者您可以直接在代码中更改属性。在两种情况下,预览都会更新以反映您的更改。

Step 12

在标签之间添加一个Spacer,以显示围绕视图的信息。

CardView.swift
import SwiftUI


struct CardView: View {
    let scrum: DailyScrum
    var body: some View {
        VStack(alignment: .leading) {
            Text(scrum.title)
                .font(.headline)
            Spacer()
            HStack {
                Label("\(scrum.attendees.count)", systemImage: "person.3")
                Label("\(scrum.lengthInMinutes)", systemImage: "clock")
            }
        }
    }
}


struct CardView_Previews: PreviewProvider {
    static var scrum = DailyScrum.sampleData[0]
    static var previews: some View {
        CardView(scrum: scrum)
            .background(scrum.theme.mainColor)
            .previewLayout(.fixed(width: 400, height: 60))
    }
}

第四节:美化卡片

卡片视图显示有关每日Scrum的信息。在这一部分中,您将为卡片视图添加样式,突出显示最重要的信息,并修改视觉组件,以确保在明亮和暗黑模式下文本和背景视图之间有足够的对比度。

Step 1

给会议时长标签添加20个点的尾部填充。
提示
您还可以在视图的.leading、.top和.bottom边缘添加填充。

Step 2

将HStack中的所有字体样式设置为标题。
在标签和图像中使用的SF Symbols会随着字体权重和大小的变化进行合理的缩放和对齐。

Step 3

给VStack添加填充,使所有内容从边缘处靠近。

Step 4

将Xcode预览模式从"Selectable"更改为"Color Scheme Variants",以在明亮和暗黑外观中呈现视图。
在暗黑模式中,默认的浅色文本与黄色背景之间的对比度不足。

Step 5

使用scrum.theme.accentColor设置foregroundColor。
在Theme.swift中定义的accentColor变量会根据明亮或暗黑主题返回黑色或白色。

CardView.swift
import SwiftUI


struct CardView: View {
    let scrum: DailyScrum
    var body: some View {
        VStack(alignment: .leading) {
            Text(scrum.title)
                .font(.headline)
            Spacer()
            HStack {
                Label("\(scrum.attendees.count)", systemImage: "person.3")
                Spacer()
                Label("\(scrum.lengthInMinutes)", systemImage: "clock")
                    .padding(.trailing, 20)
            }
            .font(.caption)
        }
        .padding()
        .foregroundColor(scrum.theme.accentColor)
    }
}


struct CardView_Previews: PreviewProvider {
    static var scrum = DailyScrum.sampleData[0]
    static var previews: some View {
        CardView(scrum: scrum)
            .background(scrum.theme.mainColor)
            .previewLayout(.fixed(width: 400, height: 60))
    }
}

Section 5

自定义标签样式

接下来,您将创建一个标签样式,将会议时长和时钟图标水平堆叠在一起。通过使用LabelStyle协议,您可以通过在多个视图中重用相同的标签样式来创建一致的设计。

Step 1

创建一个名为TrailingIconLabelStyle.swift的新的Swift文件。

Step 2

导入SwiftUI。

Step 3

创建一个名为TrailingIconLabelStyle的新结构体,并符合LabelStyle协议。
提示
如果您不想创建自定义的标签样式,您可以使用内置的标签样式之一,它们使用系统标准布局显示图标、标题或两者。

注意

编译器会生成错误,因为该结构体不符合LabelStyle协议的要求。

Step 4

创建一个空的makeBody(configuration:)函数。
系统会在使用此样式作为当前标签样式的视图层次结构中的每个Label实例中调用此方法。

注意

现在您的项目可以编译通过,没有错误了。

Step 5

添加一个HStack。

Step 6

在HStack内部添加configuration.title和configuration.icon。
configuration参数是一个LabelStyleConfiguration,其中包含图标和标题视图。这些视图表示标签的图像和标签文本。

Step 7

在LabelStyle上添加一个扩展,创建一个名为trailingIcon的静态属性。
因为您将标签样式定义为静态属性,所以可以使用前导点语法来调用它,这样可以使您的代码更易读。

Step 8

在CardView.swift文件中的lengthInMinutes标签上,用新的标签样式替换padding。
现在,时钟图标对齐到尾部,与参与人数标签对齐。
在视觉上,CardView已经完成,您可以在列表中显示它。在这之前,您将使用无障碍修饰符来改善VoiceOver用户的体验。

TrailingIconLabelStyle.swift
import SwiftUI


struct TrailingIconLabelStyle: LabelStyle {
    func makeBody(configuration: Configuration) -> some View {
        HStack {
            configuration.title
            configuration.icon
        }
    }
}
CardView.swift
import SwiftUI


struct CardView: View {
    let scrum: DailyScrum
    
    var body: some View {
        VStack(alignment: .leading) {
            Text(scrum.title)
                .font(.headline)
            Spacer()
            HStack {
                Label("\(scrum.attendees.count)", systemImage: "person.3")
                Spacer()
                Label("\(scrum.lengthInMinutes)", systemImage: "clock")
                    .labelStyle(.trailingIcon)
            }
            .font(.caption)
        }
        .padding()
        .foregroundColor(scrum.theme.accentColor)
    }
}


struct CardView_Previews: PreviewProvider {
    static var scrum = DailyScrum.sampleData[0]
    static var previews: some View {
        CardView(scrum: scrum)
            .background(scrum.theme.mainColor)
            .previewLayout(.fixed(width: 400, height: 60))
    }
}

Section 6

使卡片视图具有可访问性

现在CardView在视觉上已经布局完成,您将使其对所有用户具有可访问性。您将为您的视图添加无障碍修饰符,以使VoiceOver更容易理解和导航您的界面。
CardView包含一个带有时钟图像和表示会议时长的数字的标签。它还包含一个人物图像和与会人数。
您将添加使用字符串插值的无障碍标签,以使标签成为视图的有意义描述。VoiceOver将读取“4名与会人员”和“10分钟会议”。

Step 1

在显示scrum标题的文本视图上应用accessibilityAddTraits(.isHeader)修饰符。
此修饰符通过读取scrum标题后跟“标题”来帮助传达视图的信息架构。

Step 2

使用accessibility修饰符为HStack中的第一个Label添加描述其内容的标签。

Step 3

为HStack中的第二个Label添加类似的accessibility修饰符。

CardView.swift
import SwiftUI


struct CardView: View {
    let scrum: DailyScrum


    var body: some View {
        VStack(alignment: .leading) {
            Text(scrum.title)
                .font(.headline)
                .accessibilityAddTraits(.isHeader)
            Spacer()
            HStack {
                Label("\(scrum.attendees.count)", systemImage: "person.3")
                    .accessibilityLabel("\(scrum.attendees.count) attendees")
                Spacer()
                Label("\(scrum.lengthInMinutes)", systemImage: "clock")
                    .accessibilityLabel("\(scrum.lengthInMinutes) minute meeting")
                    .labelStyle(.trailingIcon)
            }
            .font(.caption)
        }
        .padding()
        .foregroundColor(scrum.theme.accentColor)
    }
}


struct CardView_Previews: PreviewProvider {
    static var scrum = DailyScrum.sampleData[0]
    static var previews: some View {
        CardView(scrum: scrum)
            .background(scrum.theme.mainColor)
            .previewLayout(.fixed(width: 400, height: 60))
    }
}

下一个教程将向您展示如何在SwiftUI List中使用CardView。您将了解到SwiftUI如何通过将较小的视图组合在一起来轻松构建一个大而复杂的视图,就像您刚刚创建的视图一样。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容