网络知识 娱乐 iOS shareExtension总结分享

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  _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(@"复制出错啦");
}

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