Feb 28

用UIActivityViewController搞定iOS企业应用(内部应用)的微信分享

Lrdcq , 2020/02/28 12:39 , 程序 , 閱讀(2966) , Via 本站原創
最近遇到一个内部企业应用由于业务发展需要,毕竟现在国内线下拓客逃不掉微信,因此需要这个内部应用也接入微信分享来辅助线下提效。接了一半,才注意到在年初加入规则,未上架应用一天只能分享100次。这不完犊子了嘛。但业务诉求很强烈,由于我们分享目标是用户端小程序地址,因此我们立刻意识到,分享小程序二维码的图片,即调用系统分享的图片分享,就可以解决这个问题。

用常见的例子,即分享一个这样的图片:
點擊在新視窗中瀏覽此圖片

用起来UIActivityViewController

UIActivityViewController就是那个系统默认分享弹窗,类似于安卓的默认intent界面,可以按输入信息和各个应用接收参数类型系统级串联起各个应用分享超足,因此非常方便。

UIActivityViewController以iOS13以前和之后分为两个节目版本,看起来分别是这样的:

點擊在新視窗中瀏覽此圖片點擊在新視窗中瀏覽此圖片

不过实际上主内容是一致的——就是两排分享目标按钮,上面类型是Share,下面类型是Action,除了UI并没有本质上的区别。另外iOS13前的拓展功能及少,还是往后看。

UIActivityViewController基本使用也很简单:
UIActivityViewController *activity = [[UIActivityViewController alloc] initWithActivityItems:@[source_uiimage] applicationActivities:nil];
//ActivityItems为要分享的目标对象,这里是一个UIImage,applicationActivities是应用可以单独附带的操作,目前为空
activity.excludedActivityTypes = @[UIActivityTypeAirDrop];
//系统自带的操作可以单独排除,比如排除掉AirDrop
            
UIPopoverPresentationController *popover = activity.popoverPresentationController;
if (popover) {
    popover.sourceView = sender;
    popover.permittedArrowDirections = UIPopoverArrowDirectionUp;
}
//以上是兼容ipad,无视即可
            
[self presentViewController:activity animated:YES completion:nil];
//最后打开即可

使用起来本身没有什么特别的,但是作为图片分享,我们总归希望能将图片展示出来。

图片preview

然而尝试后发现,只是分享一个UIImage,UIActivityViewController上居然没有preview。并且在多番尝试后,发现:

- 只有在分享一个NSURL对象的时候,iOS13及以上的分享出口会在顶部自动拉去url信息并且展示出图片preview。
- iOS13以下没有这样的功能,可以直接放弃。

当然我们可以考虑在系统的UIActivityViewController层上再贴一个view或者window来解决图片preview,更合理的方案还是看怎么让我们的图片数据也能展示出preview。因此我们不能直接传递UIImage或者NSURL了,需要自定义数据源。

支持展示出顶部信息的自定义数据源很简单,使用UIActivityItemProvider即可:
@interface ImageWrapper : UIActivityItemProvider

@property (nonatomic, strong) UIImage *img;

@end

@implementation ImageWrapper

+ (instancetype)activityItemSourceUrlWithImg:(UIImage *)img {
    ImageWrapper *wrapper = [[self alloc] initWithPlaceholderItem:@""];
    wrapper.img = img;
    return wrapper;
}

//这个回调是在分享页打开的时候使用,获取分享页preview用的分享目标
- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController {
    return self.img;
}

