Skip to content

Commit 2950a2e

Browse files
author
Chris
authored
Merge pull request #8 from crelies/dev
New property wrapper, Album bugfix, Photo.save for macOS
2 parents d1eb8f2 + bcf6236 commit 2950a2e

File tree

8 files changed

+136
-46
lines changed

8 files changed

+136
-46
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,8 @@ This section gives you an overview of the currently available functionality. Mos
381381

382382
The `Media` package also includes some generic property wrappers you can use to interact with the photo library.
383383

384+
- `@FetchAllAssets(sort:fetchLimit:includeAllBurstAssets:includeHiddenAssets) var assets: [AnyMedia]`
385+
384386
- `@FetchAssets(filter:sort:fetchLimit:includeAllBurstAssets:includeHiddenAssets) var assets: [ <Audio | LivePhoto | Photo | Video> ]`
385387

386388
*Fetch audios, live photos, photos or videos matching the given filter and sorted by the given sort*

Sources/MediaCore/API/Album/Album.swift

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ import Photos
1111
/// Convenience wrapper type around `PHAssetCollection`
1212
///
1313
public struct Album {
14-
static var phAsset: PHAsset.Type = PHAsset.self
15-
1614
let phAssetCollectionWrapper: PHAssetCollectionWrapper
1715

1816
var phAssetCollection: PHAssetCollection? { phAssetCollectionWrapper.value }
@@ -37,51 +35,44 @@ public struct Album {
3735
/// All audios contained in the receiver
3836
/// sorted by `creationDate descending`
3937
///
40-
@FetchAssets(sort: [Media.Sort(key: .creationDate, ascending: false)])
38+
@FetchAssets
4139
public var audios: [Audio]
4240

4341
/// All photos contained in the receiver (including `LivePhoto`s)
4442
/// sorted by `creationDate descending`
4543
///
46-
@FetchAssets(sort: [Media.Sort(key: .creationDate, ascending: false)])
44+
@FetchAssets
4745
public var photos: [Photo]
4846

4947
/// All videos contained in the receiver
5048
/// sorted by `creationDate descending`
5149
///
52-
@FetchAssets(sort: [Media.Sort(key: .creationDate, ascending: false)])
50+
@FetchAssets
5351
public var videos: [Video]
5452

5553
/// All live photos contained in the receiver
5654
/// sorted by `creationDate descending`
5755
///
58-
@FetchAssets(filter: [.mediaSubtypes([.live])],
59-
sort: [Media.Sort(key: .creationDate, ascending: false)])
56+
@FetchAssets
6057
public var livePhotos: [LivePhoto]
6158

62-
init(phAssetCollection: PHAssetCollection) {
63-
phAssetCollectionWrapper = PHAssetCollectionWrapper(phAssetCollection: phAssetCollection)
64-
}
65-
}
66-
67-
public extension Album {
6859
/// All media (audios, live photos, photos, videos and more?) contained in the receiver
69-
/// sorted by `creationDate descending`
60+
/// sorted by `creationDate descending`.
7061
///
71-
var allMedia: [AnyMedia] {
72-
guard let phAssetCollection = phAssetCollection else { return [] }
62+
@FetchAllAssets
63+
public var allMedia: [AnyMedia]
7364

74-
let options = PHFetchOptions()
75-
options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
76-
let result = Self.phAsset.fetchAssets(in: phAssetCollection, options: options)
77-
var media: [AnyMedia] = []
78-
result.enumerateObjects { asset, _, _ in
79-
guard let anyMedia = asset.anyMedia else {
80-
return
81-
}
82-
media.append(anyMedia)
83-
}
84-
return media
65+
init(phAssetCollection: PHAssetCollection) {
66+
_audios = FetchAssets(in: phAssetCollection, sort: [Media.Sort(key: .creationDate, ascending: false)])
67+
_photos = FetchAssets(in: phAssetCollection, sort: [Media.Sort(key: .creationDate, ascending: false)])
68+
_videos = FetchAssets(in: phAssetCollection, sort: [Media.Sort(key: .creationDate, ascending: false)])
69+
_livePhotos = FetchAssets(
70+
in: phAssetCollection,
71+
filter: [.mediaSubtypes([.live])],
72+
sort: [Media.Sort(key: .creationDate, ascending: false)]
73+
)
74+
_allMedia = FetchAllAssets(in: phAssetCollection, sort: [Media.Sort(key: .creationDate, ascending: false)])
75+
phAssetCollectionWrapper = PHAssetCollectionWrapper(phAssetCollection: phAssetCollection)
8576
}
8677
}
8778

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//
2+
// UniversalImage.swift
3+
// MediaCore
4+
//
5+
// Created by Christian Elies on 25.01.21.
6+
//
7+
8+
#if canImport(UIKit)
9+
import UIKit
10+
11+
public typealias UniversalImage = UIImage
12+
#elseif canImport(AppKit)
13+
import AppKit
14+
15+
public typealias UniversalImage = NSImage
16+
#endif

Sources/MediaCore/API/Photo/Photo.swift

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ public extension Photo {
162162
}
163163
#endif
164164

165-
#if !os(macOS)
166165
public extension Photo {
167166
/// Data representation of the receiver
168167
///
@@ -198,7 +197,6 @@ public extension Photo {
198197
}
199198
}
200199
}
201-
#endif
202200

203201
#if canImport(UIKit)
204202
public extension Photo {
@@ -248,18 +246,16 @@ public extension Photo {
248246
completion)
249247
}
250248

251-
#if canImport(UIKit)
252-
/// Saves the given `UIImage` if the access to the photo library is allowed
249+
/// Saves the given `UIImage` / `NSImage` if the access to the photo library is allowed
253250
///
254251
/// - Parameters:
255252
/// - image: the `UIImage` which should be saved
256253
/// - completion: a closure which gets a `Result` (`Photo` on `success` or `Error` on `failure`)
257254
///
258-
static func save(_ image: UIImage, completion: @escaping ResultPhotoCompletion) {
255+
static func save(_ image: UniversalImage, completion: @escaping ResultPhotoCompletion) {
259256
PHAssetChanger.createRequest({ assetChangeRequest.creationRequestForAsset(from: image) },
260257
completion)
261258
}
262-
#endif
263259

264260
/// Updates the `favorite` state of the receiver
265261
///
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//
2+
// FetchAllAssets.swift
3+
// MediaCore
4+
//
5+
// Created by Christian Elies on 13.02.21.
6+
//
7+
8+
import Photos
9+
10+
/// Property wrapper for fetching all assets from the photo library.
11+
/// Fetches the assets lazily (after accessing the property).
12+
///
13+
@propertyWrapper
14+
public struct FetchAllAssets {
15+
static var phAsset: PHAsset.Type = PHAsset.self
16+
17+
private var assetCollection: PHAssetCollection?
18+
private let options = PHFetchOptions()
19+
private let defaultSort: Media.Sort<Media.SortKey> = Media.Sort(key: .creationDate, ascending: false)
20+
21+
/// Wrapped array of `AnyMedia` instances.
22+
public var wrappedValue: [AnyMedia] {
23+
let result: PHFetchResult<PHAsset>
24+
if let assetCollection = assetCollection {
25+
result = Self.phAsset.fetchAssets(in: assetCollection, options: options)
26+
} else {
27+
result = Self.phAsset.fetchAssets(with: options)
28+
}
29+
var media: [AnyMedia] = []
30+
result.enumerateObjects { asset, _, _ in
31+
guard let anyMedia = asset.anyMedia else {
32+
return
33+
}
34+
media.append(anyMedia)
35+
}
36+
return media
37+
}
38+
39+
/// Initializes the property wrapper using a default sort descriptor
40+
/// (sort by `creationDate descending`).
41+
///
42+
public init() {
43+
options.sortDescriptors = [defaultSort.sortDescriptor]
44+
}
45+
46+
/// Initializes the property wrapper using the given sort descriptors
47+
/// to define the `PHFetchOptions`.
48+
///
49+
/// - Parameters:
50+
/// - assetCollection: limits the fetch to a specific asset collection, by default all assets in the library are taken into account.
51+
/// - sort: a set of `Sort<MediaSortKey>` for sorting the assets
52+
/// - fetchLimit: a maximum number of results to fetch, defaults to 0 (no limit)
53+
/// - includeAllBurstAssets: a Boolean value that determines whether the fetch result includes all assets from burst photo sequences, defaults to false
54+
/// - includeHiddenAssets: a Boolean value that determines whether the fetch result includes assets marked as hidden, defaults to false
55+
///
56+
public init(
57+
in assetCollection: PHAssetCollection? = nil,
58+
sort: Set<Media.Sort<Media.SortKey>> = [],
59+
fetchLimit: Int = 0,
60+
includeAllBurstAssets: Bool = false,
61+
includeHiddenAssets: Bool = false
62+
) {
63+
self.assetCollection = assetCollection
64+
65+
var sortKeys = sort
66+
sortKeys.insert(defaultSort)
67+
68+
if !sortKeys.isEmpty {
69+
let sortDescriptors = sortKeys.map { $0.sortDescriptor }
70+
options.sortDescriptors = sortDescriptors
71+
}
72+
73+
options.fetchLimit = fetchLimit
74+
options.includeAllBurstAssets = includeAllBurstAssets
75+
options.includeHiddenAssets = includeHiddenAssets
76+
}
77+
}

Sources/MediaCore/API/PropertyWrappers/FetchAssets.swift

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,19 @@ import Photos
1212
///
1313
@propertyWrapper
1414
public struct FetchAssets<T: MediaProtocol> {
15+
private var assetCollection: PHAssetCollection?
1516
private let options = PHFetchOptions()
1617
private let mediaTypePredicate: NSPredicate = NSPredicate(format: "mediaType = %d", T.type.rawValue)
1718
private let defaultSort: Media.Sort<Media.SortKey> = Media.Sort(key: .creationDate, ascending: false)
1819

1920
/// Wrapped array of objects conforming to the `MediaProtocol`
20-
public var wrappedValue: [T] { (try? PHAssetFetcher.fetchAssets(options: options)) ?? [] }
21+
public var wrappedValue: [T] {
22+
if let assetCollection = assetCollection {
23+
return (try? PHAssetFetcher.fetchAssets(in: assetCollection, options: options)) ?? []
24+
} else {
25+
return (try? PHAssetFetcher.fetchAssets(options: options)) ?? []
26+
}
27+
}
2128

2229
/// Initializes the property wrapper using a default sort descriptor
2330
/// (sort by `creationDate descending`)
@@ -31,17 +38,23 @@ public struct FetchAssets<T: MediaProtocol> {
3138
/// to define the `PHFetchOptions`
3239
///
3340
/// - Parameters:
41+
/// - assetCollection: limits the fetch to a specific asset collection, by default all assets in the library are taken into account.
3442
/// - filter: a set of `MediaFilter` for filtering the assets
3543
/// - sort: a set of `Sort<MediaSortKey>` for sorting the assets
3644
/// - fetchLimit: a maximum number of results to fetch, defaults to 0 (no limit)
3745
/// - includeAllBurstAssets: a Boolean value that determines whether the fetch result includes all assets from burst photo sequences, defaults to false
3846
/// - includeHiddenAssets: a Boolean value that determines whether the fetch result includes assets marked as hidden, defaults to false
3947
///
40-
public init(filter: Set<Media.Filter<T.MediaSubtype>> = [],
41-
sort: Set<Media.Sort<Media.SortKey>> = [],
42-
fetchLimit: Int = 0,
43-
includeAllBurstAssets: Bool = false,
44-
includeHiddenAssets: Bool = false) {
48+
public init(
49+
in assetCollection: PHAssetCollection? = nil,
50+
filter: Set<Media.Filter<T.MediaSubtype>> = [],
51+
sort: Set<Media.Sort<Media.SortKey>> = [],
52+
fetchLimit: Int = 0,
53+
includeAllBurstAssets: Bool = false,
54+
includeHiddenAssets: Bool = false
55+
) {
56+
self.assetCollection = assetCollection
57+
4558
if !filter.isEmpty {
4659
let predicates = filter.map { $0.predicate }
4760
options.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates + [mediaTypePredicate])

Sources/MediaCore/API/Protocols/AssetChangeRequest.swift

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,18 @@
66
//
77

88
import Photos
9-
#if canImport(UIKit)
10-
import UIKit
11-
#endif
129

1310
/// Defines the requirements for an
1411
/// asset change request
1512
///
1613
public protocol AssetChangeRequest: class {
1714
/// A placeholder object for the asset that the change request creates.
1815
var placeholderForCreatedAsset: PHObjectPlaceholder? { get }
19-
#if canImport(UIKit)
2016
/// Creates a request for adding a new image asset to the Photos library.
2117
///
2218
/// - Parameter image: An image.
2319
///
24-
static func creationRequestForAsset(from image: UIImage) -> Self
25-
#endif
20+
static func creationRequestForAsset(from image: UniversalImage) -> Self
2621
/// Creates a request for adding a new image asset to the Photos library,
2722
/// using the image file at the specified URL.
2823
///

Tests/MediaTests/Models/Album/AlbumTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ final class AlbumTests: XCTestCase {
1313
lazy var album = Album(phAssetCollection: mockAssetCollection)
1414

1515
override func setUp() {
16-
Album.phAsset = MockPHAsset.self
16+
FetchAllAssets.phAsset = MockPHAsset.self
1717
PHAssetFetcher.asset = MockPHAsset.self
1818
MockPHAsset.fetchResult.mockAssets.removeAll()
1919
AlbumFetcher.assetCollection = MockPHAssetCollection.self

0 commit comments

Comments
 (0)