Merge pull request #687 from Adlai-Holler/AdvancedPhotos

Advanced Photos Integration
This commit is contained in:
appleguy
2015-10-02 16:40:58 -07:00
7 changed files with 391 additions and 38 deletions

View File

@@ -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 */; };
@@ -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>"; };
@@ -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 */,
@@ -836,6 +844,8 @@
058D09E1195D050800B7D73C /* Details */ = {
isa = PBXGroup;
children = (
CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */,
CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */,
058D09E2195D050800B7D73C /* _ASDisplayLayer.h */,
058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */,
058D09E4195D050800B7D73C /* _ASDisplayView.h */,
@@ -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 */,
@@ -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 */,
@@ -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;
};
@@ -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 */,

View File

@@ -9,6 +9,7 @@
#import <AsyncDisplayKit/ASImageNode.h>
#import <AsyncDisplayKit/ASImageProtocols.h>
#import <Photos/Photos.h>
@protocol ASMultiplexImageNodeDelegate;
@protocol ASMultiplexImageNodeDataSource;
@@ -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
@@ -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

View File

@@ -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.
@@ -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.
@@ -120,14 +119,14 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
- (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.
@@ -457,8 +456,8 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
}];
}
// 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);
}];
@@ -512,38 +511,48 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
}];
}
- (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
@@ -643,3 +652,16 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
}
@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

View File

@@ -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>

View File

@@ -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

View File

@@ -0,0 +1,161 @@
//
// ASPhotosFrameworkImageRequest.m
// AsyncDisplayKit
//
// Created by Adlai Holler on 9/25/15.
// Copyright © 2015 Facebook. All rights reserved.
//
#import "ASPhotosFrameworkImageRequest.h"
#import "ASBaseDefines.h"
#import "ASAvailability.h"
NSString *const ASPhotosURLScheme = @"ph";
static NSString *const _ASPhotosURLQueryKeyWidth = @"width";
static NSString *const _ASPhotosURLQueryKeyHeight = @"height";
// value is PHImageContentMode value
static NSString *const _ASPhotosURLQueryKeyContentMode = @"contentmode";
// value is PHImageRequestOptionsResizeMode value
static NSString *const _ASPhotosURLQueryKeyResizeMode = @"resizemode";
// value is PHImageRequestOptionsDeliveryMode value
static NSString *const _ASPhotosURLQueryKeyDeliveryMode = @"deliverymode";
// value is PHImageRequestOptionsVersion value
static NSString *const _ASPhotosURLQueryKeyVersion = @"version";
// value is 0 or 1
static NSString *const _ASPhotosURLQueryKeyAllowNetworkAccess = @"network";
static NSString *const _ASPhotosURLQueryKeyCropOriginX = @"crop_x";
static NSString *const _ASPhotosURLQueryKeyCropOriginY = @"crop_y";
static NSString *const _ASPhotosURLQueryKeyCropWidth = @"crop_w";
static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h";
@implementation ASPhotosFrameworkImageRequest
- (instancetype)init
{
ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER();
self = [self initWithAssetIdentifier:@""];
return nil;
}
- (instancetype)initWithAssetIdentifier:(NSString *)assetIdentifier
{
self = [super init];
if (self) {
_assetIdentifier = assetIdentifier;
_options = [PHImageRequestOptions new];
_contentMode = PHImageContentModeDefault;
_targetSize = PHImageManagerMaximumSize;
}
return self;
}
#pragma mark NSCopying
- (id)copyWithZone:(NSZone *)zone
{
ASPhotosFrameworkImageRequest *copy = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:self.assetIdentifier];
copy.options = [self.options copy];
copy.targetSize = self.targetSize;
copy.contentMode = self.contentMode;
return copy;
}
#pragma mark Converting to URL
- (NSURL *)url
{
NSURLComponents *comp = [NSURLComponents new];
comp.scheme = ASPhotosURLScheme;
comp.host = _assetIdentifier;
NSMutableArray *queryItems = [NSMutableArray arrayWithObjects:
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyWidth value:@(_targetSize.width).stringValue],
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyHeight value:@(_targetSize.height).stringValue],
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyVersion value:@(_options.version).stringValue],
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyContentMode value:@(_contentMode).stringValue],
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyAllowNetworkAccess value:@(_options.networkAccessAllowed).stringValue],
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyResizeMode value:@(_options.resizeMode).stringValue],
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyDeliveryMode value:@(_options.deliveryMode).stringValue]
, nil];
CGRect cropRect = _options.normalizedCropRect;
if (!CGRectIsEmpty(cropRect)) {
[queryItems addObjectsFromArray:@[
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropOriginX value:@(cropRect.origin.x).stringValue],
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropOriginY value:@(cropRect.origin.y).stringValue],
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropWidth value:@(cropRect.size.width).stringValue],
[NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropHeight value:@(cropRect.size.height).stringValue]
]];
}
comp.queryItems = queryItems;
return comp.URL;
}
#pragma mark Converting from URL
+ (ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url
{
// not a photos URL or iOS < 8
if (![url.scheme isEqualToString:ASPhotosURLScheme] || !AS_AT_LEAST_IOS8) {
return nil;
}
NSURLComponents *comp = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
ASPhotosFrameworkImageRequest *request = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:url.host];
CGRect cropRect = CGRectZero;
CGSize targetSize = PHImageManagerMaximumSize;
for (NSURLQueryItem *item in comp.queryItems) {
if ([_ASPhotosURLQueryKeyAllowNetworkAccess isEqualToString:item.name]) {
request.options.networkAccessAllowed = item.value.boolValue;
} else if ([_ASPhotosURLQueryKeyWidth isEqualToString:item.name]) {
targetSize.width = item.value.doubleValue;
} else if ([_ASPhotosURLQueryKeyHeight isEqualToString:item.name]) {
targetSize.height = item.value.doubleValue;
} else if ([_ASPhotosURLQueryKeyContentMode isEqualToString:item.name]) {
request.contentMode = (PHImageContentMode)item.value.integerValue;
} else if ([_ASPhotosURLQueryKeyVersion isEqualToString:item.name]) {
request.options.version = (PHImageRequestOptionsVersion)item.value.integerValue;
} else if ([_ASPhotosURLQueryKeyCropOriginX isEqualToString:item.name]) {
cropRect.origin.x = item.value.doubleValue;
} else if ([_ASPhotosURLQueryKeyCropOriginY isEqualToString:item.name]) {
cropRect.origin.y = item.value.doubleValue;
} else if ([_ASPhotosURLQueryKeyCropWidth isEqualToString:item.name]) {
cropRect.size.width = item.value.doubleValue;
} else if ([_ASPhotosURLQueryKeyCropHeight isEqualToString:item.name]) {
cropRect.size.height = item.value.doubleValue;
} else if ([_ASPhotosURLQueryKeyResizeMode isEqualToString:item.name]) {
request.options.resizeMode = (PHImageRequestOptionsResizeMode)item.value.integerValue;
} else if ([_ASPhotosURLQueryKeyDeliveryMode isEqualToString:item.name]) {
request.options.deliveryMode = (PHImageRequestOptionsDeliveryMode)item.value.integerValue;
}
}
request.targetSize = targetSize;
request.options.normalizedCropRect = cropRect;
return request;
}
#pragma mark NSObject
- (BOOL)isEqual:(id)object
{
if (![object isKindOfClass:ASPhotosFrameworkImageRequest.class]) {
return NO;
}
ASPhotosFrameworkImageRequest *other = object;
return [other.assetIdentifier isEqualToString:self.assetIdentifier] &&
other.contentMode == self.contentMode &&
CGSizeEqualToSize(other.targetSize, self.targetSize) &&
CGRectEqualToRect(other.options.normalizedCropRect, self.options.normalizedCropRect) &&
other.options.resizeMode == self.options.resizeMode &&
other.options.version == self.options.version;
}
@end

