第二章:SwiftUI基础知识, 使用堆栈布局视图

使用SwiftUI的声明性语法,创建、修改和组合视图,以便使用Scrumdinger的应用程序来管理会议。通过将一组视图排列在一起,创建会议计时器屏幕。随着您在模块中的进展,您将会反复回顾计时器屏幕,以便朝着最终设计的目标进行工作。
按照以下步骤开始您的新项目,或者打开已完成的项目,自行探索其中的代码。

第1节

创建项目

在整个模块中,通过创建Scrumdinger应用程序,您将学习应用程序开发的基础知识。在向应用程序添加新功能的过程中,您将探索Xcode和SwiftUI的基础知识。在本节中,您将创建Scrumdinger的Xcode项目。


SUI010-010-intro@2x.png

步骤1

使用iOS App模板创建一个新项目。


SUI010-010-010@2x.png

步骤2

在项目选项中,将产品命名为“Scrumdinger”,点击界面弹出菜单,并选择SwiftUI。
模板包含了一个根视图的起始文件ContentView.swift,以及定义应用程序入口点的文件ScrumdingerApp.swift。
提示:
如果您对Xcode还不熟悉,请了解主窗口的相关信息,并了解如何创建一个项目。


SUI010-010-020@2x.png

步骤3

选择一个保存项目的位置。


SUI010-010-030@2x.png

第2节

组合视图

视图定义了您的用户界面的一部分。它们是应用程序的构建块。通过将小而简单的视图组合在一起,您可以构建一个复杂的视图。在本节中,您将构建计时器屏幕的标题,以显示会议的已用时间和剩余时间。


SUI010-020-intro@2x.png

步骤1

打开ContentView.swift。
默认的SwiftUI视图文件声明了两个结构体。第一个结构体遵循View协议,该协议有一个要求:一个返回View的body属性。在body属性中,您描述视图的内容、布局和行为。第二个结构体为该视图定义了一个预览,以在画布上显示。

import SwiftUI
    
struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundColor(.accentColor)
            Text("Hello, world!")
        }
        .padding()
    }
}
    
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

您将对ContentView.swift进行重构,以为其指定一个与您的应用程序目的相关的名称。

步骤2

对ContentView结构体进行控制点击,选择Refactor > Rename,并将结构体重命名为MeetingView。
您还可以将预览结构体重命名为MeetingView_Previews,以保持命名的一致性。


WX20230626-112636@2x.png

步骤3

将现有body的内容替换为ProgressView,并使用占位数据初始化视图。
当您在项目导航器中选择一个SwiftUI文件时,画布会在编辑器旁边打开。画布会显示运行时视图的预览。

MeetingView.swift
import SwiftUI


struct MeetingView: View {
    var body: some View {
        ProgressView(value: 10, total: 15)
    }
}


struct MeetingView_Previews: PreviewProvider {
    static var previews: some View {
        MeetingView()
    }
}

步骤4

将进度视图的值从10更改为5,并观察画布中的进度视图更新。
您将使用进度视图来显示Scrum期间已经过的时间的百分比。进度视图还可以显示不确定的进度,比如当应用程序正在加载数据时。

MeetingView.swift
import SwiftUI


struct MeetingView: View {
    var body: some View {
        ProgressView(value: 5, total: 15)
    }
}


struct MeetingView_Previews: PreviewProvider {
    static var previews: some View {
        MeetingView()
    }
}

步骤5

按住Command键点击ProgressView,然后选择“Embed in VStack”。
堆栈将视图水平、垂直或前后分组。您可以使用堆栈来组合和层叠视图组件。

步骤6

在代码编辑器中创建一个HStack,点击库按钮,并将一个文本视图拖到HStack中,并设置值为"Seconds Elapsed"。
无论您使用源代码编辑器、画布、库或检查器来修改视图,您的代码都会保持更新。


WX20230626-113227@2x.png

步骤7

添加另一个值为"Seconds Remaining"的文本视图。

MeetingView.swift
import SwiftUI


struct MeetingView: View {
    var body: some View {
        VStack {
            ProgressView(value: 5, total: 15)
            HStack {
                Text("Seconds Elapsed")
                Text("Seconds Remaining")
            }
        }
    }
}


