学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

Stanford CS193p - [1-3]

Developing Applications for iOS using SwiftUI

Total Lecture: 15

current: [1-3]

Getting Started with SwiftUI

behaves like a …

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import SwiftUI

struct ContentView: View {
var body: some View {
VStack() {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.orange)

Text("Hello CS193P!")
}
.padding()
}
}

ContentView结构体的行为类似于View,函数式编程关注的是功能、行为,而不是数据。面向对象编程的根源(root)是数据封装。函数式编程更像是行为封装。

1
2
3
4
5
6
7
8
9
10

struct ContentView: View {

var i: Int
var s: String

var body: some View {
...
}
}

IntString都是结构体,View不是一个结构体,是一种可以表现的东西,称为协议。

Computed Property

1
2
3
4
5
6
struct ContentView: View {

var body: some View {
...
}
}

变量的值没有存储在某个地方,是只读变量,是计算出来的。bodyvar,因为这里可能有变量,导致它在每次调用它时返回不同的东西。

some View

意味着这个变量的类型必须是世界上的任何结构,只要它的行为像View一样

creating instances of structs

1
2
3
Image(systemName: "globe")

Text("Hello CS193P!")

named parameters

systemName

parameter defaults

1
2
3
4
5
6
7
8
VStack(alignment: .leading, spcing: 20) {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.orange)

Text("Hello CS193P!")
}

1
2
3
4
5
6
7
8
VStack(){
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.orange)

Text("Hello CS193P!")
}

VStack() 后面大括号里实际上是一个函数。该函数返回一个View,完整的调用代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import SwiftUI

struct ContentView: View {
var body: some View {
VStack(content: {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.orange)

Text("Hello CS193P!")
})
.padding()
}
}

VStack是一个接受这个参数的试图。函数里面返回的是一个打包为TupleView

函数式编程,函数始终作为参数传递给其他事物

@ViewBuilder

View列表转换为一个TupleView组合视图的东西称为@ViewBuilder

TupleView(bag of Lego)

View modifier

.imageScale(),.foregroundColor(),.padding(),这些是函数,也叫View modifier,因为它的作用是在View上调用它,Image是一个行为类似于View的结构体,因此可以在其上调用这些函数。

View modifier scope

1
2
3
4
5
6
7
8
9
10
11
12
13
import SwiftUI

struct ContentView: View {
var body: some View {
VStack(content: {
Image(systemName: "globe")
Text("Hello CS193P!").padding()
})
.font(.largeTitle)
.foregroundColor(.orange)
.imageScale(.small)
}
}

VStack中的所有内容都获得了.font(.largeTitle).foregroundColor(.orange)修饰符。VStack、HStacks、Grid和其它装有乐高积木的东西,当你在它们上面做一些对整个东西来说没有实际意义的事情时,它们会把它传递给里面的东西。

.imageScale(.small)被应用于文本,而文本忽略了它,因为它不是图像。因此这提供了很大的灵活性,能够将这些东西放在外部,并让它们只应用于内部真正关心的东西。

More SwiftUI

Progres of Memorize Project

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import SwiftUI

struct ContentView: View {
var body: some View {
HStack {
CardView(isFaceUp: true)
CardView()
CardView()
CardView()
}
.foregroundColor(.orange)
.padding()
}
}

struct CardView: View {
var isFaceUp: Bool = false
var body: some View {
ZStack(content:{
if isFaceUp {
RoundedRectangle(cornerRadius: 12).foregroundColor(.white)
RoundedRectangle(cornerRadius:12).strokeBorder(lineWidth:2)
Text("👻").font(.largeTitle)
}
else{
RoundedRectangle(cornerRadius:12).fill()
}
})

}

}

some View

1
2
3
4
5
6
7
8
struct ContentView: View {
var body: Text {
HStack {
Text("hello")
Text("there")
}
}
}

Cannot convert return expression of type VStack<TupleView<Text,Text>> to return type Text

trailing closure syntax

1
2
3
ZStack(alignment: .center , content:{
...
})

ZStack是一个行为类似于View的结构体,有两个参数。任何创建对象或函数的最后一个参数是函数本身,这里是一个返回视图(TupleView)的函数,可以省略掉它的标签content

1
2
3
  ZStack(alignment:. center) {
...
}

alignment 默认值为center所以上面可简写成

1
2
3
  ZStack() {
...
}

这里()因为是尾随闭包语法,也可以删除,简写成

