iOS shareExtension总结

非常感谢大家利用自己宝贵的时间来阅读我的文章 ,好久没有写东西了,这几天给项目添加苹果iOS8之后的shareExtension功能,踩了些坑,做下总结,希望后面的朋友们做这个功能的时候可以在这里一站式解决😄。希望这篇文章能给你的开发过程带来一些帮助。喜欢的可以关注一下我的简书我的博客
关于shareExtension的基本功能和实现网上有很多资料,作者不想多说并向你扔了个传送门iOS Share Extension开发

下面主要说一下再整个过程中个人觉得比较重要的几个地方

1、NSExtensionActivationRule配置

NSExtensionActivationSupportsAttachmentsWithMaxCount(附件最多限制)NSExtensionActivationSupportsAttachmentsWithMinCount(附件最少限制)NSExtensionActivationSupportsImageWithMaxCount(图片最多限制)NSExtensionActivationSupportsMovieWithMaxCount(视频最多限制)NSExtensionActivationSupportsWebPageWithMaxCount(Web页面最多限制)NSExtensionActivationSupportsWebURLWithMaxCount(Web链接最多限制)NSExtensionActivationSupportsFileWithMaxCount(文件最多限制)NSExtensionActivationSupportsText(是否支持文本类型)

这些属性根据自己的需求设置好数量就行。说一下可能遇到的问题
1.1、NSExtensionActivationSupportsText,主要用于备忘录之类文本分享,网上资料有的说设置bool类型,有的说设置string类型,值为YES,我这边都不好使,最后设置为值为1的number类型可以了。如果跟我遇到同样问题的可以试下
1.2、在App Store分享APP时,有两个NSExtensionItem,UTI分别为public.url和public.png,loadItemForTypeIdentifier获取item类型为NSURL和UIImage,截图分享的item类型也为UIImage

2、获取分享数据搭建UI

在自定义VC的viewDidLoad异步获取分享数据,获取完毕刷新UI

[MBProgressHUD showHUDAddedTo:self.view animated:YES];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{//异步获取分享内容
        dispatch_group_t group = dispatch_group_create();
        [self.extensionContext.inputItems enumerateObjectsUsingBlock:^(NSExtensionItem *  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            [obj.attachments enumerateObjectsUsingBlock:^(NSItemProvider *  _Nonnull itemProvider, NSUInteger idx, BOOL * _Nonnull stop) {
                dispatch_group_enter(group);
                NSString *utiStr = @"要分享文件的UTI";
                if ([itemProvider hasItemConformingToTypeIdentifier:utiStr])
                {
                    [itemProvider loadItemForTypeIdentifier:“utiStr” options:nil completionHandler:^(id<NSSecureCoding>  _Nullable item, NSError * _Null_unspecified error) {//在这里保存获取到的分享数据
                        if ([(NSObject *)item isKindOfClass:[NSURL class]]){
                        } else if ([(NSObject *)item isKindOfClass:[UIImage class]]) {//截图||APP图片       
                       }else if ([(NSObject *)item isKindOfClass:[NSString class]]) {//文本 
                        }
                        dispatch_group_leave(group);
                    }];
                }
            }];
        }];
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            //构建UI
            [self configUI];
        });
    });

3、内存限制

这块是这个功能踩坑最多的地方,因为在widget中内存限制为120M,图片、视频、文件,随随便便都超了,这里说一下几个需要注意的地方

3.1图片处理

这里主要是多图时可能会出现内存过大的情况,我这边的处理是直接把图片copy到共享目录,然后跳转主App里发送原图。也可以直接将图片压缩后转成base64str数组然后写入共享目录,不过压缩比例不好控制,压缩后的总大小,小图不能压缩等都要考虑进去,下面把几个用到的方法和处理时机贴一下

3.1.1、图片压缩方法