struct MeetingView_Previews: PreviewProvider {
    static var previews: some View {
        MeetingView()
    }
}

步骤8

将每个文本视图嵌套在一个VStack中。

步骤9

在第一个文本视图下方添加一个标题为"300"、系统图片为"hourglass.tophalf.fill"的标签。
该图像使用了SF Symbols 4中的一个。系统将这些符号视为字体,因此它们会根据用户设备的设置进行动态缩放。

MeetingView.swift
import SwiftUI


struct MeetingView: View {
    var body: some View {
        VStack {
            ProgressView(value: 5, total: 15)
            HStack {
                VStack {
                    Text("Seconds Elapsed")
                    Label("300", systemImage: "hourglass.tophalf.fill")
                }
                VStack {
                    Text("Seconds Remaining")
                }
            }
        }
    }
}


struct MeetingView_Previews: PreviewProvider {
    static var previews: some View {
        MeetingView()
    }
}

步骤10

在第二个文本视图下方添加一个标题为"600"、系统图片为"hourglass.bottomhalf.fill"的标签。

MeetingView.swift
import SwiftUI


struct MeetingView: View {
    var body: some View {
        VStack {
            ProgressView(value: 5, total: 15)
            HStack {
                VStack {
                    Text("Seconds Elapsed")
                    Label("300", systemImage: "hourglass.tophalf.fill")
                }
                VStack {
                    Text("Seconds Remaining")
                    Label("600", systemImage: "hourglass.bottomhalf.fill")
                }
            }
        }
    }
}


struct MeetingView_Previews: PreviewProvider {
    static var previews: some View {
        MeetingView()
    }
}

第3节

修改和样式化视图

现在您已经在标题中创建了基本视图,您将对会议计时器屏幕的其余部分进行原型设计和样式设置。您将添加内置修饰符来调整标题的外观。您还将创建额外的堆栈和视图,并开始添加控件。

您将通过调整视图的间距来开始样式化标题。

步骤1

在每个VStack之间添加一个spacer,以利用包含父视图中的可用空间。

步骤2

在包围“Seconds Elapsed”的VStack中添加前导对齐,并在包围“Seconds Remaining”的VStack中添加尾部对齐。
这些对齐方式覆盖了默认的居中对齐方式。某些系统使用左对齐和右对齐。SwiftUI使用前导对齐和尾部对齐来简化您的应用程序的本地化。
提示
您还可以通过选择VStack并在属性检查器中使用对齐选项来设置对齐方式。

MeetingView.swift
import SwiftUI


struct MeetingView: View {
    var body: some View {
        VStack {
            ProgressView(value: 5, total: 15)
            HStack {
                VStack(alignment: .leading) {
                    Text("Seconds Elapsed")
                    Label("300", systemImage: "hourglass.tophalf.fill")
                }
                Spacer()
                VStack(alignment: .trailing) {
                    Text("Seconds Remaining")
                    Label("600", systemImage: "hourglass.bottomhalf.fill")
                }
            }
        }
    }
}


struct MeetingView_Previews: PreviewProvider {
    static var previews: some View {
        MeetingView()
    }
}

步骤3

对文本视图添加.font(.caption)修饰符以减小文本的大小。
要自定义SwiftUI视图,您可以调用称为修饰符的方法。每个修饰符都返回一个新的视图。您可以在单个视图上使用多个修饰符。要链式调用修饰符,请将它们垂直堆叠。

现在标题具有适当的间距,您将为屏幕中央显示的圆形计时器视图创建一个占位符。

步骤4

添加一个具有边框的圆形形状作为占位符。
您将在以后的教程中完善圆形计时器视图的设计。

import SwiftUI


struct MeetingView: View {
    var body: some View {
        VStack {
            ProgressView(value: 5, total: 15)
            HStack {
                VStack(alignment: .leading) {
                    Text("Seconds Elapsed")
                        .font(.caption)
                    Label("300", systemImage: "hourglass.tophalf.fill")
                }
                Spacer()
                VStack(alignment: .trailing) {
                    Text("Seconds Remaining")
                        .font(.caption)
                    Label("600", systemImage: "hourglass.bottomhalf.fill")
                }
            }
            Circle()
                .strokeBorder(lineWidth: 24)
            HStack {
                Text("Speaker 1 of 3")
            }
        }
    }
}

