Lecture 1&2: Getting started with SwiftUI & Learning more about SwiftUI

Lecture 1&2: Getting started with SwiftUI & Learning more about SwiftUI

#学习/swift/stanford/2021

YouTube
YouTube

又是一年一度的Stanford春季iOS开发课程 - CS193P,还是熟悉的Paul老师,还是熟悉的纸牌游戏。

虽然每年的Standford CS193的课程内容都差不多,主要是一个小的DemoApp入手,面向一些有一定计算机基础的大学生们,但是每年Paul老师会根据每年语言的新特性有一些不同的介绍,这套课程在我看来,除了一些刚刚入门的iOS开发者,对于一些已经使用Swift作为主要语言开发App工作的同学们也会有很大的帮助,从中可以学到很多设计模式及Swift语言的一些语法特性。对即使用了很久的Swift的同学也会收获到一些新的知识。

开始

这次课程中的前两节主要用来介绍去年才刚刚出现的SwiftUI,因为前两节中的课程基本都是SwiftUI的简单介绍,所以这里我把两节课并在一起。

因为这套课程同时也是针对Standford的学生们的,并且因为疫情的原因,他们也只能在家上课,所以课程开始的时候Paul老师还是会介绍一些课堂及作业巴拉巴拉的东西。

接下来会简单介绍一下这套课程需要完成的一个叫做Memorize的Demo,接下来是一些环境搭建,以及Xcode的大概的使用方法,以及各个区域的功能,以及Xcode针对于SwiftUI,和选择了SwiftUI App类型的Life Cycle的一些改变(Main函数放到了Memorize.swift中)等等,大概到33分钟左右开始介绍SwiftUI。

初识SwiftUI

[image:D648C9CD-A226-4993-AAD3-13D4527F2E5A-15524-0001F77CE8081435/Screen Shot 2021-06-29 at 10.42.02 PM.png]
创建一个使用SwiftUI,并且选择LifeCycle为SwiftUI App的项目之后,可以目录下看到两个.swift后缀的文件:

*App.swift

和使用UIKit 的声明周期会自动生成AppDelegate.swift和SceneDelegate不同,使用SwiftUI的生命周期只会创建一个名为App.swift的文件,其中的为创建App时给App起的名称。

1
2
3
4
5
6
7
8
9
10
11
12
import SwiftUI
// @main标记这个文件是App的入口
@main
struct MemorizeApp: App {
var body: some Scene {
WindowGroup {
// 这里指的就是另外一个swift文件,ContentView.swift,用来描述外观的文件
ContentView()
}
}
}

ContentView.swift

从上边的文件中也能看的出来,这个文件就是用来写UI部分的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import SwiftUI
// 真正用来绘制的ContentView
struct ContentView: View {
var body: some View {
Text("Hello, world!")
.padding()
}
}
// 用来预览的view,用来连接SwiftUI代码与旁边的Preview
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

some

some是swift5.1新推出的特性,同时some在SwiftUI中也起到了至关重要的作用。

这里老师将一个个view比喻成一个个乐高,我们的App就是一整个大的乐高套装,假设这个乐高套装是一个乐高房子,那么乐高房子中有厨房,有卧室,有餐厅等等,而每个乐高屋子里又有很多小的乐高组件,像是电器,橱柜等等。然而当我们创建一个个乐高椅子之后,再和一个乐高餐桌组合起来,就会变成一个乐高餐桌椅套装。然后一个个乐高套装组成了一个乐高厨房,然后厨房和其他屋子组合成一个乐高房子,一个个乐高房子可以组成一个乐高街区,最后组成一个乐高宇宙。

而这里的some View,并不是明确的表示返回的是一个View,用some修饰了之后,表示在编译阶段,告诉编译器,嘿,你去帮我弄清楚我是什么类型,在编译的时候替换他。比如例子中实际return了一个Text,编译器看到之后,就用Text替代了some View。

那么我们为什么不直接将var body: some View干脆写成var body: Text? 这里有两个原因,一是外层的ContentView要求返回的是一个View,所以他不会说让你直接返回一个Text。二是当我们的代码变得越来越复杂时,这里不一定就是返回一个Text,而有可能是更多View的组合。而我们不想要弄清楚每一个到底是返回什么类型,然后在声明body时指定他,这些交给编译器去做就可以了。

接下来老师通过对Text进行一些UI配置来说明为什么不能指定body的类型。时间段大概是从55分钟开始。感兴趣的同学可以从这里开始看看。

SwiftUI的一些简单使用

Lecture1的后半部分和Lecture2的前边一小部分基本上就在介绍如何使用SwiftUI,并介绍了一些属性,这里跟着视频往下看就可以了。都是一些比较基础的点,没有什么可以提炼出来讲的部分。

@State

因为SwiftUI中的struct实际上是不可变的,所以即使在一个strcut中创建了一个属性,他仍然是不可变的。这时候如果想要修改自己创建的值,就会报变量不可变的错误,这时候就需要用到@State这个关键字。