//这个用户选择了一个具体UIActivity动作后,递给人家的对象,按理说应该和上面一致,否则可能会炸
- (id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(NSString *)activityType {
    return self.img;
}

//部分UIActivity动作可能会调用这个方法获取浏览图片,无脑返即可
- (UIImage *)activityViewController:(UIActivityViewController *)activityViewController thumbnailImageForActivityType:(NSString *)activityType suggestedSize:(CGSize)size {
    return self.img;
}

//关键,见下
- (nullable LPLinkMetadata *)activityViewControllerLinkMetadata:(UIActivityViewController *)activityViewController  API_AVAILABLE(ios(13.0)){
    LPLinkMetadata *data = [LPLinkMetadata new];
    data.iconProvider = [[NSItemProvider alloc] initWithObject:self.img];
    data.title = @"浏览图片的描述信息";
    data.URL = [NSURL URLWithString:@"http://unknownurl.url/urlurlurl"];//浏览图片的地址
    return data;
}

@end

//使用
UIActivityViewController *activity = [[UIActivityViewController alloc] initWithActivityItems:@[[ImageWrapper activityItemSourceUrlWithImg:source_uiimage]] applicationActivities:nil];

效果如下:
點擊在新視窗中瀏覽此圖片

这段代码的关键是在activityViewControllerLinkMetadata回调中返回了一个LPLinkMetadata对象。LPLinkMetadata又是个啥,是苹果在iOS13推出的另一个功能https://developer.apple.com/documentation/linkpresentation/lplinkmetadata,这里更类似于借用了LPLinkMetadata对象来实现了如果分享url对url浏览的功能。

因此实现这个回调的含义就是,虽然我分享的不是url,但是我还是返回了一个url浏览信息。因此,填充上iconProvider即左边的图片浏览位,title即描述,URL会展示在subtitle的位置上来做地址浏览,有必要的话可以借用展示出来一些多余的信息。

不过这里有一个迷惑行为,UIActivityViewController在initWithActivityItems可是分享的一组item,但是其用LPLinkMetadata展示的只能展示第一个对象的信息,感觉不科学。

结构化preview

如上的问题,估计iOS13推出后苹果也发现了。比如向微信朋友圈分享,我可以在initWithActivityItems里携带一个图片与一段文字,微信可以将这些信息组合成一条朋友圈,但是在LPLinkMetadata的展示中,我们只能针对图片信息进行展示,感觉怪怪的。

因此,这个地方实际用LPLinkMetadata去抽象展示内容其实是不合适的,估计苹果也发现了这个问题,所以在iOS14又推出了一种新的抽象方式,即,这玩意儿描述的类似于一个datasource的数据源,本次分享操作的所有信息都是从这里获取,当然也包括分享的主item与preview信息了。
@interface ImageDataSource : NSObject <UIActivityItemsConfigurationReading>

@property (nonatomic, strong) NSItemProvider *img;
@property (nonatomic, strong) NSString *data_text;

@end

@implementation ImageDataSource

+ (instancetype)activitySourceUrlWithImg:(UIImage *)img message:(NSString *)data {
    ImageDataSource *wrapper = [[ImageDataSource alloc] init];
    wrapper.img = [[NSItemProvider alloc] initWithObject:img];
    wrapper.data_text = data;
    return wrapper;
}

//必要返回
- (NSArray<NSItemProvider *> *)itemProvidersForActivityItemsConfiguration {
    return @[_img];
}

//辅助Metadata信息
- (nullable id)activityItemsConfigurationMetadataForKey:(UIActivityItemsConfigurationMetadataKey)key {
    return _data_text;
}

//浏览图片
- (nullable NSItemProvider *)activityItemsConfigurationPreviewForItemAtIndex:(NSInteger)index intent:(UIActivityItemsConfigurationPreviewIntent)intent suggestedSize:(CGSize)suggestedSize {
    return _img;
}

@end

//使用
UIActivityViewController *activity = [[UIActivityViewController alloc] initWithActivityItemsConfiguration:[ImageDataSource activitySourceUrlWithImg:source_uiimage message:@"这是图片描述信息。"]];

效果如图:
點擊在新視窗中瀏覽此圖片

UIActivityItemsConfiguration这个抽象是一个非常结构化的抽象,目前网上可以找到的信息太少了。

- 整体结构应该是通过itemProvidersForActivityItemsConfiguration,即原本输入的数组返回主体信息,但是这里强制要求通过NSItemProvider给出了。说起来NSItemProvider这里其实是老瓶新装,毕竟能手动指定传输对象的typeIdentifier即苹果UTI格式(https://www.jianshu.com/p/d6fe1e7af9b6),类似于MIME,确实好用。
- 同时通过activityItemsConfigurationMetadataForKey可以提供各个字段的描述信息,目前可能会要的只有“title”和“messageBody”两个信息,不过目前还没找到title用在哪儿了。messageBody会展示在顶部占位的副标题位置,并且也会作为分享信息加入输出数组里。
- 通过activityItemsConfigurationPreviewForItemAtIndex可以返回左上角展位浏览图片。
- 目前顶部占位的标题会被这套框架返回固定的信息,即分享的内容类型罗列。比如这里是一段文本和一张图片“Plain Text and 1 Image”。看整个设计的话,其实预期是将一个结构化的数据按需求进行preview,并且能整体告诉用户到底分享了啥。不过顶部那个展位确实太寒碜了,所以效果不佳,不到迫不得已也没人用。

落地到微信

当然,除了默认的分享动作,还能进行自定义操作,自己继承于UIActivity干活就行了,不过一般来说也没有啥必要。

最后落地到微信,总的来说都是基于扫码的,但是当然也有两个落地方式-公众号or小程序。

- 落地到公众号相对来说简单,其实就是一个h5链接即可,当然如果携带参数最好找一个短链接服务承接一下。
- 落地到小程序就麻烦了,整体来说有两种生产方式:a. 长期小程序码。b. 临时码。
- 长期小程序码是有一定数量限制的,因此适用于固定分享目标,如落地到小程序首页,或者固定的订单列表,我的页等,简单来说就是不带参数的页面。当时起来也很简单,把码提前准备好就行了,每一个固定地址准备一张二维码图片就done了。
- 临时码更多用于带参数的二维码分享,需要一个后端服务去动态承接-调用微信接口生成二维码,整体都参考https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/qr-code.html即可。
logo