通过创建页脚来完成会议计时器屏幕的原型设计。

步骤5

添加一个包含文本视图的HStack,显示"Speaker 1 of 3"。

步骤6

添加一个使用forward.fill图像作为标签的按钮。
暂时保留按钮的操作为空。在以后的教程中,您将使操作将计时器环推进到下一个发言人。

步骤7

在文本和按钮之间添加一个spacer。

步骤8

对顶层的VStack添加padding,将视图从边缘向内移动。
当您省略padding(_:)的参数时,SwiftUI会选择适合平台和展示上下文的默认值。

MeetingView.swift
import SwiftUI


struct MeetingView: View {
    var body: some View {
        VStack {
            ProgressView(value: 5, total: 15)
            HStack {
                VStack(alignment: .leading) {
                    Text("Seconds Elapsed")
                        .font(.caption)
                    Label("300", systemImage: "hourglass.tophalf.fill")
                }
                Spacer()
                VStack(alignment: .trailing) {
                    Text("Seconds Remaining")
                        .font(.caption)
                    Label("600", systemImage: "hourglass.bottomhalf.fill")
                }
            }
            Circle()
                .strokeBorder(lineWidth: 24)
            HStack {
                Text("Speaker 1 of 3")
                Spacer()
                Button(action: {}) {
                    Image(systemName: "forward.fill")
                }
            }
        }
        .padding()
    }
}
SUI010-030-080-preview.png

第4节

补充辅助功能数据

SwiftUI具有内置的辅助功能,因此您可以在很少的额外工作下获得辅助功能支持。例如,文本视图中的字符串内容会自动对设备功能(如VoiceOver)进行辅助访问。但有时,您可能希望补充推断的数据,以增强用户的辅助功能体验。

步骤1

忽略标题栏中HStack的子视图的推断辅助功能标签和值。
在接下来的几个步骤中添加补充数据可以改进辅助功能体验。

步骤2

为HStack添加辅助功能标签,传递一个有意义的标签名称。
考虑标签和值中是否有足够的上下文让用户理解元素的目的。在这种情况下,您可以通过添加一个标签来补充数据,让VoiceOver用户只需听取一个输出的标签,显示最重要的信息。

步骤3

为剩余时间添加辅助功能值到HStack。
因为您有意忽略了子视图的值,所以您需要为HStack添加一个值。否则,SwiftUI会自动推断子视图的值。

步骤4

为前进按钮添加辅助功能标签。
默认情况下,VoiceOver会读取图像的系统名称:forward.fill。当您添加辅助功能标签时,VoiceOver会先读取标签文本,然后是内在的辅助功能特征:“下一个发言者。按钮。”
使用这四个修饰符,您可以增强用户的辅助功能体验。

MeetingView.swift
import SwiftUI


struct MeetingView: View {
    var body: some View {
        VStack {
            ProgressView(value: 5, total: 15)
            HStack {
                VStack(alignment: .leading) {
                    Text("Seconds Elapsed")
                        .font(.caption)
                    Label("300", systemImage: "hourglass.tophalf.fill")
                }
                Spacer()
                VStack(alignment: .trailing) {
                    Text("Seconds Remaining")
                        .font(.caption)
                    Label("600", systemImage: "hourglass.bottomhalf.fill")
                }
            }
            .accessibilityElement(children: .ignore)
            .accessibilityLabel("Time remaining")
            .accessibilityValue("10 minutes")
            Circle()
                .strokeBorder(lineWidth: 24)
            HStack {
                Text("Speaker 1 of 3")
                Spacer()
                Button(action: {}) {
                    Image(systemName: "forward.fill")
                }
                .accessibilityLabel("Next speaker")
            }
        }
        .padding()
    }
}


struct MeetingView_Previews: PreviewProvider {
    static var previews: some View {
        MeetingView()
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容