feat(view, model): 增加自动保存功能
增加自动保存功能 Signed-off-by: Ching <loooching@gmail.com>
This commit is contained in:
parent
89d38faea4
commit
ac3031dd05
@ -11,4 +11,5 @@ import SwiftUI
|
|||||||
|
|
||||||
struct const {
|
struct const {
|
||||||
static let defaultEmojiFontSize: CGFloat = 40
|
static let defaultEmojiFontSize: CGFloat = 40
|
||||||
|
static let coalescingInterval = 5.0
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,14 +10,55 @@ import SwiftUI
|
|||||||
class EmojiArtDocument: ObservableObject {
|
class EmojiArtDocument: ObservableObject {
|
||||||
@Published private(set) var emojiArt: EmojiArtModel {
|
@Published private(set) var emojiArt: EmojiArtModel {
|
||||||
didSet {
|
didSet {
|
||||||
|
scheduleAutosave()
|
||||||
if emojiArt.background != oldValue.background {
|
if emojiArt.background != oldValue.background {
|
||||||
fetchBackgoundImageDataIfNecessary()
|
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() {
|
init() {
|
||||||
emojiArt = EmojiArtModel()
|
if let url = Autosave.url, let autosavedEmojiArt = try? EmojiArtModel(url: url) {
|
||||||
|
emojiArt = autosavedEmojiArt
|
||||||
|
fetchBackgoundImageDataIfNecessary()
|
||||||
|
} else {
|
||||||
|
emojiArt = EmojiArtModel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var emojis: [EmojiArtModel.Emoji] { emojiArt.emojis }
|
var emojis: [EmojiArtModel.Emoji] { emojiArt.emojis }
|
||||||
|
|||||||
@ -8,11 +8,36 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension EmojiArtModel {
|
extension EmojiArtModel {
|
||||||
enum Background: Equatable {
|
enum Background: Equatable, Codable {
|
||||||
case blank
|
case blank
|
||||||
case url(URL)
|
case url(URL)
|
||||||
case imageData(Data)
|
case imageData(Data)
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
if let url = try? container.decode(URL.self, forKey: .url) {
|
||||||
|
self = .url(url)
|
||||||
|
} else if let imageData = try? container.decode(Data.self, forKey: .imageData) {
|
||||||
|
self = .imageData(imageData)
|
||||||
|
} else {
|
||||||
|
self = .blank
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case url = "theURL"
|
||||||
|
case imageData
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
switch self {
|
||||||
|
case .url(let url): try container.encode(url, forKey: .url)
|
||||||
|
case .imageData(let data): try container.encode(data, forKey: .imageData)
|
||||||
|
case .blank: break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var url: URL? {
|
var url: URL? {
|
||||||
switch self {
|
switch self {
|
||||||
case .url(let url): return url
|
case .url(let url): return url
|
||||||
|
|||||||
@ -7,11 +7,11 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct EmojiArtModel {
|
struct EmojiArtModel: Codable {
|
||||||
var background: Background = EmojiArtModel.Background.blank
|
var background: Background = EmojiArtModel.Background.blank
|
||||||
var emojis = [Emoji]()
|
var emojis = [Emoji]()
|
||||||
|
|
||||||
struct Emoji: Identifiable {
|
struct Emoji: Identifiable, Hashable, Codable {
|
||||||
let text: String
|
let text: String
|
||||||
var x: Int
|
var x: Int
|
||||||
var y: Int
|
var y: Int
|
||||||
@ -27,14 +27,25 @@ struct EmojiArtModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
func json() throws -> Data {
|
||||||
|
return try JSONEncoder().encode(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
init(json: Data) throws {
|
||||||
|
self = try JSONDecoder().decode(EmojiArtModel.self, from: json)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(url: URL) throws {
|
||||||
|
let data = try Data(contentsOf: url)
|
||||||
|
self = try EmojiArtModel.init(json: data)
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {}
|
||||||
|
|
||||||
private var uniqueEmojiId = 0
|
private var uniqueEmojiId = 0
|
||||||
mutating func addEmoji(_ text: String, at location: (x: Int, y: Int), size: Int) {
|
mutating func addEmoji(_ text: String, at location: (x: Int, y: Int), size: Int) {
|
||||||
uniqueEmojiId += 1
|
uniqueEmojiId += 1
|
||||||
emojis.append(Emoji(text: text, x: location.x, y: location.y, size: size, id: uniqueEmojiId))
|
emojis.append(Emoji(text: text, x: location.x, y: location.y, size: size, id: uniqueEmojiId))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user