www.youtube.com/watch?v=tmx-OwkBWxA
소스
EmojiArt.swift
//
// EmojiArt.swift
// EmojiArt
//
// Created by user on 2021/01/18.
// Copyright © 2021 CS193p Instructor. All rights reserved.
//
import Foundation
struct EmojiArt {
var backgroundURL: URL?
var emojis = [Emoji]()
struct Emoji: Identifiable {
let text: String
var x: Int
var y: Int
var size: Int
let 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
}
}
private var uniqueEmojiId = 0
mutating func addEmoji(_ text: String, x: Int, y: Int, size: Int) {
uniqueEmojiId += 1
emojis.append(Emoji(text: text, x: x, y: y, size: size, id: uniqueEmojiId))
}
}
접근 제한자 헝용 범위
private : 같은 클래스
fileprivate : 같은 소스 파일(.swift)
internal : 같은 모듈(framework) or 같은 프로젝트
public : 모듈 외부까지 가능
open : 모듈 외부, 상속 및 override가능(확장 가능)
선언하지 않을시 디폴트는 internal 접근 제한자
EmojiArtDocument.swift
//
// EmojiArtDocument.swift
// EmojiArt
//
// Created by user on 2021/01/18.
// Copyright © 2021 CS193p Instructor. All rights reserved.
//
import SwiftUI
class EmojiArtDocument: ObservableObject
{
static let palette: String = "⭐️⛈🍎🌏🥨⚾️"
@Published private var emojiArt: EmojiArt = EmojiArt()
@Published private(set) var backgroundImage: UIImage?
var emojis: [EmojiArt.Emoji] { emojiArt.emojis }
// MARK: - Intent(s)
func addEmoji(_ emoji: String, at location: CGPoint, size: CGFloat) {
emojiArt.addEmoji(emoji, x: Int(location.x), y: Int(location.y), size: Int(size))
}
func moveEmoji(_ emoji: EmojiArt.Emoji, by offset: CGSize) {
if let index = emojiArt.emojis.firstIndex(matching: emoji) {
emojiArt.emojis[index].x += Int(offset.width)
emojiArt.emojis[index].y += Int(offset.height)
}
}
func scaleEmoji(_ emoji: EmojiArt.Emoji, by scale: CGFloat) {
if let index = emojiArt.emojis.firstIndex(matching: emoji) {
emojiArt.emojis[index].size = Int((CGFloat(emojiArt.emojis[index].size) * scale).rounded(.toNearestOrEven))
}
}
func setBackgroundURL(_ url: URL?) {
emojiArt.backgroundURL = url?.imageURL
fetchBackgroundImageData()
}
private func fetchBackgroundImageData() {
backgroundImage = nil
if let url = self.emojiArt.backgroundURL {
DispatchQueue.global(qos: .userInitiated).async {
if let imageData = try? Data(contentsOf: url) {
DispatchQueue.main.async {
if url == self.emojiArt.backgroundURL {
self.backgroundImage = UIImage(data: imageData)
}
}
}
}
}
}
}
extension EmojiArt.Emoji {
var fontSize: CGFloat { CGFloat(self.size) }
var location: CGPoint { CGPoint(x: CGFloat(x), y: CGFloat(y)) }
}
DispatchQueue
DispatchQueue는 작업항목의 실행을 관리하는 클래스입니다. 대기열(큐-Queue)에 추가된 작업항목은 시스템이 관리하는 스레드풀에서 처리하고 작업을 완료하면 스레드를 알아서 해제합니다. DispatchQueue의 장점은 일반 스레드 코드보다 쉽고 효율적으로 코드를 작성할 수 있습니다. 주로 iOS에서는 서버에서 데이터를 내려받는다던지 이미지, 동영상 등 멀티미디어 처리와 같이 CPU사용량이 많은 처리를 별도의 스레드에서 처리한 뒤 메인 스레드로 결과를 전달하여 화면에 표시합니다.
그리고 DispatchQueue를 생성 시 기본은 Serial입니다. Concurrent 유형으로 바꾸려면 별도로 명시만 해주면 됩니다.
Synchronous vs. Asnychronous
(sync와 async의 차이를 이해할 때, 많은 사람들이 혼동을 한다. 바로 Serial큐와 Concurrent큐의 개념과 헷갈리기 때문이다.)
GCD에서 Queue에 작업을 추가할 때, 동기 또는 비동기로 추가할 수 있다.
Sync
DispatchQueue.sync(execute:)를 호출하여 사용한다.
sync 메소드를 호출한 caller는 전달된 작업이 끝날 때까지 기다린다. sync는 작업이 끝나야 return하기 때문이다.
** 가끔 앱개발을 하다가 DispatchQueue.main.sync{}에서 크래쉬가 발생한적이 있지 않나요? 위와 같은 이유로 데드락이 발생했기 때문입니다.
Async
DispatchQueue.async(execute:)를 호출하여 사용한다.
sync와는 달리 작업을 전달하자마자 return한다.
즉, caller는 async를 호출한 뒤에, 다음작업을 바로 수행한다.
EmojiArtDocumentView.swift
//
// EmojiArtDocumentView.swift
// EmojiArt
//
// Created by user on 2021/01/18.
// Copyright © 2021 CS193p Instructor. All rights reserved.
//
import SwiftUI
struct EmojiArtDocumentView: View {
@ObservedObject var document: EmojiArtDocument
var body: some View {
VStack {
ScrollView(.horizontal) {
HStack {
ForEach(EmojiArtDocument.palette.map { String($0) }, id: \.self) { emoji in
Text(emoji)
.font(Font.system(size: self.defaultEmojiSize))
.onDrag { NSItemProvider(object: emoji as NSString) }
}
}
}
.padding(.horizontal)
GeometryReader { geometry in
ZStack {
Color.white.overlay(
Group {
if self.document.backgroundImage != nil {
Image(uiImage: self.document.backgroundImage!)
}
}
)
.edgesIgnoringSafeArea([.horizontal, .bottom])
.onDrop(of: ["public.image","public.text"], isTargeted: nil) { providers, location in
// SwiftUI bug (as of 13.4)? the location is supposed to be in our coordinate system
// however, the y coordinate appears to be in the global coordinate system
var location = CGPoint(x: location.x, y: geometry.convert(location, from: .global).y)
location = CGPoint(x: location.x - geometry.size.width/2, y: location.y - geometry.size.height/2)
return self.drop(providers: providers, at: location)
}
ForEach(self.document.emojis) { emoji in
Text(emoji.text)
.font(self.font(for: emoji))
.position(self.position(for: emoji, in: geometry.size))
}
}
}
}
}
private func font(for emoji: EmojiArt.Emoji) -> Font {
Font.system(size: emoji.fontSize)
}
private func position(for emoji: EmojiArt.Emoji, in size: CGSize) -> CGPoint {
CGPoint(x: emoji.location.x + size.width/2, y: emoji.location.y + size.height/2)
}
private func drop(providers: [NSItemProvider], at location: CGPoint) -> Bool {
var found = providers.loadFirstObject(ofType: URL.self) { url in
self.document.setBackgroundURL(url)
}
if !found {
found = providers.loadObjects(ofType: String.self) { string in
self.document.addEmoji(string, at: location, size: self.defaultEmojiSize)
}
}
return found
}
private let defaultEmojiSize: CGFloat = 40
}
[NSItemProvider]
특정 데이터 요소에 사용할 끌기 표현을 제공하는 클로저를 제공
EmojiArtApp.swift
//
// EmojiArtApp.swift
// EmojiArt
//
// Created by user on 2021/01/18.
// Copyright © 2021 CS193p Instructor. All rights reserved.
//
import SwiftUI
@main
struct MemorizeApp: App {
var body: some Scene {
WindowGroup {
EmojiArtDocumentView(document: EmojiArtDocument())
}
}
}
'IOS' 카테고리의 다른 글
3. IOS 강의 Data Flow, Modal Presentation and Navigation (0) | 2021.01.21 |
---|---|
3. IOS 강의 Gestures JSON (0) | 2021.01.19 |
3. IOS 강의 Animation (0) | 2021.01.18 |
3. IOS 강의 ViewBuilder Shape ViewModifier (0) | 2021.01.18 |
3. IOS 강의 Grid enum Optionals (0) | 2021.01.18 |