Skip to content
This repository was archived by the owner on Feb 2, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions AsyncDisplayKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,10 @@
B350625D1B0111740018CF92 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; };
B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; };
B350625F1B0111800018CF92 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; };
CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; };
CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; settings = {ASSET_TAGS = (); }; };
CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */; settings = {ASSET_TAGS = (); }; };
CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; };
D785F6621A74327E00291744 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; };
DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; };
Expand Down Expand Up @@ -619,6 +623,9 @@
ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackLayoutSpecSnapshotTests.mm; sourceTree = "<group>"; };
B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B35061DD1B010EDF0018CF92 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = "../AsyncDisplayKit-iOS/Info.plist"; sourceTree = "<group>"; };
CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPhotosFrameworkImageRequest.h; sourceTree = "<group>"; };
CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequest.m; sourceTree = "<group>"; };
CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequestTests.m; sourceTree = "<group>"; };
D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = "<group>"; };
D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = "<group>"; };
D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -801,6 +808,7 @@
ACF6ED581B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m */,
242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */,
29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */,
CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */,
296A0A341A951ABF005ACEAA /* ASBatchFetchingTests.m */,
9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */,
2911485B1A77147A005D0878 /* ASControlNodeTests.m */,
Expand Down Expand Up @@ -836,6 +844,8 @@
058D09E1195D050800B7D73C /* Details */ = {
isa = PBXGroup;
children = (
CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */,
CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */,
058D09E2195D050800B7D73C /* _ASDisplayLayer.h */,
058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */,
058D09E4195D050800B7D73C /* _ASDisplayView.h */,
Expand Down Expand Up @@ -1106,6 +1116,7 @@
9C8221951BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */,
9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */,
AC21EC101B3D0BF600C8B19A /* ASStackLayoutDefines.h in Headers */,
CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */,
ACF6ED2F1B17843500DA7C62 /* ASStackLayoutSpec.h in Headers */,
ACF6ED4E1B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h in Headers */,
ACF6ED4F1B17847A00DA7C62 /* ASStackPositionedLayout.h in Headers */,
Expand Down Expand Up @@ -1208,6 +1219,7 @@
9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */,
9C49C3701B853961000B0DD5 /* ASStackLayoutable.h in Headers */,
34EFC7701B701CFA00AD841F /* ASStackLayoutDefines.h in Headers */,
CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */,
34EFC7711B701CFF00AD841F /* ASStackLayoutSpec.h in Headers */,
2767E9411BB19BD600EA9B77 /* ASViewController.h in Headers */,
044284FE1BAA387800D16268 /* ASStackLayoutSpecUtilities.h in Headers */,
Expand Down Expand Up @@ -1493,6 +1505,7 @@
058D0A21195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Sources */,
205F0E101B371875007741D0 /* UICollectionViewLayout+ASConvenience.m in Sources */,
058D0A25195D050800B7D73C /* UIView+ASConvenience.m in Sources */,
CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -1514,6 +1527,7 @@
056D21551ABCEF50001107EF /* ASImageNodeSnapshotTests.m in Sources */,
ACF6ED5E1B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm in Sources */,
ACF6ED601B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m in Sources */,
CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */,
052EE0661A159FEF002C6279 /* ASMultiplexImageNodeTests.m in Sources */,
058D0A3C195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m in Sources */,
ACF6ED611B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm in Sources */,
Expand Down
35 changes: 32 additions & 3 deletions AsyncDisplayKit/ASMultiplexImageNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#import <AsyncDisplayKit/ASImageNode.h>
#import <AsyncDisplayKit/ASImageProtocols.h>

#import <Photos/Photos.h>

@protocol ASMultiplexImageNodeDelegate;
@protocol ASMultiplexImageNodeDataSource;
Expand Down Expand Up @@ -98,6 +99,13 @@ typedef NS_ENUM(NSUInteger, ASMultiplexImageNodeErrorCode) {
*/
@property (nonatomic, readonly) id displayedImageIdentifier;

/**
* @abstract The image manager that this image node should use when requesting images from the Photos framework. If this is `nil` (the default), then `PHImageManager.defaultManager` is used.

* @see `+[NSURL URLWithAssetLocalIdentifier:targetSize:contentMode:options:]` below.
*/
@property (nonatomic, strong) PHImageManager *imageManager;

@end


Expand Down Expand Up @@ -195,11 +203,32 @@ didFinishDownloadingImageWithIdentifier:(id)imageIdentifier
* @abstract An image URL for the specified identifier.
* @param imageNode The sender.
* @param imageIdentifier The identifier for the image that will be downloaded.
* @discussion Supported URLs include assets-library, Photo framework URLs (ph://), HTTP, HTTPS, and FTP URLs. If the
* image is already available to the data source, it should be provided via <[ASMultiplexImageNodeDataSource
* @discussion Supported URLs include HTTP, HTTPS, AssetsLibrary, and FTP URLs as well as Photos framework URLs (see note).
*
* If the image is already available to the data source, it should be provided via <[ASMultiplexImageNodeDataSource
* multiplexImageNode:imageForImageIdentifier:]> instead.
* @returns An NSURL for the image identified by `imageIdentifier`, or nil if none is available.
* @return An NSURL for the image identified by `imageIdentifier`, or nil if none is available.
* @see `+[NSURL URLWithAssetLocalIdentifier:targetSize:contentMode:options:]` below.
*/
- (NSURL *)multiplexImageNode:(ASMultiplexImageNode *)imageNode URLForImageIdentifier:(id)imageIdentifier;

@end

#pragma mark -

@interface NSURL (ASPhotosFrameworkURLs)

/**
* @abstract Create an NSURL that specifies an image from the Photos framework.
*
* @discussion When implementing `-multiplexImageNode:URLForImageIdentifier:`, you can return a URL
* created by this method and the image node will attempt to load the image from the Photos framework.
* @note The `synchronous` flag in `options` is ignored.
* @note The `Opportunistic` delivery mode is not supported and will be treated as `HighQualityFormat`.
*/
+ (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier
targetSize:(CGSize)targetSize
contentMode:(PHImageContentMode)contentMode
options:(PHImageRequestOptions *)options;

@end
92 changes: 57 additions & 35 deletions AsyncDisplayKit/ASMultiplexImageNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#import "ASBaseDefines.h"
#import "ASDisplayNode+Subclasses.h"
#import "ASLog.h"
#import "ASPhotosFrameworkImageRequest.h"

#if !AS_IOS8_SDK_OR_LATER
#error ASMultiplexImageNode can be used on iOS 7, but must be linked against the iOS 8 SDK.
Expand All @@ -26,8 +27,6 @@
NSString *const ASMultiplexImageNodeErrorDomain = @"ASMultiplexImageNodeErrorDomain";

static NSString *const kAssetsLibraryURLScheme = @"assets-library";
static NSString *const kPHAssetURLScheme = @"ph";
static NSString *const kPHAssetURLPrefix = @"ph://";

/**
@abstract Signature for the block to be performed after an image has loaded.
Expand Down Expand Up @@ -120,14 +119,14 @@ - (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imag
- (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock;

/**
@abstract Loads the image corresponding to the given assetURL from the Photos framework.
@abstract Loads the image corresponding to the given image request from the Photos framework.
@param imageIdentifier The identifier for the image to be loaded. May not be nil.
@param assetURL The photos framework URL (e.g., "ph://identifier") of the image to load, from PHAsset. May not be nil.
@param request The photos image request to load. May not be nil.
@param completionBlock The block to be performed when the image has been loaded, if possible. May not be nil.
@param image The image that was loaded. May be nil if no image could be downloaded.
@param error An error describing why the load failed, if it failed; nil otherwise.
*/
- (void)_loadPHAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock;
- (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock;

/**
@abstract Downloads the image corresponding to the given imageIdentifier from the given URL.
Expand Down Expand Up @@ -457,8 +456,8 @@ - (void)_loadNextImage
}];
}
// Likewise, if it's a iOS 8 Photo asset, we need to fetch it accordingly.
else if (AS_AT_LEAST_IOS8 && [[nextImageURL scheme] isEqualToString:kPHAssetURLScheme]) {
[self _loadPHAssetWithIdentifier:nextImageIdentifier URL:nextImageURL completion:^(UIImage *image, NSError *error) {
else if (ASPhotosFrameworkImageRequest *request = [ASPhotosFrameworkImageRequest requestWithURL:nextImageURL]) {
[self _loadPHAssetWithRequest:request identifier:nextImageIdentifier completion:^(UIImage *image, NSError *error) {
ASMultiplexImageNodeCLogDebug(@"[%p] Acquired next image (%@) from Photos Framework", weakSelf, nextImageIdentifier);
finishedLoadingBlock(image, nextImageIdentifier, error);
}];
Expand Down Expand Up @@ -512,38 +511,48 @@ - (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL com
}];
}

- (void)_loadPHAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock
- (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock
{
ASDisplayNodeAssert(AS_AT_LEAST_IOS8, @"PhotosKit is unavailable on iOS 7.");
ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required");
ASDisplayNodeAssertNotNil(assetURL, @"assetURL is required");
ASDisplayNodeAssertNotNil(request, @"request is required");
ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required");

// Get the PHAsset itself.
ASDisplayNodeAssertTrue([[assetURL absoluteString] hasPrefix:kPHAssetURLPrefix]);
NSString *assetIdentifier = [[assetURL absoluteString] substringFromIndex:[kPHAssetURLPrefix length]];
PHFetchResult *assetFetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:@[assetIdentifier] options:nil];
if ([assetFetchResult count] == 0) {
// Error.
completionBlock(nil, nil);
return;
}

// Get the best image we can.
PHAsset *imageAsset = [assetFetchResult firstObject];

PHImageRequestOptions *requestOptions = [[PHImageRequestOptions alloc] init];
requestOptions.version = PHImageRequestOptionsVersionCurrent;
requestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
requestOptions.resizeMode = PHImageRequestOptionsResizeModeNone;

[[PHImageManager defaultManager] requestImageForAsset:imageAsset
targetSize:CGSizeMake(2048.0, 2048.0) // Ideally we would use PHImageManagerMaximumSize and kill the options, but we get back nil when requesting images of video assets. rdar://18447788
contentMode:PHImageContentModeDefault
options:requestOptions
resultHandler:^(UIImage *image, NSDictionary *info) {
completionBlock(image, info[PHImageErrorKey]);
}];

// This is sometimes called on main but there's no reason to stay there
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
// Get the PHAsset itself.
PHFetchResult *assetFetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:@[request.assetIdentifier] options:nil];
if ([assetFetchResult count] == 0) {
// Error.
completionBlock(nil, nil);
return;
}

PHAsset *imageAsset = [assetFetchResult firstObject];
PHImageRequestOptions *options = [request.options copy];

// We don't support opportunistic delivery – one request, one image.
if (options.deliveryMode == PHImageRequestOptionsDeliveryModeOpportunistic) {
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
}

if (options.deliveryMode == PHImageRequestOptionsDeliveryModeHighQualityFormat) {
// Without this flag the result will be delivered on the main queue, which is pointless
// But synchronous -> HighQualityFormat so we only use it if high quality format is specified
options.synchronous = YES;
}

PHImageManager *imageManager = self.imageManager ?: PHImageManager.defaultManager;
[imageManager requestImageForAsset:imageAsset targetSize:request.targetSize contentMode:request.contentMode options:options resultHandler:^(UIImage *image, NSDictionary *info) {
if (NSThread.isMainThread) {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
completionBlock(image, info[PHImageErrorKey]);
});
} else {
completionBlock(image, info[PHImageErrorKey]);
}
}];
});
}

- (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image))completionBlock
Expand Down Expand Up @@ -643,3 +652,16 @@ - (BOOL)_shouldClearFetchedImageData {
}

@end

@implementation NSURL (ASPhotosFrameworkURLs)

+ (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(PHImageRequestOptions *)options
{
ASPhotosFrameworkImageRequest *request = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:assetLocalIdentifier];
request.options = options;
request.contentMode = contentMode;
request.targetSize = targetSize;
return request.url;
}

@end
1 change: 1 addition & 0 deletions AsyncDisplayKit/AsyncDisplayKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#import <AsyncDisplayKit/ASBasicImageDownloader.h>
#import <AsyncDisplayKit/ASMultiplexImageNode.h>
#import <AsyncDisplayKit/ASNetworkImageNode.h>
#import <AsyncDisplayKit/ASPhotosFrameworkImageRequest.h>

#import <AsyncDisplayKit/ASTableView.h>
#import <AsyncDisplayKit/ASCollectionView.h>
Expand Down
66 changes: 66 additions & 0 deletions AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// ASPhotosFrameworkImageRequest.h
// AsyncDisplayKit
//
// Created by Adlai Holler on 9/25/15.
// Copyright © 2015 Facebook. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <Photos/Photos.h>

// NS_ASSUME_NONNULL_BEGIN

extern NSString *const ASPhotosURLScheme;

/**
@abstract Use ASPhotosFrameworkImageRequest to encapsulate all the information needed to request an image from
the Photos framework and store it in a URL.
*/
@interface ASPhotosFrameworkImageRequest : NSObject <NSCopying>

- (instancetype)initWithAssetIdentifier:(NSString *)assetIdentifier NS_DESIGNATED_INITIALIZER;

/**
@return A new image request deserialized from `url`, or nil if `url` is not a valid photos URL.
*/
+ (/*nullable*/ ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url;

/**
@abstract The asset identifier for this image request provided during initialization.
*/
@property (nonatomic, readonly) NSString *assetIdentifier;

/**
@abstract The target size for this image request. Defaults to `PHImageManagerMaximumSize`.
*/
@property (nonatomic) CGSize targetSize;

/**
@abstract The content mode for this image request. Defaults to `PHImageContentModeDefault`.

@see `PHImageManager`
*/
@property (nonatomic) PHImageContentMode contentMode;

/**
@abstract The options specified for this request. Default value is the result of `[PHImageRequestOptions new]`.

@discussion Some properties of this object are ignored when converting this request into a URL.
As of iOS SDK 9.0, these properties are `progressHandler` and `synchronous`.
*/
@property (nonatomic, strong) PHImageRequestOptions *options;

/**
@return A new URL converted from this request.
*/
@property (nonatomic, readonly) NSURL *url;

/**
@return `YES` if `object` is an equivalent image request, `NO` otherwise.
*/
- (BOOL)isEqual:(id)object;

@end

// NS_ASSUME_NONNULL_END
Loading