- (UIImage *)resizeScaleImage:(CGFloat)scale {
    
    CGSize imgSize = self.size;
    CGSize targetSize = CGSizeMake(imgSize.width * scale, imgSize.height * scale);
    NSData *imageData = UIImageJPEGRepresentation(self, 1.0);
    CFDataRef data = (__bridge CFDataRef)imageData;
    
    CFStringRef optionKeys[1];
    CFTypeRef optionValues[4];
    optionKeys[0] = kCGImageSourceShouldCache;
    optionValues[0] = (CFTypeRef)kCFBooleanFalse;
    CFDictionaryRef sourceOption = CFDictionaryCreate(kCFAllocatorDefault, (const void **)optionKeys, (const void **)optionValues, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CGImageSourceRef imageSource = CGImageSourceCreateWithData(data, sourceOption);
    CFRelease(sourceOption);
    if (!imageSource) {
        NSLog(@"imageSource is Null!");
        return nil;
    }
    //获取原图片属性
    int imageSize = (int)MAX(targetSize.height, targetSize.width);
    CFStringRef keys[5];
    CFTypeRef values[5];
    //创建缩略图等比缩放大小,会根据长宽值比较大的作为imageSize进行缩放
    keys[0] = kCGImageSourceThumbnailMaxPixelSize;
    CFNumberRef thumbnailSize = CFNumberCreate(NULL, kCFNumberIntType, &imageSize);
    values[0] = (CFTypeRef)thumbnailSize;
    keys[1] = kCGImageSourceCreateThumbnailFromImageAlways;
    values[1] = (CFTypeRef)kCFBooleanTrue;
    keys[2] = kCGImageSourceCreateThumbnailWithTransform;
    values[2] = (CFTypeRef)kCFBooleanTrue;
    keys[3] = kCGImageSourceCreateThumbnailFromImageIfAbsent;
    values[3] = (CFTypeRef)kCFBooleanTrue;
    keys[4] = kCGImageSourceShouldCacheImmediately;
    values[4] = (CFTypeRef)kCFBooleanTrue;
    
    CFDictionaryRef options = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys, (const void **)values, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CGImageRef thumbnailImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options);
    UIImage *resultImg = [UIImage imageWithCGImage:thumbnailImage];
    
    CFRelease(thumbnailSize);
    CFRelease(options);
    CFRelease(imageSource);
    CFRelease(thumbnailImage);
    
    return resultImg;
}

3.1.2、图片base64str互转

//图片转字符串
-(NSString *)imageToBase64Str
{
    NSData *data = UIImageJPEGRepresentation(self, 1.0f);
    NSString *encodedImageStr = [data base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
    return encodedImageStr;
}
//字符串转图片
+ (UIImage *)base64StrToUIImage:(NSString *)encodedImageStr
{
    NSData *_decodedImageData = [[NSData alloc] initWithBase64Encoding:encodedImageStr];
    UIImage *_decodedImage = [UIImage imageWithData:_decodedImageData];
    return _decodedImage;
}

3.1.3、获取主要信息loadItemForTypeIdentifier方法里保存图片地址,用于UI展示及后续写入共享目录

 if ([itemProvider hasItemConformingToTypeIdentifier:@"public.image"]||[itemProvider hasItemConformingToTypeIdentifier:@"public.png"]) {
  [weakSelf.images addObject:((NSURL *)item).absoluteString];
} 

3.1.4、确认分享时把图片批量copy到共享目录,也可以选择压缩后转base64str数组写入文件

NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.58.mismobile"];
NSMutableArray *imageStringData = [NSMutableArray array];
  if (self.images.count > 0) {
    [self.images enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
      @autoreleasepool {
        NSError *copyError;
        NSURL *fileURL = [groupURL URLByAppendingPathComponent:[((NSString *)obj).lastPathComponent stringByRemovingPercentEncoding]];
        if ([[NSFileManager defaultManager] fileExistsAtPath:fileURL.absoluteString]) {
          [[NSFileManager defaultManager] removeItemAtPath:fileURL.absoluteString error:nil];
        }
        NSRange fromRange = [((NSString *)obj) rangeOfString:@"/var"];//文件路径需要移除/var之前路径
        NSString *fromPath = [((NSString *)obj) substringFromIndex:fromRange.location].stringByRemovingPercentEncoding;
        NSRange toRange = [fileURL.absoluteString rangeOfString:@"/var"];//文件路径需要移除/var之前路径
        NSString *toPath = [fileURL.absoluteString substringFromIndex:toRange.location].stringByRemovingPercentEncoding;
        [[NSFileManager defaultManager] copyItemAtPath:fromPath toPath:toPath error:&copyError];
        [imageStringData addObject:fileURL.absoluteString];
      }
    }];
  }