1
2
3
4
5
6
7
8
9
10
11
ZStack {
if isFaceUp {
RoundedRectangle(cornerRadius: 12).foregroundColor(.white)
RoundedRectangle(cornerRadius:12).strokeBorder(lineWidth:2)
Text("👻").font(.largeTitle)
}
else{
RoundedRectangle(cornerRadius:12).fill()
}
}

locals in @ViewBilder

1
2
3
4
5
6
7
8
9
10
11
12
ZStack {
var base: RoundedRectangle = RoundedRectangle(cornerRadius: 12)
if isFaceUp {
base.foregroundColor(.white)
base.strokeBorder(lineWidth:2)
Text("👻").font(.largeTitle)
}
else{
base.fill()
}
}

let VS var

1
let base: RoundedRectangle = RoundedRectangle(cornerRadius: 12)

视图是只读的,所以无论如何都无法改变它

根据是否需要改变事物来在let和var之间做出决定,请始终从let开始,如果你尝试改变它,编译器就会报错,把let想象成真正定义变量的方式,而var更像是标记你要更改的内容。因为当你阅读某人的代码时,你可以知道他们将要在代码的某个地方改变这个东西,本质上这只是一个常量。

Views are immutable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

struct CardView: View {
var isFaceUp: Bool = false
var body: some View {
ZStack{
let base: RoundedRectangle = RoundedRectangle(cornerRadius: 12)
if isFaceUp {
base.foregroundColor(.white)
base.strokeBorder(lineWidth:2)
Text("👻").font(.largeTitle)
}
else{
base.fill()
}
}
.onTapGesture {
isFaceUp = !isFaceUp
}
}
}

Cannot assign to property:’self’ is immutable

即使isFaceUp声明为var,可变只是在最开始而已

@State

@State var isFaceUp = false

创建一个指向isFaceUp的指针,用于保存isFaceUp,所以指针本身不会改变,它指向的东西可以改变,所以它满足View不能改变但isFaceUp可以改变

如果你有想要改变的变量,以便在View结构体中使用它,且只适用于临时状态,比如如果你在动画的中间,你想跟踪动画的开始或结束或类似的东西,它不是用来存储游戏状态的。

ViewBuilder can do

ViewBuilder中不能使用for,可以做三件事,条件、列表、局部变量

ForEach

这里进行for循环的方式是使用特殊的视图,称为ForEach,它是a bag of lego

1
2
3
ForEach(0..<4, id: \.self) { index in

}

这里有一个ViewBuilder,它实际上有一个参数index(arguments to closures)。

因为ViewBuilder是一个函数,函数可以有参数

ForEach会跟踪视图与index的匹配

Button

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
HStack{

Button(action:{
cardCount -= 1
},label:{
Image(systemName: "rectangle.stack.badge.minus.fill")
})
.imageScale(.large)
.font(.largeTitle)

Button("Remove Card"){

}
Spacer()
Button("Add Card"){
cardCount += 1
}
}

label后面的闭包是一个ViewBuilderButton可以制作成任何极其复杂的lego bag

为什么.font要应用于图像?
系统图像会尝试追踪它们附近事物的字体,它们尝试以内联方式工作,因此无论你将其设置为何字体,它们都会与之匹配,然后图像比例与之相关。

implicit return

1
2
3
4
5
6
7
8
var cards: some View {
return HStack {
ForEach(0..<cardCount,id: \.self) { index in
CardView(content: emojis[index])
}
}
.foregroundColor(.orange)
}

为什么这里可以省略returncards是一个计算属性,它返回HStack{},它不是ViewBuilderHStack中的内容是ViewBuilder,但是HStack本身不是,这里只是一个普通的函数,它只有一行代码,所以不需要return,这种称为隐式返回(适用于函数和计算属性)。

internal VS external parameter names

1
2
3
4
5
6
7
8
func cardCountAdjuster(by offset: Int, symbol:String) -> some View{
Button (action: {
cardCount += offset
}, label: {
Image(systemName: symbol)
})
.disabled(cardCount + offset < 1 || cardCount + offset > emojis.count)
}

有两个标签时by offset,第一个by是调用者使用的标签,第二个offset是在函数中使用,symbol既是外部名称又是内部名称

LazyVGrid

1
2
3
4
5
6
7
8
var cards: some View {
LazyVGrid(columns: [GridItem(.adaptive(minimum:120))]) {
ForEach(0..<cardCount,id: \.self) { index in
CardView(content: emojis[index])
}
}
.foregroundColor(.orange)
}

LazyVGrid不会将子视图堆叠在一起,而是将它们排列在一个具有一定列数的网格中。

