MWPhoto.m 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. //
  2. // MWPhoto.m
  3. // MWPhotoBrowser
  4. //
  5. // Created by Michael Waterfall on 17/10/2010.
  6. // Copyright 2010 d3i. All rights reserved.
  7. //
  8. #import <AssetsLibrary/AssetsLibrary.h>
  9. #import "MWPhoto.h"
  10. #import "MWPhotoBrowser.h"
  11. @interface MWPhoto () {
  12. BOOL _loadingInProgress;
  13. PHImageRequestID _assetRequestID;
  14. PHImageRequestID _assetVideoRequestID;
  15. }
  16. @property (nonatomic, strong) UIImage *image;
  17. @property (nonatomic, strong) NSURL *photoURL;
  18. @property (nonatomic, strong) PHAsset *asset;
  19. @property (nonatomic) CGSize assetTargetSize;
  20. - (void)imageLoadingComplete;
  21. @end
  22. @implementation MWPhoto
  23. @synthesize underlyingImage = _underlyingImage; // synth property from protocol
  24. #pragma mark - Class Methods
  25. + (MWPhoto *)photoWithImage:(UIImage *)image {
  26. return [[MWPhoto alloc] initWithImage:image];
  27. }
  28. + (MWPhoto *)photoWithURL:(NSURL *)url {
  29. return [[MWPhoto alloc] initWithURL:url];
  30. }
  31. + (MWPhoto *)photoWithAsset:(PHAsset *)asset targetSize:(CGSize)targetSize {
  32. return [[MWPhoto alloc] initWithAsset:asset targetSize:targetSize];
  33. }
  34. + (MWPhoto *)videoWithURL:(NSURL *)url {
  35. return [[MWPhoto alloc] initWithVideoURL:url];
  36. }
  37. #pragma mark - Init
  38. - (id)init {
  39. if ((self = [super init])) {
  40. self.emptyImage = YES;
  41. [self setup];
  42. }
  43. return self;
  44. }
  45. - (id)initWithImage:(UIImage *)image {
  46. if ((self = [super init])) {
  47. self.image = image;
  48. [self setup];
  49. }
  50. return self;
  51. }
  52. - (id)initWithURL:(NSURL *)url {
  53. if ((self = [super init])) {
  54. self.photoURL = url;
  55. [self setup];
  56. }
  57. return self;
  58. }
  59. - (id)initWithAsset:(PHAsset *)asset targetSize:(CGSize)targetSize {
  60. if ((self = [super init])) {
  61. self.asset = asset;
  62. self.assetTargetSize = targetSize;
  63. self.isVideo = asset.mediaType == PHAssetMediaTypeVideo;
  64. [self setup];
  65. }
  66. return self;
  67. }
  68. - (id)initWithVideoURL:(NSURL *)url {
  69. if ((self = [super init])) {
  70. self.videoURL = url;
  71. self.isVideo = YES;
  72. self.emptyImage = YES;
  73. [self setup];
  74. }
  75. return self;
  76. }
  77. - (void)setup {
  78. _assetRequestID = PHInvalidImageRequestID;
  79. _assetVideoRequestID = PHInvalidImageRequestID;
  80. }
  81. - (void)dealloc {
  82. [self cancelAnyLoading];
  83. }
  84. #pragma mark - Video
  85. - (void)setVideoURL:(NSURL *)videoURL {
  86. _videoURL = videoURL;
  87. self.isVideo = YES;
  88. }
  89. - (void)getVideoURL:(void (^)(NSURL *url))completion {
  90. if (_videoURL) {
  91. completion(_videoURL);
  92. } else if (_asset && _asset.mediaType == PHAssetMediaTypeVideo) {
  93. [self cancelVideoRequest]; // Cancel any existing
  94. PHVideoRequestOptions *options = [PHVideoRequestOptions new];
  95. options.networkAccessAllowed = YES;
  96. __weak __typeof(self) weakSelf = self;
  97. _assetVideoRequestID = [[PHImageManager defaultManager] requestAVAssetForVideo:_asset options:options resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) {
  98. // dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ // Testing
  99. __typeof(self) strongSelf = weakSelf;
  100. if (!strongSelf) return;
  101. strongSelf->_assetVideoRequestID = PHInvalidImageRequestID;
  102. if ([asset isKindOfClass:[AVURLAsset class]]) {
  103. completion(((AVURLAsset *)asset).URL);
  104. } else {
  105. completion(nil);
  106. }
  107. }];
  108. }
  109. }
  110. #pragma mark - MWPhoto Protocol Methods
  111. - (UIImage *)underlyingImage {
  112. return _underlyingImage;
  113. }
  114. - (void)loadUnderlyingImageAndNotify {
  115. NSAssert([[NSThread currentThread] isMainThread], @"This method must be called on the main thread.");
  116. if (_loadingInProgress) return;
  117. _loadingInProgress = YES;
  118. @try {
  119. if (self.underlyingImage) {
  120. [self imageLoadingComplete];
  121. } else {
  122. [self performLoadUnderlyingImageAndNotify];
  123. }
  124. }
  125. @catch (NSException *exception) {
  126. self.underlyingImage = nil;
  127. _loadingInProgress = NO;
  128. [self imageLoadingComplete];
  129. }
  130. @finally {
  131. }
  132. }
  133. // Set the underlyingImage
  134. - (void)performLoadUnderlyingImageAndNotify {
  135. // Get underlying image
  136. if (_image) {
  137. // We have UIImage!
  138. self.underlyingImage = _image;
  139. [self imageLoadingComplete];
  140. } else if (_photoURL) {
  141. // Check what type of url it is
  142. if ([[[_photoURL scheme] lowercaseString] isEqualToString:@"assets-library"]) {
  143. // Load from assets library
  144. [self _performLoadUnderlyingImageAndNotifyWithAssetsLibraryURL: _photoURL];
  145. } else if ([_photoURL isFileReferenceURL]) {
  146. // Load from local file async
  147. [self _performLoadUnderlyingImageAndNotifyWithLocalFileURL: _photoURL];
  148. }
  149. } else if (_asset) {
  150. // Load from photos asset
  151. [self _performLoadUnderlyingImageAndNotifyWithAsset: _asset targetSize:_assetTargetSize];
  152. } else {
  153. // Image is empty
  154. [self imageLoadingComplete];
  155. }
  156. }
  157. // Load from local file
  158. - (void)_performLoadUnderlyingImageAndNotifyWithLocalFileURL:(NSURL *)url {
  159. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  160. @autoreleasepool {
  161. @try {
  162. self.underlyingImage = [UIImage imageWithContentsOfFile:url.path];
  163. if (!_underlyingImage) {
  164. MWLog(@"Error loading photo from path: %@", url.path);
  165. }
  166. } @finally {
  167. [self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO];
  168. }
  169. }
  170. });
  171. }
  172. // Load from asset library async
  173. - (void)_performLoadUnderlyingImageAndNotifyWithAssetsLibraryURL:(NSURL *)url {
  174. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  175. @autoreleasepool {
  176. @try {
  177. PHAsset * asset = [[PHAsset fetchAssetsWithALAssetURLs:@[url] options:nil] firstObject];
  178. if (! asset) {
  179. MWLog(@"Photo from asset library error");
  180. [self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO];
  181. }
  182. PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
  183. options.synchronous = NO;
  184. options.networkAccessAllowed = NO;
  185. options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
  186. [[PHImageManager defaultManager] requestImageDataForAsset:asset
  187. options:options
  188. resultHandler:^(NSData * imageData,
  189. NSString * dataUTI,
  190. UIImageOrientation orientation,
  191. NSDictionary * info)
  192. {
  193. if (imageData) {
  194. self.underlyingImage = [UIImage imageWithData:imageData];
  195. [self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO];
  196. } else {
  197. MWLog(@"Photo from asset library error");
  198. [self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO];
  199. }
  200. }];
  201. } @catch (NSException *e) {
  202. MWLog(@"Photo from asset library error: %@", e);
  203. [self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO];
  204. }
  205. }
  206. });
  207. }
  208. // Load from photos library
  209. - (void)_performLoadUnderlyingImageAndNotifyWithAsset:(PHAsset *)asset targetSize:(CGSize)targetSize {
  210. PHImageManager *imageManager = [PHImageManager defaultManager];
  211. PHImageRequestOptions *options = [PHImageRequestOptions new];
  212. options.networkAccessAllowed = YES;
  213. options.resizeMode = PHImageRequestOptionsResizeModeFast;
  214. options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
  215. options.synchronous = false;
  216. options.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
  217. NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
  218. [NSNumber numberWithDouble: progress], @"progress",
  219. self, @"photo", nil];
  220. [[NSNotificationCenter defaultCenter] postNotificationName:MWPHOTO_PROGRESS_NOTIFICATION object:dict];
  221. };
  222. _assetRequestID = [imageManager requestImageForAsset:asset targetSize:targetSize contentMode:PHImageContentModeAspectFit options:options resultHandler:^(UIImage *result, NSDictionary *info) {
  223. dispatch_async(dispatch_get_main_queue(), ^{
  224. self.underlyingImage = result;
  225. [self imageLoadingComplete];
  226. });
  227. }];
  228. }
  229. // Release if we can get it again from path or url
  230. - (void)unloadUnderlyingImage {
  231. _loadingInProgress = NO;
  232. self.underlyingImage = nil;
  233. }
  234. - (void)imageLoadingComplete {
  235. NSAssert([[NSThread currentThread] isMainThread], @"This method must be called on the main thread.");
  236. // Complete so notify
  237. _loadingInProgress = NO;
  238. // Notify on next run loop
  239. [self performSelector:@selector(postCompleteNotification) withObject:nil afterDelay:0];
  240. }
  241. - (void)postCompleteNotification {
  242. [[NSNotificationCenter defaultCenter] postNotificationName:MWPHOTO_LOADING_DID_END_NOTIFICATION
  243. object:self];
  244. }
  245. - (void)cancelAnyLoading {
  246. [self cancelImageRequest];
  247. [self cancelVideoRequest];
  248. }
  249. - (void)cancelImageRequest {
  250. if (_assetRequestID != PHInvalidImageRequestID) {
  251. [[PHImageManager defaultManager] cancelImageRequest:_assetRequestID];
  252. _assetRequestID = PHInvalidImageRequestID;
  253. }
  254. }
  255. - (void)cancelVideoRequest {
  256. if (_assetVideoRequestID != PHInvalidImageRequestID) {
  257. [[PHImageManager defaultManager] cancelImageRequest:_assetVideoRequestID];
  258. _assetVideoRequestID = PHInvalidImageRequestID;
  259. }
  260. }
  261. @end