if (self.shareImage) {//截图先写入文件,再存储地址
  NSString *string = [NSString stringWithFormat:@"%f",[NSDate date].timeIntervalSince1970];
  NSURL *fileURL = [groupURL URLByAppendingPathComponent:string];
  NSData *imageData = UIImagePNGRepresentation(self.shareImage);
  [imageData writeToURL:fileURL atomically:YES];
  [imageStringData addObject:fileURL.absoluteString];
}
NSURL *imageInfoURL = [groupURL URLByAppendingPathComponent:[@"图片信息" stringByRemovingPercentEncoding]];
NSData *urlData = [NSJSONSerialization dataWithJSONObject:imageStringData options:NSJSONWritingPrettyPrinted error:nil];
[urlData writeToURL: imageInfoURL atomically:YES];

3.1.5、呼起主APP后先从获取图片地址数组,在通过图片地址取分享数据

// 把目标路径文件中的内容转化成NSData类型,加载(存储)到内存中
NSURL *dataURL = [[NSURL alloc] initWithString:imageStr];
NSData *data = [NSData dataWithContentsOfURL:(NSURL *)dataURL];
id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
NSArray *imageStringArray = (NSArray *)jsonObject;
NSMutableArray *images = [NSMutableArray arrayWithCapacity:imageStringArray.count];
[imageStringArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:(NSString *)obj]];
  [images addObject:[UIImage imageWithData:imageData]];
}];
if (images.count > 0) {
  //拿到image数据做后续处理         
}

3.2视频处理&文件处理

一方面是UI展示所需要的内容,如视频封面图、时长、大小,文件的大小、类型
这里说一下取文件大小时不能取NSData得length方式,大文件会导致内存溢出,应该使用NSFileManager获取,这里要注意下通过loadItemForTypeIdentifier取得的路径要先stringByRemovingPercentEncoding解码再截取/var之后的路径,比如你取得文件路径为file%3A%2F%2F%2Fprivate%2Fvar%2Fmobile%2FLibrary%2FMobile%20Documents%2Fcom%7Eapple%7ECloudDocs%2F%E6%9C%AA%E5%91%BD%E5%90%8D%E6%96%87%E4%BB%B6%E5%A4%B9%2Faaa.pdf
那你要用/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/未命名文件夹/aaa.pdf去取文件大小,下面把主要用的方法贴一下

3.2.1、文件大小以及格式化

//单个文件的大小
+ (NSString *) fileSizeAtPath:(NSString*) filePath{

    NSFileManager *manager = [NSFileManager defaultManager];
    if ([manager fileExistsAtPath:filePath]){
        return [self sizeString:[[manager attributesOfItemAtPath:filePath error:nil] fileSize]];
        }
    return @"";
}

+ (NSString *)sizeString:(long long)size {
    CGFloat k = 1024.0;
    CGFloat fileSize = size;
    if (size < k) { // B
        fileSize = size;
    } else if (size < k * k) { // KB
        fileSize = size / k;
    } else {
        fileSize = size / (k * k);
    }
    
    if (size < k) { // B
        return [NSString stringWithFormat:@"%.2fB", fileSize];
    } else if (size < k * k) { // KB
        return [NSString stringWithFormat:@"%.2fKB", fileSize];
    } else { // M
        return [NSString stringWithFormat:@"%.2fM", fileSize];
    }
}