通过@State修饰过的属性就已经不再是一个单纯的值本身了,它包含了这个值的getter和setter。每当这个值发生变化时,SwiftUI都会重新计算body,然后重新渲染ContentView。

不过因为@State是用于SwiftUI的内部属性,实际上并不能通过外部修改传递,所以在真正开发App的设计模式下,比如MVVM,是不会经常使用到@State用来存储变量的。只有在一些View自己的操作导致属性改变下才可能用到,比如记录一些移动、捏合手势的状态等等。

ForEach

SwiftUI中的ForEach与Swift中的forEach(_:)还是有些不同的,其中前者是一个Structure,而后者是Array的一个Instance Method

那么SwiftUI中的ForEach是用来做什么的呢?就像视频中的场景一样,当我们需要从一个集合(比如数组)中,生成多个View,且用到的都是集合中的内容时,就可以使用ForEach了,除此之外,ForEach还可以解决我们SwiftUI中的ViewBuilder无法传入参数的问题。

比如之前我们生成4张卡片用了:

1
2
3
4
CardView(content: emojis[0])
CardView(content: emojis[1])
CardView(content: emojis[2])
CardView(content: emojis[3])

而在使用ForEach之后,就可以直接使用:

1
2
3
ForEach(emojis, id: \.self, content: { emoji in
CardView(content: emoji)
})

还有一个需要注意的是,在使用ForEach的时候,因为他需要为每一个集合的元素创建一个View,而当我们需要对view进行操作时,要找到唯一的那个view,这样他才能找到是哪个View发生了改变,才能去获取并渲染他,所以我们使用的集合必须要是一个遵循identifiable协议的集合,并且当中需要有一个KeyPath为id的参数,这个id将被设为是集合成员的id,使用时ForEach将使用id区分集合中的成员。

比如当我们在使用时,如果将id传递为\.self,即表示将自己设置为id,所以✈️的id则为✈️。

当然如果我们的数组是一个结构体,也可以使用结构体中的某一个参数来作为id,前提是这个id必须是独一无二的。比如如下的结构体。

1
2
3
4
5
6
7
8
9
struct Song {
var name: String
var singer: String
}
var songs = [Song(name: "温柔", singer: "五月天"),
Song(name: "知足", singer: "五月天"),
Song(name: "晴天", singer: "周杰伦"),
Song(name: "江南", singer: "林俊杰")]

这里,我们可以将\.name,作为我们的id,而不能使用\.singer

当然我们还可以通过Range来只使用集合中的一部分,比如之前的CardView,我们就可以改成这样:

1
2
3
ForEach(emojis[0..<3], id: \.self, content: { emoji in
CardView(content: emoji)
})

SF Symbols

SF Symbols - Apple Developer
SF Symbols是Apple提供的一个内置的图标库,打开之后就是下边这个样子,你可以通过分类或者关键字搜索找到你想要的图标。
[image:ACEE3E4C-884F-4375-88B3-A354C1441CC6-15524-00031024FBD8657A/Screen Shot 2021-07-03 at 5.48.11 PM.png]
找到之后,选中这个图标,你就能够看到他的一些详细信息(如果没有,点击一下右上角的 i)。
[image:C8776BF8-F1B6-43EC-B9CA-6D6B2445E58C-15524-0003104C5783575A/Screen Shot 2021-07-03 at 5.51.00 PM.png]
从这里的信息可以看到他支持的系统级版本,以及他的名称。然后在SwiftUI中我们只需要这样,就可以使用了:

1
Image(systemName: "plus.circle")

结束

Lecture2的后边20几分钟显示回顾了一下我们这两节课的知识。大概从1:01:10~1:08:20 。然后接下来介绍了LazyVGrid,Scrollview等几个SwiftUI中比较复杂的View类型的使用,另外介绍了strokestrokeBorder的不同之处。这一部分还是蛮值得看的。

以上就是StandFord2021 CS193P Lecture1 和 Lecture2的全部内容了。每次看StandFord pual老师的课程还是都能学到一些新的东西的。

本文章为个人学习过程中的记录使用,也许会有一些部分有失偏差,如果有哪里有问题,欢迎大佬们批评指正。∠(°ゝ°)

参考文档

some

https://stackoverflow.com/questions/56433665/what-is-the-some-keyword-in-swiftui
SwiftUI 和 Swift 5.1 新特性(1) some + 协议名称作为返回类型
What’s this “some” in SwiftUI?. An Intuitive Explanation of The New… | by Mischa Hildebrand | Medium

@State

Apple Developer Documentation
关于 SwiftUI State 的一些细节 | OneV’s Den
What is the @State property wrapper? - a free SwiftUI by Example tutorial

ForEach

Apple Developer Documentation
Apple Developer Documentation
SwiftUI 的 ForEach. SwiftUI 提供 ForEach 型別幫助我們將集合裡的東西合併成一個… | by 彼得潘的 iOS App Neverland | 彼得潘的 Swift iOS App 開發問題解答集 | Medium
https://www.hackingwithswift.com/quick-start/swiftui/how-to-create-views-in-a-loop-using-foreach