cs193p_2021_笔记_1
cs193p_2021_笔记_2
cs193p_2021_笔记_3_Animation_Transition
cs193p_2021_笔记_4_Color_Image_Gesture
cs193p_2021_笔记_5_Property Wrapper
cs193p_2021_笔记_6_Persistence
cs193p_2021_笔记_7_Document Architecture
cs193p_2021_笔记_8
--
Color, UIColor & CGColor
Color:
- Is a color-specifier, e.g.,
.foregroundColor(Color.green)
. - Can also act like a
ShapeStyle
, e.g.,.fill(Color.blue)
. - Can also act like a
View
, e.g., Color.white can appearwherever
a View can appear.(可以当作view)
UIColor:
- Is used to
manipulate
colors.(主打操控) - Also has many
more
built-incolors
thanColor
, including “system-related” colors.(颜色更多) - Can be interrogated and can convert between color spaces.
For example, you can get the RGBA values from a UIColor.
Once you have desired UIColor, employ Color(uiColor:)
to use it in one of the roles above.
CGColor:
- The fundamental color representation in the Core Graphics drawing system
color.cgColor
Image V.S. UIImage
Image:
- Primarily serves as a View.(主要功能是View)
- Is
not
a type for vars thathold an image
(i.e. a jpeg or gif or some such). That’s UIImage. - Access images in your Assets.xcassets (in Xcode) by name using
Image(_ name: String)
. - Also, many, many system images available via
Image(systemName:)
. - You can control the size of system images with
.imageScale()
View modifier. - System images also are affected by the .font modifier.
- System images are also very useful
as masks
(for gradients, for example).
UIImage
- Is the type for actually
creating/manipulating
images andstoring
in vars. - Very powerful representation of an image.
- Multiple file formats, transformation primitives, animated images, etc.
- Once you have the UIImage you want, use Image(uiImage:) to display it.
Multithreading
- 多线程其实并不是同时运行,而是前后台非常快速地切换
-
Queue
只是有顺序执行的代码,封装了threading
的应用 - 这些“代码”用
closure
来传递 -
main queue唯一能操作UI的线程
- 主线程是单线程,所以不能执行异步代码
-
background queues执行任意:long-lived, non-UI tasks
- 可以并行运行(running in parallel) -> even with main UI queue
- 可以手动设置优先级,服务质量(
QoS
)等 - 优先级永远不可能超过main queue
- base API: GCD (
Grand Central Dispatch
)- getting access to a queue
- plopping a block of code on a queue
A: Creating a Queue
There are numerous ways to create a queue, but we’re only going to look at two ...
DispatchQueue.main // the queue where all UI code must be posted
DispatchQueue.global(qos: QoS) // a non-UI queue with a certain quality of service qos (quality of service) is one of the following ...
.userInteractive // do this fast, the UI depends on it!
.userInitiated // the user just asked to do this, so do it now
.utility // this needs to happen, but the user didn’t just ask for it
.background // maintenance tasks (cleanups, etc.)
B: Plopping a Closure onto a Queue
There are two basic ways to add a closure to a queue ...
let queue = DispatchQueue.main //or
let queue = DispatchQueue.global(qos:)
queue.async { /* code to execute on queue */ }
queue.sync { /* code to execute on queue */ }
主线程里永远不要.sync
, 那样会阻塞UI
DispatchQueue(global: .userInitiated).async {
// 耗时代码
// 不阻塞UI,也不能更新UI
// 到主线程去更新UI
DispatchQueue.main.async {
// UI code can go here! we’re on the main queue!
}
}
Gestures
手势是iOS里的一等公民
// recognize
myView.gesture(theGesture) // theGesture must implement the Gesture protocol
// create
var theGesture: some Gesture {
return TapGesture(count: 2) // double tap
}
// discrete gestures
var theGesture: some Gesture {
return TapGesture(count: 2)
.onEnded { /* do something */ }
}
// 其实就是:
func theGesture() -> some Gesture {
tapGesture(count: 2)
}
// “convenience versions”
myView.onTapGesture(count: Int) { /* do something */ }
myView.onLongPressGesture(...) { /* do something */ }
// non-discrete gestures
var theGesture: some Gesture {
DragGesture(...)
.onEnded { value in /* do something */ }
non-discrete手势里传递的value
是一个state:
- For a
DragGesture
, it’s a struct with things like thestart and end location
of the fingers. - For a
MagnificationGesture
it’s thescale
of the magnification (how far the fingers spread out). - For a
RotationGesture
it’s theAngle
of the rotation (like the fingers were turning a dial). - 还可以跟踪一个state:
@GestureState var myGestureState: MyGestureStateType = <starting value>
唯一可以更新这个myGestureState
的机会:
var theGesture: some Gesture {
DragGesture(...)
.updating($myGestureState) { value, myGestureState, transaction in
myGestureState = /* usually something related to value */
}
.onEnded { value in /* do something */ }
}
注意$
的用法
如果不需要去计算一个gestureState
传出去的话,有个updating
用简版:
.onChanged { value in
/* do something with value (which is the state of the fingers) */
}
事实上,目前来看gestureState
只做了两件事:
- 把实时手势对应的值保存起来
- 在手势结束时复原(对于缩放,变为1,对于移动,变为0)
- 同时,它是只读的,只在
.updating
方法里有更新的机会
所以,如果你的UI和动画逻辑,用到了手势结束时的值(即需要它复原),那么你也可以直接在.onEnded
方法里手动把它设回去,等同于你也实现了你的gestureState
,并且没有它那些限制。
Drag and Drop
Item Provider
- The heart of drag nad drop is the
NSItemProvider
class. - It facilitates the transfer of data between processes (via drag and drop, for example)
- It facilitates the transfer of a number of data types in iOS, for example:
- NSAttributedString and NSString
- NSURL
- UIImage and UIColor
- pre-Swift,所以需要bridging,比如:
String as NSString
结合几个要点,一句话就能让你的元素能被拖动(drag):
Text(emoji).onDrag{ NSItemProvider(object: emoji as NSString)}
而接收(drop)则要复杂很多:
otherView.onDrop(of: [.plainText], isTarget: nil) {providers, location in return false }
- 参接收的类型由
of
参数指定,这里假定是文本 - 方法里最终要返回一个bool值,表示成功接收与否,我返了个false,意思是你能让物体拖动,但是一松开手指就复原了
从itemprovider
里加载对象有模板代码:
extension Array where Element == NSItemProvider {
func loadObjects<T>(ofType theType: T.Type, firstOnly: Bool = false, using load: @escaping (T) -> Void) -> Bool where T: NSItemProviderReading {
if let provider = first(where: { $0.canLoadObject(ofClass: theType)}) {
provider.loadObject(ofClass: theType) { object, error in
if let value = object as? T {
DispatchQueue.main.async {
load(value)
}
}
}
return true
}
return false
}
// and
// where T: _ObjectiveCBridgeable, T._ObjectiveCType: NSItemProviderReading
- 提供了两段代码,可以看到其实就是对要加载的对象的约束不同,提供了对OC的兼容
- 模板代码演示了
稳健地从拖拽对象加载内容(canload -> load) - 真正的业务逻辑其实就是为拖进来的这个view选择一个位置存放(或读取它携带的数据)
-
T.Type
传的是类别的.self
,比如String.self
网友评论