TTOpenInAppActivity.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  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. }
  82. if ([activityItem isKindOfClass:[UIImage class]]) {
  83. count++;
  84. }
  85. }
  86. return (count >= 1);
  87. }
  88. - (void)prepareWithActivityItems:(NSArray *)activityItems
  89. {
  90. NSMutableArray *fileURLs = [NSMutableArray array];
  91. for (id activityItem in activityItems) {
  92. if ([activityItem isKindOfClass:[NSURL class]] && [(NSURL *)activityItem isFileURL]) {
  93. [fileURLs addObject:activityItem];
  94. }
  95. if ([activityItem isKindOfClass:[UIImage class]]) {
  96. NSURL *imageURL = [self localFileURLForImage:activityItem];
  97. [fileURLs addObject:imageURL];
  98. }
  99. }
  100. self.fileURLs = [fileURLs copy];
  101. }
  102. - (void)performActivity
  103. {
  104. if(!self.superViewController){
  105. [self activityDidFinish:YES];
  106. return;
  107. }
  108. void(^presentOpenIn)(void) = ^{
  109. if (self.fileURLs.count > 1) {
  110. [self openSelectFileActionSheet];
  111. }
  112. else {
  113. // Open UIDocumentInteractionController
  114. [self openDocumentInteractionControllerWithFileURL:self.fileURLs.lastObject];
  115. }
  116. };
  117. // Check to see if it's presented via popover
  118. if ([self.superViewController respondsToSelector:@selector(dismissPopoverAnimated:)]) {
  119. [self.superViewController dismissPopoverAnimated:YES];
  120. [((UIPopoverController *)self.superViewController).delegate popoverControllerDidDismissPopover:self.superViewController];
  121. presentOpenIn();
  122. } else if([self.superViewController presentingViewController]) { // Not in popover, dismiss as if iPhone
  123. [self.superViewController dismissViewControllerAnimated:YES completion:^(void){
  124. presentOpenIn();
  125. }];
  126. } else {
  127. presentOpenIn();
  128. }
  129. }
  130. #pragma mark - Helper
  131. - (NSString *)UTIForURL:(NSURL *)url
  132. {
  133. CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)url.pathExtension, NULL);
  134. return (NSString *)CFBridgingRelease(UTI) ;
  135. }
  136. - (void)openDocumentInteractionControllerWithFileURL:(NSURL *)fileURL
  137. {
  138. // Open "Open in"-menu
  139. self.docController = [UIDocumentInteractionController interactionControllerWithURL:fileURL];
  140. self.docController.delegate = self;
  141. self.docController.UTI = [self UTIForURL:fileURL];
  142. BOOL sucess; // Sucess is true if it was possible to open the controller and there are apps available
  143. if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone){
  144. sucess = [self.docController presentOpenInMenuFromRect:CGRectZero inView:self.superView animated:YES];
  145. } else {
  146. if(self.barButtonItem){
  147. sucess = [self.docController presentOpenInMenuFromBarButtonItem:self.barButtonItem animated:YES];
  148. } else {
  149. sucess = [self.docController presentOpenInMenuFromRect:self.rect inView:self.superView animated:YES];
  150. }
  151. }
  152. if(!sucess){
  153. // There is no app to handle this file
  154. NSString *deviceType = [UIDevice currentDevice].localizedModel;
  155. 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];
  156. // Display alert
  157. UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedStringFromTableInBundle(@"No suitable App installed", @"TTOpenInAppActivityLocalizable", [TTOpenInAppActivity bundle], nil)
  158. message:message
  159. delegate:nil
  160. cancelButtonTitle:NSLocalizedStringFromTableInBundle(@"OK", @"TTOpenInAppActivityLocalizable", [TTOpenInAppActivity bundle], nil)
  161. otherButtonTitles:nil];
  162. [alert show];
  163. // Inform app that the activity has finished
  164. // Return NO because the service was canceled and did not finish because of an error.
  165. // http://developer.apple.com/library/ios/#documentation/uikit/reference/UIActivity_Class/Reference/Reference.html
  166. [self activityDidFinish:NO];
  167. }
  168. }
  169. - (void)dismissDocumentInteractionControllerAnimated:(BOOL)animated {
  170. // Hide menu
  171. [self.docController dismissMenuAnimated:animated];
  172. // Inform app that the activity has finished
  173. [self activityDidFinish:NO];
  174. }
  175. - (void)openSelectFileActionSheet
  176. {
  177. UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:NSLocalizedStringFromTableInBundle(@"Select a file", @"TTOpenInAppActivityLocalizable", [TTOpenInAppActivity bundle], nil)
  178. delegate:self
  179. cancelButtonTitle:nil
  180. destructiveButtonTitle:nil
  181. otherButtonTitles:nil];
  182. for (NSURL *fileURL in self.fileURLs) {
  183. [actionSheet addButtonWithTitle:[fileURL lastPathComponent]];
  184. }
  185. actionSheet.cancelButtonIndex = [actionSheet addButtonWithTitle:NSLocalizedStringFromTableInBundle(@"Cancel", @"TTOpenInAppActivityLocalizable", [TTOpenInAppActivity bundle], nil)];
  186. if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone){
  187. [actionSheet showFromRect:CGRectZero inView:self.superView animated:YES];
  188. } else {
  189. if(self.barButtonItem){
  190. [actionSheet showFromBarButtonItem:self.barButtonItem animated:YES];
  191. } else {
  192. [actionSheet showFromRect:self.rect inView:self.superView animated:YES];
  193. }
  194. }
  195. }
  196. #pragma mark - UIDocumentInteractionControllerDelegate
  197. - (void) documentInteractionControllerWillPresentOpenInMenu:(UIDocumentInteractionController *)controller
  198. {
  199. // Inform delegate
  200. if([self.delegate respondsToSelector:@selector(openInAppActivityWillPresentDocumentInteractionController:)]) {
  201. [self.delegate openInAppActivityWillPresentDocumentInteractionController:self];
  202. }
  203. }
  204. - (void) documentInteractionControllerDidDismissOpenInMenu: (UIDocumentInteractionController *) controller
  205. {
  206. // Inform delegate
  207. if([self.delegate respondsToSelector:@selector(openInAppActivityDidDismissDocumentInteractionController:)]) {
  208. [self.delegate openInAppActivityDidDismissDocumentInteractionController:self];
  209. }
  210. }
  211. - (void) documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application
  212. {
  213. // Inform delegate
  214. if([self.delegate respondsToSelector:@selector(openInAppActivityDidEndSendingToApplication:)]) {
  215. [self.delegate openInAppActivityDidDismissDocumentInteractionController:self];
  216. }
  217. if ([self.delegate respondsToSelector:@selector(openInAppActivityDidSendToApplication:)]) {
  218. [self.delegate openInAppActivityDidSendToApplication:application];
  219. }
  220. // Inform app that the activity has finished
  221. [self activityDidFinish:YES];
  222. }
  223. #pragma mark - Action sheet delegate
  224. - (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
  225. {
  226. if (buttonIndex != actionSheet.cancelButtonIndex) {
  227. [self openDocumentInteractionControllerWithFileURL:self.fileURLs[buttonIndex]];
  228. } else {
  229. // Inform app that the activity has finished
  230. [self activityDidFinish:NO];
  231. }
  232. }
  233. #pragma mark - Image conversion
  234. - (NSURL *)localFileURLForImage:(UIImage *)image
  235. {
  236. // save this image to a temp folder
  237. NSURL *tmpDirURL = [NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES];
  238. NSString *filename = [[NSUUID UUID] UUIDString];
  239. NSURL *fileURL;
  240. // if there is an images array, this is an animated image.
  241. if (image.images) {
  242. fileURL = [[tmpDirURL URLByAppendingPathComponent:filename] URLByAppendingPathExtension:@"gif"];
  243. NSInteger frameCount = image.images.count;
  244. CGFloat frameDuration = image.duration / frameCount;
  245. NSDictionary *fileProperties = @{
  246. (__bridge id)kCGImagePropertyGIFDictionary: @{
  247. (__bridge id)kCGImagePropertyGIFLoopCount: @0, // 0 means loop forever
  248. }
  249. };
  250. NSDictionary *frameProperties = @{
  251. (__bridge id)kCGImagePropertyGIFDictionary: @{
  252. (__bridge id)kCGImagePropertyGIFDelayTime: [NSNumber numberWithFloat:frameDuration],
  253. }
  254. };
  255. CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)fileURL, kUTTypeGIF, frameCount, NULL);
  256. CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)fileProperties);
  257. for (NSUInteger i = 0; i < frameCount; i++) {
  258. @autoreleasepool {
  259. UIImage *frameImage = [image.images objectAtIndex:i];
  260. CGImageDestinationAddImage(destination, frameImage.CGImage, (__bridge CFDictionaryRef)frameProperties);
  261. }
  262. }
  263. NSAssert(CGImageDestinationFinalize(destination),@"Failed to create animated image.");
  264. CFRelease(destination);
  265. } else {
  266. fileURL = [[tmpDirURL URLByAppendingPathComponent:filename] URLByAppendingPathExtension:@"jpg"];
  267. NSData *data = [NSData dataWithData:UIImageJPEGRepresentation(image, 0.8)];
  268. [[NSFileManager defaultManager] createFileAtPath:[fileURL path] contents:data attributes:nil];
  269. }
  270. return fileURL;
  271. }
  272. @end