View File

@@ -0,0 +1,60 @@
//
// ASPhotosFrameworkImageRequestTests.m
// AsyncDisplayKit
//
// Created by Adlai Holler on 9/25/15.
// Copyright © 2015 Facebook. All rights reserved.
//
#import <XCTest/XCTest.h>
#import "ASPhotosFrameworkImageRequest.h"
static NSString *const kTestAssetID = @"testAssetID";
@interface ASPhotosFrameworkImageRequestTests : XCTestCase
@end
@implementation ASPhotosFrameworkImageRequestTests
#pragma mark Example Data
+ (ASPhotosFrameworkImageRequest *)exampleImageRequest
{
ASPhotosFrameworkImageRequest *req = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:kTestAssetID];
req.options.networkAccessAllowed = YES;
req.options.normalizedCropRect = CGRectMake(0.2, 0.1, 0.6, 0.8);
req.targetSize = CGSizeMake(1024, 1536);
req.contentMode = PHImageContentModeAspectFill;
req.options.version = PHImageRequestOptionsVersionOriginal;
req.options.resizeMode = PHImageRequestOptionsResizeModeFast;
return req;
}
+ (NSURL *)urlForExampleImageRequest
{
NSString *str = [NSString stringWithFormat:@"ph://%@?width=1024&height=1536&version=2&contentmode=1&network=1&resizemode=1&deliverymode=0&crop_x=0.2&crop_y=0.1&crop_w=0.6&crop_h=0.8", kTestAssetID];
return [NSURL URLWithString:str];
}
#pragma mark Test cases
- (void)testThatConvertingToURLWorks
{
XCTAssertEqualObjects([self.class exampleImageRequest].url, [self.class urlForExampleImageRequest]);
}
- (void)testThatParsingFromURLWorks
{
NSURL *url = [self.class urlForExampleImageRequest];
XCTAssertEqualObjects([ASPhotosFrameworkImageRequest requestWithURL:url], [self.class exampleImageRequest]);
}
- (void)testThatCopyingWorks
{
ASPhotosFrameworkImageRequest *example = [self.class exampleImageRequest];
ASPhotosFrameworkImageRequest *copy = [[self.class exampleImageRequest] copy];
XCTAssertEqualObjects(example, copy);
}
@end