-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathAsset.swift
151 lines (118 loc) · 3.59 KB
/
Asset.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/*
See the LICENSE.txt file for this sample’s licensing information.
Abstract:
The model object for a media asset.
*/
import Foundation
import CoreLocation
import CoreSpotlight
import CoreTransferable
import Photos
@Observable
final class Asset {
// MARK: Static
static let dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .short
return dateFormatter
}()
// MARK: Properties
let phAsset: PHAsset
var placemark: CLPlacemark?
var isFavorite = false
var isHidden = false
var title: String {
guard let placemark, let locality = placemark.locality else {
return Self.dateFormatter.string(from: creationDate ?? .now)
}
return locality
}
var creationDate: Date? {
phAsset.creationDate
}
var type: AssetType {
switch phAsset.mediaType {
case .image:
return .photo
default:
return .video
}
}
var duration: TimeInterval {
phAsset.duration
}
// MARK: Lifecycle
init(phAsset: PHAsset) {
self.phAsset = phAsset
self.isHidden = phAsset.isHidden
self.isFavorite = phAsset.isFavorite
}
// MARK: Methods
func fetchPlacemark() async throws {
guard let location = phAsset.location, placemark == nil else {
return
}
// Reverse geocode the location into the placemark.
let placemark = try await LocationManager.shared.lookUp(location)
await MainActor.run {
self.placemark = placemark
}
// Update this entity in Spotlight with the updated placemark.
try await CSSearchableIndex.default().indexAppEntities([entity])
}
func setIsFavorite(_ isFavorite: Bool) async throws {
try await PHPhotoLibrary.shared().performChanges {
let request = PHAssetChangeRequest(for: self.phAsset)
request.isFavorite = isFavorite
}
self.isFavorite = isFavorite
}
func setIsHidden(_ isHidden: Bool) async throws {
try await PHPhotoLibrary.shared().performChanges {
let request = PHAssetChangeRequest(for: self.phAsset)
request.isHidden = isHidden
}
self.isHidden = isHidden
}
}
extension Asset: Identifiable, Hashable {
var id: String {
phAsset.localIdentifier
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
static func == (lhs: Asset, rhs: Asset) -> Bool {
lhs.id == rhs.id
}
}
extension Asset: Transferable {
static var transferRepresentation: some TransferRepresentation {
DataRepresentation(exportedContentType: .png) { asset in
try await asset.pngData()
}
}
func pngData() async throws -> Data {
let targetSize = CGSize(width: 1000, height: 1000)
let image = await ImageManager.shared.requestImage(for: self, targetSize: targetSize)
guard let kitImage = await image?.kit,
let data = kitImage.pngData() else {
throw MediaLibrary.Error.failedToFetchAsset
}
return data
}
}
extension Asset: @unchecked Sendable {
var entity: AssetEntity {
let entity = AssetEntity(id: id, asset: self)
entity.title = title
entity.assetType = type
entity.creationDate = creationDate
entity.location = placemark
entity.isFavorite = isFavorite
entity.isHidden = isHidden
entity.hasSuggestedEdits = false
return entity
}
}