TTOpenInAppActivity.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. //
  2. // TTOpenInAppActivity.m
  3. //
  4. // Created by Tobias Tiemerding on 12/25/12.
  5. // Copyright (c) 2012-2013 Tobias Tiemerding
  6. //
  7. // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8. // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
  9. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  10. #import "TTOpenInAppActivity.h"
  11. #import <MobileCoreServices/MobileCoreServices.h> // For UTI
  12. #import <ImageIO/ImageIO.h>
  13. @interface TTOpenInAppActivity () <UIActionSheetDelegate>
  14. // Private attributes
  15. @property (nonatomic, strong) NSArray *fileURLs;
  16. @property (atomic) CGRect rect;
  17. @property (nonatomic, strong) UIBarButtonItem *barButtonItem;
  18. @property (nonatomic, strong) UIView *superView;
  19. @property (nonatomic, strong) UIDocumentInteractionController *docController;
  20. // Private methods
  21. - (NSString *)UTIForURL:(NSURL *)url;
  22. - (void)openDocumentInteractionControllerWithFileURL:(NSURL *)fileURL;
  23. - (void)openSelectFileActionSheet;
  24. @end
  25. @implementation TTOpenInAppActivity
  26. @synthesize rect = _rect;
  27. @synthesize superView = _superView;
  28. @synthesize superViewController = _superViewController;
  29. + (NSBundle *)bundle
  30. {
  31. NSBundle *bundle;
  32. NSURL *openInAppActivityBundleURL = [[NSBundle mainBundle] URLForResource:@"TTOpenInAppActivity" withExtension:@"bundle"];
  33. if (openInAppActivityBundleURL) {
  34. // TTOpenInAppActivity.bundle will likely only exist when used via CocoaPods
  35. bundle = [NSBundle bundleWithURL:openInAppActivityBundleURL];
  36. } else {
  37. bundle = [NSBundle mainBundle];
  38. }
  39. return bundle;
  40. }
  41. - (id)initWithView:(UIView *)view andRect:(CGRect)rect
  42. {
  43. if(self =[super init]){
  44. self.superView = view;
  45. self.rect = rect;
  46. }
  47. return self;
  48. }
  49. - (id)initWithView:(UIView *)view andBarButtonItem:(UIBarButtonItem *)barButtonItem
  50. {
  51. if(self =[super init]){
  52. self.superView = view;
  53. self.barButtonItem = barButtonItem;
  54. }
  55. return self;
  56. }
  57. - (NSString *)activityType
  58. {
  59. return NSStringFromClass([self class]);
  60. }
  61. - (NSString *)activityTitle
  62. {
  63. return NSLocalizedStringFromTableInBundle(@"Open in ...", @"TTOpenInAppActivityLocalizable", [TTOpenInAppActivity bundle], nil);
  64. }
  65. - (UIImage *)activityImage
  66. {
  67. if([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0){
  68. return [UIImage imageNamed:@"TTOpenInAppActivity8"];
  69. } else if([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0){
  70. return [UIImage imageNamed:@"TTOpenInAppActivity7"];
  71. } else {
  72. return [UIImage imageNamed:@"TTOpenInAppActivity"];
  73. }
  74. }
  75. - (BOOL)canPerformWithActivityItems:(NSArray *)activityItems
  76. {
  77. NSUInteger count = 0;
  78. for (id activityItem in activityItems) {
  79. if ([activityItem isKindOfClass:[NSURL class]] && [(NSURL *)activityItem isFileURL]) {
  80. count++;
  81. } else {
  82. if ([activityItem isKindOfClass:[NSDictionary class]]) {
  83. NSDictionary *itemDict = (NSDictionary *)activityItem;
  84. if ([itemDict[@"url"] isKindOfClass:[NSURL class]] && [(NSURL *)itemDict[@"url"] isFileURL]) {
  85. count++;
  86. }
  87. }
  88. }
  89. if ([activityItem isKindOfClass:[UIImage class]]) {
  90. count++;
  91. }
  92. }
  93. return (count >= 1);
  94. }
  95. - (void)prepareWithActivityItems:(NSArray *)activityItems
  96. {
  97. NSMutableArray *fileURLs = [NSMutableArray array];
  98. for (id activityItem in activityItems) {
  99. if ([activityItem isKindOfClass:[NSURL class]] && [(NSURL *)activityItem isFileURL]) {
  100. [fileURLs addObject:activityItem];
  101. } else {
  102. if ([activityItem isKindOfClass:[NSDictionary class]]) {
  103. NSDictionary *itemDict = (NSDictionary *)activityItem;
  104. if ([itemDict[@"url"] isKindOfClass:[NSURL class]] && [(NSURL *)itemDict[@"url"] isFileURL]) {
  105. [fileURLs addObject:itemDict[@"url"]];
  106. }
  107. }
  108. }
  109. if ([activityItem isKindOfClass:[UIImage class]]) {
  110. NSURL *imageURL = [self localFileURLForImage:activityItem];
  111. [fileURLs addObject:imageURL];
  112. }
  113. }
  114. self.fileURLs = [fileURLs copy];
  115. }
  116. - (void)performActivity
  117. {
  118. if(!self.superViewController){
  119. [self activityDidFinish:YES];
  120. return;
  121. }
  122. void(^presentOpenIn)(void) = ^{
  123. if (self.fileURLs.count > 1) {
  124. [self openSelectFileActionSheet];
  125. }
  126. else {
  127. // Open UIDocumentInteractionController
  128. [self openDocumentInteractionControllerWithFileURL:self.fileURLs.lastObject];
  129. }
  130. };
  131. // Check to see if it's presented via popover
  132. if ([self.superViewController respondsToSelector:@selector(dismissPopoverAnimated:)]) {
  133. [self.superViewController dismissPopoverAnimated:YES];
  134. [((UIPopoverController *)self.superViewController).delegate popoverControllerDidDismissPopover:self.superViewController];
  135. presentOpenIn();
  136. } else if([self.superViewController presentingViewController]) { // Not in popover, dismiss as if iPhone
  137. [self.superViewController dismissViewControllerAnimated:YES completion:^(void){
  138. presentOpenIn();
  139. }];
  140. } else {
  141. presentOpenIn();
  142. }
  143. }
  144. #pragma mark - Helper
  145. - (NSString *)UTIForURL:(NSURL *)url
  146. {
  147. CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)url.pathExtension, NULL);
  148. return (NSString *)CFBridgingRelease(UTI) ;
  149. }
  150. - (void)openDocumentInteractionControllerWithFileURL:(NSURL *)fileURL
  151. {
  152. // Open "Open in"-menu
  153. self.docController = [UIDocumentInteractionController interactionControllerWithURL:fileURL];
  154. self.docController.delegate = self;
  155. self.docController.UTI = [self UTIForURL:fileURL];
  156. BOOL sucess; // Sucess is true if it was possible to open the controller and there are apps available
  157. if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone){
  158. sucess = [self.docController presentOpenInMenuFromRect:CGRectZero inView:self.superView animated:YES];
  159. } else {
  160. if(self.barButtonItem){
  161. sucess = [self.docController presentOpenInMenuFromBarButtonItem:self.barButtonItem animated:YES];
  162. } else {
  163. sucess = [self.docController presentOpenInMenuFromRect:self.rect inView:self.superView animated:YES];
  164. }
  165. }
  166. if(!sucess){
  167. // There is no app to handle this file
  168. NSString *deviceType = [UIDevice currentDevice].localizedModel;
  169. NSString *message = [NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"Your %@ doesn't seem to have any other Apps installed that can open this document.", @"TTOpenInAppActivityLocalizable", [TTOpenInAppActivity bundle], nil), deviceType];
  170. // Display alert
  171. UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTableInBundle(@"No suitable App installed", @"TTOpenInAppActivityLocalizable", [TTOpenInAppActivity bundle], nil)
  172. message:message preferredStyle:UIAlertControllerStyleAlert];
  173. [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTableInBundle(@"OK", @"TTOpenInAppActivityLocalizable", [TTOpenInAppActivity bundle], nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
  174. // Inform app that the activity has finished
  175. // Return NO because the service was canceled and did not finish because of an error.
  176. // http://developer.apple.com/library/ios/#documentation/uikit/reference/UIActivity_Class/Reference/Reference.html
  177. [self activityDidFinish:NO];
  178. [_superViewController dismissViewControllerAnimated:YES completion:nil];
  179. }]];
  180. [_superViewController presentViewController:alertController animated:YES completion:nil];
  181. }
  182. }
  183. - (void)dismissDocumentInteractionControllerAnimated:(BOOL)animated {
  184. // Hide menu
  185. [self.docController dismissMenuAnimated:animated];
  186. // Inform app that the activity has finished
  187. [self activityDidFinish:NO];
  188. }
  189. - (void)openSelectFileActionSheet
  190. {
  191. UIAlertController *actionSheet = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTableInBundle(@"Select a file", @"TTOpenInAppActivityLocalizable", [TTOpenInAppActivity bundle], nil)
  192. message:nil preferredStyle:UIAlertControllerStyleActionSheet];
  193. for (NSURL *fileURL in self.fileURLs) {
  194. [actionSheet addAction:[UIAlertAction actionWithTitle:[fileURL lastPathComponent] style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
  195. [self openDocumentInteractionControllerWithFileURL:fileURL];
  196. }]];
  197. }
  198. [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTableInBundle(@"Cancel", @"TTOpenInAppActivityLocalizable", [TTOpenInAppActivity bundle], nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
  199. [self activityDidFinish:NO];
  200. [_superViewController dismissViewControllerAnimated:YES completion:nil];
  201. }]];
  202. if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone){
  203. } else {
  204. if(self.barButtonItem){
  205. ((UIViewController *)_superViewController).modalPresentationStyle = UIModalPresentationPopover;
  206. ((UIViewController *)_superViewController).popoverPresentationController.barButtonItem = self.barButtonItem;
  207. } else {
  208. ((UIViewController *)_superViewController).modalPresentationStyle = UIModalPresentationPopover;
  209. ((UIViewController *)_superViewController).popoverPresentationController.sourceRect = self.rect;
  210. ((UIViewController *)_superViewController).popoverPresentationController.sourceView = self.superView;
  211. }
  212. }
  213. [_superViewController presentViewController:actionSheet animated:YES completion:nil];
  214. }
  215. #pragma mark - UIDocumentInteractionControllerDelegate
  216. - (void) documentInteractionControllerWillPresentOpenInMenu:(UIDocumentInteractionController *)controller
  217. {
  218. // Inform delegate
  219. if([self.delegate respondsToSelector:@selector(openInAppActivityWillPresentDocumentInteractionController:)]) {
  220. [self.delegate openInAppActivityWillPresentDocumentInteractionController:self];
  221. }
  222. }
  223. - (void) documentInteractionControllerDidDismissOpenInMenu: (UIDocumentInteractionController *) controller
  224. {
  225. // Inform delegate
  226. if([self.delegate respondsToSelector:@selector(openInAppActivityDidDismissDocumentInteractionController:)]) {
  227. [self.delegate openInAppActivityDidDismissDocumentInteractionController:self];
  228. }
  229. }
  230. - (void) documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application
  231. {
  232. // Inform delegate
  233. if([self.delegate respondsToSelector:@selector(openInAppActivityDidEndSendingToApplication:)]) {
  234. [self.delegate openInAppActivityDidDismissDocumentInteractionController:self];
  235. }
  236. if ([self.delegate respondsToSelector:@selector(openInAppActivityDidSendToApplication:)]) {
  237. [self.delegate openInAppActivityDidSendToApplication:application];
  238. }
  239. // Inform app that the activity has finished
  240. [self activityDidFinish:YES];
  241. }
  242. #pragma mark - Image conversion
  243. - (NSURL *)localFileURLForImage:(UIImage *)image
  244. {
  245. // save this image to a temp folder
  246. NSURL *tmpDirURL = [NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES];
  247. NSString *filename = [[NSUUID UUID] UUIDString];
  248. NSURL *fileURL;
  249. // if there is an images array, this is an animated image.
  250. if (image.images) {
  251. fileURL = [[tmpDirURL URLByAppendingPathComponent:filename] URLByAppendingPathExtension:@"gif"];
  252. NSInteger frameCount = image.images.count;
  253. CGFloat frameDuration = image.duration / frameCount;
  254. NSDictionary *fileProperties = @{
  255. (__bridge id)kCGImagePropertyGIFDictionary: @{
  256. (__bridge id)kCGImagePropertyGIFLoopCount: @0, // 0 means loop forever
  257. }
  258. };
  259. NSDictionary *frameProperties = @{
  260. (__bridge id)kCGImagePropertyGIFDictionary: @{
  261. (__bridge id)kCGImagePropertyGIFDelayTime: [NSNumber numberWithFloat:frameDuration],
  262. }
  263. };
  264. CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)fileURL, kUTTypeGIF, frameCount, NULL);
  265. CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)fileProperties);
  266. for (NSUInteger i = 0; i < frameCount; i++) {
  267. @autoreleasepool {
  268. UIImage *frameImage = [image.images objectAtIndex:i];
  269. CGImageDestinationAddImage(destination, frameImage.CGImage, (__bridge CFDictionaryRef)frameProperties);
  270. }
  271. }
  272. NSAssert(CGImageDestinationFinalize(destination),@"Failed to create animated image.");
  273. CFRelease(destination);
  274. } else {
  275. fileURL = [[tmpDirURL URLByAppendingPathComponent:filename] URLByAppendingPathExtension:@"jpg"];
  276. NSData *data = [NSData dataWithData:UIImageJPEGRepresentation(image, 0.8)];
  277. [[NSFileManager defaultManager] createFileAtPath:[fileURL path] contents:data attributes:nil];
  278. }
  279. return fileURL;
  280. }
  281. @end