# SwiftUI_Study **Repository Path**: jxzy999/swift-ui_-study ## Basic Information - **Project Name**: SwiftUI_Study - **Description**: SwiftUI_Study的学习记录 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2021-10-28 - **Last Updated**: 2024-07-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SwiftUI学习项目 根据斯坦福大学课程,学习SwiftUI [https://cs193p.sites.stanford.edu](https://cs193p.sites.stanford.edu/) ## lecture 1 - ViewModifier 返回的是一个新的View,不是原来的View 例如 ```swift Text("Hello, world!").padding() is Text ``` 上述代码返回的是false,类型已经改变了 - some View 的类型非常重要 抽象理解,指 **一些行为类似View的结构** 类型 ## lecture 2 - @State ```swift struct contentView: View { @State var str: String = "1" var body: some View{ Text("Hello") } } ``` View都是不可变的,@State的声明是意思str是一个指向其他地方的可变变量的指针,其本身是不可变的,但指向的地方的内容是可变的,View也是不可变的,只能重绘 - 'let' declarations cannot be computed properties 计算变量不能用let定义。let 定义的必须是都确定的值,不能有未初始化的值。 - ForEach 需要Id唯一标识符 ```swift ForEach(emojis, id: \.self) { emoji in CardView(content: emoji) } // \.self表示用获取的变量本身 作为唯一标识符 ``` - stroke和strokeBorder stroke以原始大小为中心,向内外扩展,画图 strokeBorder只向内扩展 ## lecture 3 - static 静态属性和静态方法,本质是有命名空间的全局变量和全局方法 - struct 和 class 结构体是值拷贝,是否可变取决于let或var的声明,没有继承,函数式编程 类是引用类型,总是可变的,单继承,面向对象编程 面向对象:数据的封装,存储数据 函数式编程(面向协议): 行为的封装,只是针对特定行为的集合,不针对数据 `struct ContentView: View {}` 这不是继承,是遵循View协议 ![lecture3_1](./illustrations/lecture3_1.png) - MVVM ![lecture3_2](./illustrations/lecture3_2.png) ## lecture 4 - 对结构体的赋值操作,是一个值拷贝,是复制了一份结构体。 ```swift var chosenCard = cards[chooseIndex] ``` 操作chosenCard并不会改变cards中的数据,Card是结构体,不是类。 - mutating 该关键词声明的方法,表示该方法可以改变结构体,否则结构体不可变 - ObservableObject 可观察的对象 objectWillChange.send() 主动通知观察者,我要改变了 @Published 标记的属性,有任何变化时,都会自动通知观察者 - @ObservedObject 观察对象,意思当对象发生变化时,视图重绘 - switch case default break fallthrough ```swift enum Food { case hamburger(pattyCount: Int) case cookie case drink(String, ounces: Int) } var menuItem = Food.cookie switch menuItem { case .cookie: print("cookie") case .hamburger(let pattyCount): print("hamburger pattyCount: \(pattyCount)") default: print("other") } ``` - Optional是一个enum ![lecture4_1](./illustrations/lecture4_1.png) ![lecture4_2](./illustrations/lecture4_2.png) ## lecture 5 - Layout ![lecture5_1](./illustrations/lecture5_1.png) ![lecture5_2](./illustrations/lecture5_2.png) ![lecture5_3](./illustrations/lecture5_3.png) ![lecture5_4](./illustrations/lecture5_4.png) ## lecture 6 - **protocol** 协议可以继承。类、结构体、枚举可以遵守多个协议。 协议可以作为类型,但不推荐这样用,不是所有协议都能作为类型。 协议用来指定类、结构体、枚举的行为。 协议可以用来将扩展限制为仅适用于某些事情 `extension Array where Element: Hashable {...}`。 协议可以用来限制方法只处理某些事情 `init(data: Data) where Data: Collection, Data.Element: Identifiable`。 协议可以用来共享代码,实现可以通过扩展添加到协议上。扩展可以对协议中的func或var等等添加默认实现。 自引用协议(协议用了**Self**自身)不能当做普通类型使用(用作数组元素类型等),例如 **Equatable** ... 。 - clockwise 顺时针 Path绘图中,由于iOS系统绘图的坐标原点在左上角,所以顺时针和我们日常中的是反的 ## lecture 7 - **ViewModifier** **GeometryEffect** ![lecture7_1](./illustrations/lecture7_1.png) ![lecture7_2](./illustrations/lecture7_2.png) - **Animation** `.animation(animation: Animation?)` 会传播给所有子视图 `.transition(t: AnyTransition)`不会传播 只有当View已经出现了,值发生变化时,才会隐式动画 `withAnimation`的显示动画,基本总是和交互意图一起使用 ## lecture 8 - @State 基本只用于界面动画的临时状态。和View相关的,但和逻辑无关,就让View的state去处理。 ## lecture 9 - Collections of Identifiables ![lecture9_1](./illustrations/lecture9_1.png) ![lecture9_2](./illustrations/lecture9_2.png) - **NSItemProvider** 两个进程间交换数据,比如从Safari拖拽图片到备忘录 ![lecture9_3](./illustrations/lecture9_3.png) ## lecture 10 - 多线程 - 手势 **`@GestureState`** 通常是只读的,通过`$`,作为输入输出参数传递给`.updating($myGestureState)`函数,在函数的闭包中修改(只能在这修改) ![lecture10_1](./illustrations/lecture10_1.png) ![lecture10_2](./illustrations/lecture10_2.png) ## lecture 11 - Codable - Error - Timer - `"\(String(describing: self)).\(#function)"` ## lecture 12 - 属性装饰器 `Property Wrappers` 实际是一个结构体 ```swift @Published var emojiArt: EmojiArt = EmojiArt() /******** 和下面等效 **********/ struct Publish { var wrappedValue: EmojiArt var projectedValue: Publisher } var _emojiArt: Publish = Publish(wrappedValue: EmojiArt()) var emojiArt: EmojiArt { get { _emojiArt.wrappedValue } set { _emojiArt.wrappedValue = newValue } } $emojiArt // 设置projectedValue ``` ```swift // @State 初始化 @State private var foo: Int init() { _foo = .init(initialValue: 5) //需要这样初始化 } ``` ![lecture12_1](./illustrations/lecture12_1.png) ![lecture12_2](./illustrations/lecture12_2.png) **Binding** ![lecture12_3](./illustrations/lecture12_3.png) ![lecture12_4](./illustrations/lecture12_4.png) ![lecture12_5](./illustrations/lecture12_5.png) ![lecture12_6](./illustrations/lecture12_6.png) - `.id()` ViewModifier通过不同的id,使数据改变时,View重新生成,和之前不是一个View,从而触发转场动画等操作 ```swift HStack { Text(palette.name) ScrollingEmojisView(emojis: palette.emojis) .padding(.horizontal) .font(emojiFont) } .id(palette.id) // id不同,返回的就不是同一个view,就能触发转场动画 .transition(rollTransition) ``` ## lecture 13 - Publisher ![lecture13_1](./illustrations/lecture13_1.png) ![lecture13_2](./illustrations/lecture13_2.png) ![lecture13_3](./illustrations/lecture13_3.png) - 注意`$`的用法 ```swift // 下面两种表达的意思是不一样的 document.$backgroundImage //获取Publisher变量backgroundImage $document.backgroundImage //绑定到变量backgroundImage ``` - CloudKit ![lecture13_4](./illustrations/lecture13_4.png) ![lecture13_5](./illustrations/lecture13_5.png) ![lecture13_6](./illustrations/lecture13_6.png) - Core Data ![lecture13_7](./illustrations/lecture13_7.png) ![lecture13_8](./illustrations/lecture13_8.png) ![lecture13_9](./illustrations/lecture13_9.png) 存: ![lecture13_10](./illustrations/lecture13_10.png) ![lecture13_11](./illustrations/lecture13_11.png) 取: ![lecture13_12](./illustrations/lecture13_12.png) 用: ![lecture13_13](./illustrations/lecture13_13.png) ![lecture13_14](./illustrations/lecture13_14.png) ## lecture 14 - WindowGroup ```swift struct MemorizeApp: App { @StateObject var game = EmojiMemorizeGame() var body: some Scene { WindowGroup { EmojiMemorizeGameView(game: game) } } } ``` - `@SceneStorage` `@AppStorage` `@ScaledMetric` - DocumentGroup ```swift struct EmojiArtApp: App { var body: some Scene { DocumentGroup(newDocument: { EmojiArtDocument() }) { config in //config.document是EmojiArtDocument的实例 //每次打开一个Scene,就会新建一个 EmojiArtDocumentView(document: config.document) } } } /* For this code to work, though, your ViewModel must conform to ReferenceFileDocument. And you must implement Undo in your application */ ``` ![lecture14_1](./illustrations/lecture14_1.png) ![lecture14_2](./illustrations/lecture14_2.png) ![lecture14_3](./illustrations/lecture14_3.png) ![lecture14_4](./illustrations/lecture14_4.png) - UTType (文件格式,自定义文件格式) ![lecture14_5](./illustrations/lecture14_5.png) ![lecture14_6](./illustrations/lecture14_6.png) ![lecture14_7](./illustrations/lecture14_7.png) ![lecture14_8](./illustrations/lecture14_8.png) - Undo 撤销 ![lecture14_9](./illustrations/lecture14_9.png) ![lecture14_10](./illustrations/lecture14_10.png) ![lecture14_11](./illustrations/lecture14_11.png) ## lecture 15 - **UIViewRepresentable** **UIViewControllerRepresentable** ![lecture15_1](./illustrations/lecture15_1.png) ## lecture 16 - MacOS com.apple.security.network.client 网络权限 ## Homework - APNS Client 苹果推送客户端,用于测试推送 参考资料: https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/sending_notification_requests_to_apns#overview