非常感谢大家利用自己宝贵的时间来阅读我的文章 ,好久没有写东西了,这几天给项目添加苹果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:©Error];
[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:©Error];
if (copyError != nil) {
NSLog(@"复制出错啦");
}
目前能想到的就这么多啦,如果有什么疑问或者发现什么不足,欢迎评论指正。