cs193p-emojiart/EmojiArt/EmojiArtDocument.swift
Ching 566f3539d1 feat(view, viewmodels): 增加调色板按钮,增加报错提示
1. 增加从调色板中增加、删除、跳转和编辑 emoji 的功能
2. 增加通过 url 获取背景图时失败带报错

Signed-off-by: Ching <loooching@gmail.com>
2023-02-26 19:34:35 +08:00

126 lines
4.0 KiB
Swift

//
// EmojiArtDocument.swift
// EmojiArt
//
// Created by ching on 2023/2/19.
//
import SwiftUI
class EmojiArtDocument: ObservableObject {
@Published private(set) var emojiArt: EmojiArtModel {
didSet {
scheduleAutosave()
if emojiArt.background != oldValue.background {
fetchBackgoundImageDataIfNecessary()
}
}
}
private var autosaveTimer: Timer?
private func scheduleAutosave() {
autosaveTimer?.invalidate()
autosaveTimer = Timer.scheduledTimer(withTimeInterval: const.coalescingInterval, repeats: false) { _ in
self.autoSave()
}
}
private enum Autosave {
static let filename = "Autosaved.emojiart"
static var url: URL? {
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
return documentDirectory?.appendingPathComponent(filename)
}
}
private func autoSave() {
if let url = Autosave.url {
save(to: url)
}
}
private func save(to url: URL) {
let thisFunction = "\(String(describing: self)).\(#function)"
do {
let data: Data = try emojiArt.json()
print("\(thisFunction) json = \(String(data: data, encoding: .utf8) ?? "nil")")
try data.write(to: url)
print("\(thisFunction) success")
} catch {
print("\(thisFunction) error = \(error)")
}
}
init() {
if let url = Autosave.url, let autosavedEmojiArt = try? EmojiArtModel(url: url) {
emojiArt = autosavedEmojiArt
fetchBackgoundImageDataIfNecessary()
} else {
emojiArt = EmojiArtModel()
}
}
var emojis: [EmojiArtModel.Emoji] { emojiArt.emojis }
var background: EmojiArtModel.Background { emojiArt.background }
@Published var backgroundImage: UIImage?
@Published var backgroundImageFetchStatus = BackgroundImageFetchStatus.idle
enum BackgroundImageFetchStatus: Equatable {
case idle
case fetching
case failed(URL )
}
// MARK: - Intent(s)
func setBackground(_ background: EmojiArtModel.Background) {
emojiArt.background = background
print("background set to \(background)")
}
func addEmoji(_ emoji: String, at location: (x: Int, y: Int), size: CGFloat) {
emojiArt.addEmoji(emoji, at: location, size: Int(size))
}
func moveEmoji(_ emoji: EmojiArtModel.Emoji, by offset: CGSize) {
if let index = emojiArt.emojis.index(matching: emoji) {
emojiArt.emojis[index].x += Int(offset.width)
emojiArt.emojis[index].y += Int(offset.height)
}
}
func scaleEmoji(_ emoji: EmojiArtModel.Emoji, by scale: CGFloat) {
if let index = emojiArt.emojis.index(matching: emoji) {
emojiArt.emojis[index].size = Int((CGFloat(emojiArt.emojis[index].size) * scale).rounded(.toNearestOrAwayFromZero))
}
}
private func fetchBackgoundImageDataIfNecessary() {
backgroundImage = nil
switch emojiArt.background {
case .url(let url):
// fetch url
backgroundImageFetchStatus = .fetching
DispatchQueue.global(qos: .userInitiated).async {
let imageData = try? Data(contentsOf: url)
DispatchQueue.main.async { [weak self] in
if self?.emojiArt.background == EmojiArtModel.Background.url(url) {
self?.backgroundImageFetchStatus = .idle
if imageData != nil {
self?.backgroundImage = UIImage(data: imageData!)
}
if self?.backgroundImage == nil {
self?.backgroundImageFetchStatus = .failed(url)
}
}
}
}
case .imageData(let data):
backgroundImage = UIImage(data: data)
case .blank:
break
}
}
}