3.2.2、获取视频时长(这里要用全路径)

//获取视频时长
+ (NSString *)getdurationTimeFromVideoPath:(NSURL*)fileURL {
    AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:fileURL options:nil];
    CMTime  durationTime = [asset duration];
    NSUInteger duration = ceil(durationTime.value/durationTime.timescale);
    return [self formatTime:duration];
}

3.2.3、获取视频封面图(这里也要用全路径)

//获取视频一桢截图
+ (UIImage *)getScreenShotImageFromVideoPath:(NSURL*)fileURL
{
    UIImage *shotImage;
    AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:fileURL options:nil];
    AVAssetImageGenerator *gen = [[AVAssetImageGenerator alloc] initWithAsset:asset];
    gen.appliesPreferredTrackTransform = YES;
    
    CMTime time = CMTimeMakeWithSeconds(0.0, 600);
    NSError *error = nil;
    CMTime actualTime;
    CGImageRef image = [gen copyCGImageAtTime:time actualTime:&actualTime error:&error];
    shotImage = [[UIImage alloc] initWithCGImage:image];
    CGImageRelease(image);
    return shotImage;
}

3.2.4、获取文件类型对应切图,把图片素材替换为你们UI给的切图

+ (UIImage *)fileIconForName:(NSString *)name {
    NSArray *videoTypeArray = @[@"mov", @"mp4", @"3gp", @"m4v"];
    NSArray *audioTypeArray = @[@"aac", @"caf", @"mp3", @"m4a", @"wav", @"m4r"];
    NSArray *pictureTypeArray = @[@"jpg", @"jpeg", @"gif", @"png", @"ico"];
    NSArray *wordTypeArray = @[@"doc", @"docx", @"txt"];
    NSString *suffix = name.pathExtension.lowercaseString;
    UIImage *image;
    if ([wordTypeArray containsObject:suffix]) {
        image = [UIImage imageNamed:@""];
    } else if ([suffix isEqualToString:@"xls"] || [suffix isEqualToString:@"xlsx"]) {
        image = [UIImage imageNamed:@""];
    } else if ([suffix isEqualToString:@"ppt"] || [suffix isEqualToString:@"pptx"]) {
        image = [UIImage imageNamed:@""];
    } else if ([suffix isEqualToString:@"pdf"]) {
        image = [UIImage imageNamed:@""];
    }else if ([suffix isEqualToString:@"zip"]) {
        image = [UIImage imageNamed:@""];
    } else if ([suffix isEqualToString:@"rar"]) {
        image = [UIImage imageNamed:@""];
    } else if ([videoTypeArray containsObject:suffix]) {
        image = [UIImage imageNamed:@""];
    } else if ([audioTypeArray containsObject:suffix]) {
        image = [UIImage imageNamed:@""];
    } else if ([pictureTypeArray containsObject:suffix]) {
        image = [UIImage imageNamed:@""];
    } else {
        image = [UIImage imageNamed:@""];
    }
    return image;
}

3.3大文件

因为需要把分享的文件存储到group共享目录里去,当文件超过120M的时候,就不能使用writeToURL了,要不会内存溢出,这里使用NSFileManager的copyItemAtPath,在使用这个函数的时候两个路径都要使用解码后再截取/var之后的路径

NSError *copyError;
NSRange toRange = [fileURL.absoluteString rangeOfString:@"/var"];//文件路径需要移除/var之前路径
NSString *toPath = [fileURL.absoluteString substringFromIndex:toRange.location].stringByRemovingPercentEncoding;
//这里我self.filePath保存时就做了处理了,所以只需处理toPath
[[NSFileManager defaultManager] copyItemAtPath:self.filePath toPath:toPath error:&copyError];
if (copyError != nil) {
  NSLog(@"复制出错啦");
}

目前能想到的就这么多啦,如果有什么疑问或者发现什么不足,欢迎评论指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容