Developing Applications for iOS using SwiftUI
Total Lecture: 15
current: [10-12]
Emoji Art
Start a brand-new app called EmojiArt
Positioin Modifier
Position
就是你如何在视图空间内定位某物
1 | private var documentBody: some View { |
Drag and Drop
Transferable
协议
1 | Text(emoji).draggable(emoji) // emojj is a String |
dropDestination
1 | private var highlighted = false |
Gestures, 2nd MVVM
Making your Views Recognize Gestures
1 | myView.gesture(theGesture) //theGesture must implement the Gesture prtocol |
Creating a Gesture
1 | var theGesture: some Gesture { |
离散手势的识别处理
处理离散手势的方式
1 | var theGesture: some Gesture { |
Discrete 离散的
TapGesture 是离散手势
非离散手势
1 | var theGesture: some Gesture { |
非离散手势处理
1 | var myGestureState: MyGestureStateType = <staring value> |
This var will always return to
<starting value>
when the gesture ends.
Zoom Gesture
1 | pricate var documentBody: some View { |
有些手势会消耗手指动作,然后你才能进行其他手势
1 | .gesture(panGesture.simultaneously(with: zoomGesture)) |
告诉系统同时识别这两个手势
1 | extension CGOffset { |
inout 当该函数结束时,参数会被复制回来
Multiple MVVMs
Palette
1 | struct Palette: Identifiable { |
UUID
是一个生成唯一Id
的小结构体,16个字节,随机生成,可哈希
何时使用?
当你拥有的东西数量相当少的时候
environmentObject
1 | @main |
environmentObject
表示这个对象paletteStore
被注入到所有的视图中
向上滑动消失动画
1 | func view(for palette: Palette) -> some View { |
为什么转变不起作用?
过渡动画是什么?视图的来来去去,对吗?这个HStack
永远不会来来去去,只是这个调色板的名称,标签符号正在发生变化。它不会被删除。
1 | func view(for palette: Palette) -> some View { |
这样一来HStack
身份就成为了其本身的一部分,这个调色板出现并且它是一个具有不同ID
的新调色板时,整个视图都必须被替换,因为这是一种不通的视图
.clipped
如何避免动画超出视图绘制?
1 | var body: some View{ |
@escaping
1 | struct AnimatedActionButton: View { |
@escaping
意味着这个函数init
,将会保留这个闭包并在稍后调用它,所以它从这个init
中逃脱,
为什么Swift
想知道这一点?
因为它要内联无法逃脱的闭包,在这种情况下,它必须将闭包转换成引用类型,将它们放在堆中,并为它们提供一个指针,这样它们就可以被保存。
Persistence, Property Wrappers
FileSystem
sandbox
Application directory – Your executable,.jpgs,ect.; not writaable
Documents directory – Permanenet storage created by and always visiable to the user
Application Support directory - Permanent storage not seen directly by the user
Caches directory - Store temprary files here (this is not backed up)
Accessing the file system
1 | URL.documentDirectory |
1 | func appendingPathComponent(String) -> URL |
1 | var ifFileURL: Bool // is this a file URL (whether file exists or not) or something else? |
example keys: .creationDateKey
,.isDirectoryKey
,.fileSizeKey
Data
Reading
1 | init(contentsOf: URL, options: Data.ReadingOptions) throws |
Writing
1 | func write(to url: URL, options: Data.WritingOptions) thros -> Bool |
FileManager
用于管理文件系统本身,复制文件,创建目录,列出目录
1 | fileExists(atPath: String) -> Bool |
Codable Mechanism
如何创建想要放入文件系统的数据呢?
Codable
本质上是一种收集结构体所有变量并将其分解成数据的方法(like a JSON Data)
1 | let object: MyType = ... //MyType must conrom to Codable |
1 | let jsonString = String(data: jsonData, encoding: .utf8) // JSON is always utf8 |
UserDefaults
dictionary-like thing
1 | let defaults = UserDefaults.standard |
Property List
不是某种协议,或某种具体类型
UserDefaults不会在你每次存储时都写入磁盘,而是缓冲更改并在它认为你可能需要时将其写入。
切换App时会写入
Error Handing
为什么需要throw
这些错误?
如果没有throw
错误,那么函数就必须返回一个错误代码,现在你要返回什么?
要么是一个有成功和失败的枚举,要么像一个有正确答案然后有错误的元组,这会变得混乱。
1 | let imageData = try? Data(contentsOf: url) //imageData = nil if error thrown |
1 | func foo() throws { |
try foo()
handing a thrown error
1 | do { |
值类型可以用EmojiArt.self
替换它们的整个自我
@Published
1 | class PaletteStore: ObervableObject { |
属性包装器不能用于计算属性
如果你有一个类并且实现了ObservableObject
,那么你就会得到一个免费的变量var objectWillChange: ObservableObjectPublisher
,它用来告诉视图某些东西将会改变,你可能需要更新,这就是@Published
所做的。
@Published
每当你更改该变量时,它都会调用该变量的发送函数。
1 | class PaletteStore: ObervableObject { |
Property Wrappers
all those @
things
属性包装器实际创建的是一个结构体,这些结构体封装了你始终希望应用于某个变量的某种行为。
@ObservedObject
当它看到objectWillChange,send()
时,会导致视图重新绘制
属性包装器时Swift
语言中的一个特性,它允许你通过语法糖的方式创建和使用这些结构体
属性包装器语法糖
1 | var emojiArt: EmojiArt = EmojiArt() |
1 | var _emojiArt: Published = Published(wrappedValue: EmojiArt()) |
可以通过$emojiArt
的方式获取projectedValue
,它可以是结构体想要的任何东西,不同的属性包装器有不同的projectValue
publisher
本质上是Swift
里知道如何在流上发布事物的东西,它发布的是信息流
Why?
the Wrapper struct does something on set
/get
of the wrappedValue
@Published
当它的wrappedValue
被设置时( is set
),它会通过projectedValue
($smojiArt
),那个发布者(Publisher
),发布变化,
它也会在其封闭的ObservableObject
中调用objectWillChange,send()
@State
The wrappedValue is : any type
它会导致wrappedValue
被移动到堆中,并在视图不断变化时稳定地保存在那里,该值发生变化时,它就会使视图无效,进行重绘。
Projected value: a Binding to the value in the heap
1 | private var foo: Int |
@StateObject and @ObservedObject
The wrapedValue
必须是一个class
,基本上就是你的ViewModel
,anything that implements the ObservableObject
protocol
当wrappedValue
执行objectWillChange.send()
时,它们会使View
无效
可以使用Binding
来访问Viewmodel
所有变量
1 | $myViewModel.someVar.someArray[3] |
@Binding
一个绑定到其他东西的值,当你获取并设置其wrappedValue
时,它会获取并设置它所绑定到的事物的值,它是一个引用,当绑定的值改变时,它会使视图无效。
Projected value: a binding to the Binding itself
1 | struct MyView: View { |
Binding.constant(value)
Binding(get:,set:)
@EnvironmentObject
它的wrappedValue
是通过.environmentObject()
注入到View
的ObservableObject
,当wrappedValue发生改变时,它可以使视图无效
@Environment
1 | var colorScheme (\.colorSheme) |
将创建一个名为colorScheme
的变量,代表当前正在运行的进程的配色方案(暗模式或亮模式)
\.colorSheme
is a key path
你可以在EnvironmentValues
结构体中找到所有的可用的key
the wrappedValue
is: the value of some var in EnvironmentValues
What it does: gets/sets a value of some var in EnvironmentValues
Projected value: none,所以无法使用binding