Collection
: immutable
RangeReplaceableColelction
: mutable
subscripting
가능!Color
: color-specifier
, ShapeStyle
, View
UIColor
: 색깔을 다양하게 변경 가능CGColor
: Core Graphics
에서 사용...color.cgColor
는 옵셔널`Color
를 UIColor
로 바꾸려면 : Color
-> CGColor
-> UIColor
Image
: 이미지를 UI 상에 그려내는 View
UIImage
: 이미지를 담고있는 변수의 타입
e.g. JPEG, PNG, etc.UIImage
를 UI 상에 나타낼 때 Image
를 사용하면 됨!드래그 앤 드랍과 같은 과정을 통해 서로 다른 프로세스 간 비동기적 데이터 전송을 용이케 해주는 친구!!
원소의 내용과 타입에 관한 정보를 담고 있음
Emoji 구조체
의 경우 사용자가 크기/위치를 변경할 수 있어야 하기 때문에private
으로 선언할 수 없다. 하지만 모델 밖에서 free init
을 통해 임의로 이모지를 생성하는 것을 막고 싶으므로 init
만 fileprivate
으로 선언해서 모델 밖에서 임의로 이모지를 새로 생성하는 것을 막을 수 있다! struct Emoji: Identifiable, Hashable {
let text: String
var x: Int
var y: Int
var size: Int
var id: Int
fileprivate init(text: String, x: Int, y: Int, size: Int, id: Int) {
self.text = text
self.x = x
self.y = y
self.size = size
self.id = id
}
}
EmojiArtModel
의 free init
을 외부에서 임의로 사용하는 것을 방지하기 위해 다음과 같이 빈 init
을 설정해준다! (이모지와 배경 추가는 모델 내부의 함수를 통해서만 이루어짐)struct EmojiArtModel {
init() {}
}
imagedata
와 url
변수를 통해 Background
구조체의 인스턴스가 이미지 데이터 혹은 url을 갖고 있는지 바로 확인할 수 있다!extension EmojiArtModel {
enum Background {
case blank
case url(URL)
case imageData(Data)
var url: URL? {
switch self {
case .url(let url): return url
default: return nil
}
}
var imageData: Data? {
switch self {
case .imageData(let data): return data
default: return nil
}
}
}
}
let blankBackground = EmojiArtModel.Background.blank blankBackground.imagedata // nil blankBackground.url // nil
ScrollingEmojisView
에서 임의의 이모지를 drag
해서 background
에 보내고 싶은 것이므로 드래그 시, NSItemProvider
가 사용할 수 있도록 각 emoji
를 NSString
으로 변환해서 보내주면 해결된다!struct ScrollingEmojisView: View {
let emojis: String
var body: some View {
ScrollView(.horizontal) {
HStack{
ForEach(emojis.map { String($0) }, id: \.self) { emoji in
Text(emoji)
.onDrag { NSItemProvider(object: emoji as NSString) }
}
}
}
}
}
도 설명하고 싶었지만...뉴비에게는 너무 어려운 내용이라 미래의 나를 믿어보기로...
아래의 두 가지 extension
이 핵심이다, 교수님이 올려주신 데모 코드의 주석까지 고대로 복붙...
extension Character {
var isEmoji: Bool {
// Swift does not have a way to ask if a Character isEmoji
// but it does let us check to see if our component scalars isEmoji
// unfortunately unicode allows certain scalars (like 1)
// to be modified by another scalar to become emoji (e.g. 1️⃣)
// so the scalar "1" will report isEmoji = true
// so we can't just check to see if the first scalar isEmoji
// the quick and dirty here is to see if the scalar is at least the first true emoji we know of
// (the start of the "miscellaneous items" section)
// or check to see if this is a multiple scalar unicode sequence
// (e.g. a 1 with a unicode modifier to force it to be presented as emoji 1️⃣)
if let firstScalar = unicodeScalars.first, firstScalar.properties.isEmoji {
return (firstScalar.value >= 0x238d || unicodeScalars.count > 1)
} else {
return false
}
}
}
NSItemProvider
의 object
를 @escaping
클로저를 이용해서 처리하고(우리의 경우 Model
의 emojis 배열
에 추가할 것임) 드랍할 수 있게 해 줄 거라는 뜻...로딩 성공 여부를 반환한다...! // convenience functions for [NSItemProvider] (i.e. array of NSItemProvider)
// makes the code for loading objects from the providers a bit simpler
// NSItemProvider is a holdover from the Objective-C (i.e. pre-Swift) world
// you can tell by its very name (starts with NS)
// so unfortunately, dealing with this API is a little bit crufty
// thus I recommend you just accept that these loadObjects functions will work and move on
// it's a rare case where trying to dive in and understand what's going on here
// would probably not be a very efficient use of your time
// (though I'm certainly not going to say you shouldn't!)
// (just trying to help you optimize your valuable time this quarter)
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
}
func loadObjects<T>(ofType theType: T.Type, firstOnly: Bool = false, using load: @escaping (T) -> Void) -> Bool where T: _ObjectiveCBridgeable, T._ObjectiveCType: NSItemProviderReading {
if let provider = first(where: { $0.canLoadObject(ofClass: theType) }) {
let _ = provider.loadObject(ofClass: theType) { object, error in
if let value = object {
DispatchQueue.main.async {
load(value)
}
}
}
return true
}
return false
}
func loadFirstObject<T>(ofType theType: T.Type, using load: @escaping (T) -> Void) -> Bool where T: NSItemProviderReading {
loadObjects(ofType: theType, firstOnly: true, using: load)
}
func loadFirstObject<T>(ofType theType: T.Type, using load: @escaping (T) -> Void) -> Bool where T: _ObjectiveCBridgeable, T._ObjectiveCType: NSItemProviderReading {
loadObjects(ofType: theType, firstOnly: true, using: load)
}
}
.onDrop
에서 drop 함수
를 호출하면 String
이 존재하고, 이모지 타입이 맞는지 확인한 다음 클로저를 이용해 처리(우리의 경우 위에서 말했듯이 모델의 이모지 목록에 추가하는 것)하고 성공 여부를 반환한다... ...
var documentBody: some View {
GeometryReader { geometry in
ZStack {
// some code displaying background and all added emojis
}
.onDrop(of: [.plainText], isTargeted: nil) { provider, location in
drop(provider: provider, at: location, in: geometry)
}
}
}
// MARK: - Drag and Drop
// returns whether loading(drop) was successful
private func drop(provider: [NSItemProvider], at location: CGPoint, in geometry: GeometryProxy) -> Bool {
// give a closure to handle the loaded data
return provider.loadObjects(ofType: String.self) { string in
if let emoji = string.first, emoji.isEmoji {
document.addEmoji(String(emoji), at: convertToEmojiCoordinates(location, in: geometry) , size: defaultEmojiFontSize)
}
}
}
...
Main Queue
: UI에 영항을 미칠 수도 있는 모든 코드 블록을 담고 있음
쓰기
작업은 메인 큐에서 읽기
작업은 백그라운드 큐에서 수행함으로써 동기화 가능일단 오늘은 이론만 슥 들었기 때문에 다음 시간에 실제로 구현해보면서 swift에서 멀티스레딩을 하는지 이해할 수 있을 것 같다...제발
GCD의 많은 부분이 올해 더욱 swift한 방식으로 교체되었다고 한다...
메인 큐 : DispatchQueue.Main
백그라운드 큐 : DispatchQueue.global(qos: QoS)
qos(quality of service)
: 해당 큐의 우선순위를 나타냄 (강의 1:24:00 참고)
queue.async
: 여러 작업을 비동기적으로 처리(큐에 넣는 것일뿐 즉각적으로 작업이 실제 실행되는 것은 아님!)하므로 여러 작업을 동시에 수행 가능queue.sync
: 해당 작업이 끝날 때까지 다른 작업 블락 dispatch Queue
찾아보기! NS 친구들...어렵다...멀티스레딩...더 어려울 것 같다....ㅎㅎ
재수강하면서 느낀건데 동기, 비동기에 대한 개념이 완전히 잘못 잡혀있었다...이제 정말 CS공부의 필요성을 느낀다...
API : programming interface, the vars, functions that are involved in making this all work..!
queue.async
: queue.sync
: 해당 클로저가