LazyVGrid适用尽可能少的空间,HStack适用尽可能大的空间

Group

一个装有乐高玩具的袋子,称为Group,有点像ForEach,它只是一个容纳其它乐高的组,所以你可以对它们应用view modifier

1
2
3
4
5
6
7
8
9
10
11
12
ZStack{
let base: RoundedRectangle = RoundedRectangle(cornerRadius: 12)
Group {
base.fill(.white)
base.strokeBorder(lineWidth:2)
Text("👻").font(.largeTitle)
}
.opacity(isFaceUp ? 1 : 0)
base.fill().opacity(isFaceUp ? 0 : 1)

}

aspectRatio

1
2
3
4
5
6
7
8
9
10
var cards: some View {
LazyVGrid(columns: [GridItem(.adaptive(minimum:120))]) {
ForEach(0..<cardCount,id: \.self) { index in
CardView(content: emojis[index])
.aspectRatio(2/3,contenMode: .fit)
}
}
.foregroundColor(.orange)
}

.fit意思是将它放入可用空间

MVVM

MVVM (Design paradigm)

Separating “Logic and Data” from “UI”

we call this logic and data our Model

UI实际上就像模型所供给的可参数化的外壳

The UI is basically just a “parametrizable” shell that the Model feed and brings to life

Think of the UI as a visual manifestation of the Model

Connecting the Model to the UI

  1. the model just be an @State in a View
  2. the model only be accessable via a gatekeeper ViewModel class

Swift Type System

struct

class

protocol

“don’t care” type(aka generics)

enum

functions

struct and class

struct是函数式编程的基础

class是面向对象编程的基础

面向对象 VS 函数式

面向对象编程: 数据封装,封装现实世界概念或事物的数据,然后将功能与该数据相关联,通过将其全部封装到一个具有与该数据相关的函数的结构中。

它增加了继承,因为有些东西非常像其他东西,但有一些细微的修改,所以我们可以从它们中继承,然后调整它们成为它的特定实例或其他什么。

函数式编程: 行为封装,真正要做的就是描述事物的行为方式,而当谈到数据时,它就在幕后。在函数式编程中,我们真正想要的东西之一是可证明性(计算机科学中的概念),面向对象编程中无法实现,因为你不知道谁将拥有一个指向你的类的指针,而在函数式编程中,你可以,因为你总是传递其中的内容的副本,没人可以从外部干扰该函数

可证明性: 你希望能够获取一段代码并证明它无论发生什么,无论它处于什么环境或其他什么情况下都会做它会做的事情。

为什么要为视图模型使用一个类?

我们的视图模型是共享的,可能在许多不同的视图之间共享,许多视图都想获得模型

Generics

sometimes we just don’t caretype agnostic

1
2
3
4
struct Array<Element> {
...
func append(_ element: Element) { ... }
}

Usage

1
2
3
var a = Array<Int>()
a.append(5)
a.append(22)

Type Parameter

想象一下你有一个不关心的事情,你强迫它行为像(behaves like a)某种东西,这为你构建编程接口提供了很大的灵活性。

protocol

协议看起来像没有实现的类或结构体,它只是一种行为的声明描述,

A protocol is sort of a “stripped-down” struct/class

1
2
3
4
5
protocol Moveable {
func move(by: Int)
var hasMoved: Bool { get }
var distanceFromStart: Int { get set }
}

PortableThing now conforms to(aka “behaves like a”)Moveable

1
2
3
struct PortableThing: Moveable {
// must implement move(by:), hasMoved and distanceFromStart
}

what is a protocol used for?

  1. constrains and gains 既能限制事物,又能获得事物
  2. turning don’t cares into “somewhat cares”
1
struct Array<Element> where Element: Equatable

Functions as Types

functions are types

1
2
3
4
(Int,Int) -> Bool    // takes two Ints and returns a Bool
(Double) -> Void // takes a Double and returns nothing
() -> Array<String>
() -> Void

var foo: (Double) -> Void

func doSomething(what: () -> Bool)

这些都不包含参数名称,当你指定参数类型时,不包括参数名称

Usge:

1
2
3
4
5
6
7
8
9
10
11
12
var operation: (Double) -> Double


func suare(operand: Double) -> Double {
return operand * operand
}

operation = square
let result1 = operation(4)

operation = sqrt
let result2 = operation(4)

Closures

闭包又叫内联函数

1
2
3
ZStack(content:{
...//ViewBuilder
})

它的类型是一个不带参数并返回View的函数