现在uni-app使用的越来越多,可是有些功用需求原生开发插件放入uni-app程序中来调用,本篇文章记录以下iOS原生截图插件的开发过程
1:首要创立插件工程
翻开 Xcode,创立一个新的Framework工程,然后点击 Next,然后选中工程名,在TARGETS->Build Settings中,将 Mach-O Type 设置为 Static Library
输入插件工程称号,然后点击Next,然后挑选存放路劲
2:导入插件工程
翻开工程目录,双击目录中的HBuilder-uniPlugin.xcodeproj 文件运转插件开发主工程, 在 Xcode 项目左边目录选中主工程名,然后点击右键挑选Add Files to “HBuilder-uniPlugin”导入插件工程
然后挑选您刚刚创立的插件工程途径中,选中插件工程文件,勾选 Create folder references 和 Add to targets 两项,然后点击Add
3:工程装备
在 Xcode 项目左边目录选中主工程名,在TARGETS->Build Phases->Dependencies中点击+,在弹窗中选中插件工程,然后点击Add,将插件工程增加到Dependencies中;然后在Link Binary With Libraries中点击+,同样在弹窗中选中插件工程,点击Add.
接下来需求在插件工程的Header Search Paths中增加开发插件所需的头文件引用,头文件存放在主工程的HBuilder-Hello/inc中,,在 Xcode 项目左边目录选中插件工程名,找到TARGETS->Build Settings->Header Search Paths双击右侧区域翻开增加窗口,然后将inc目录拖入会主动填充相对途径,然后将形式改成recursive
4:代码完结
本文需求是阅读网页的长截图,苹果没有单独的生成长截图的API,所以要采取代替计划,使用屡次截图并屏截计划,屡次测验中,屏截计划会有显现空白页面问题,所以才去了笨办法,使用createPDFWithConfiguration把网页先转化成PDF文件再生成image,然后与截图的图片再次屏截在一个页面,生成最终的图片; 计划执行中,百度阅读器搜索内容网页,截图后生成图片会有黑色遮挡,所以采取了新的截图计划
//保存地址 并传参uni-app
- (void)saveAsPDFWithTitle:(NSString *)title {
if (@available(iOS 13.4, *)) {
__weak __typeof(self)weakSelf = self;
__strong __typeof(weakSelf)strongSelf = weakSelf;
NSLog(@"%f",[self getWebView].wkWebView.frame.size.height);
//创立的截图页面
UIView *backgroundView = [[UIView alloc] init];
[self snapshotForWKWebView:[self getWebView].wkWebView CaptureCompletionHandler:^(UIImage *capturedImage) {
NSLog(@"%@",capturedImage);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDownloadsDirectory, NSUserDomainMask, YES);
NSString *downloadsDirectory = [paths firstObject];
if (downloadsDirectory) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *myPathDocs = [documentsDirectory stringByAppendingPathComponent:@"/Pandora/documents/output_image.jpeg"];
if([[NSFileManager defaultManager] fileExistsAtPath:myPathDocs]) {
[[NSFileManager defaultManager] removeItemAtPath:myPathDocs error:nil];
}
NSURL *outputURL = [NSURL fileURLWithPath:myPathDocs];
NSData *jpegData = UIImageJPEGRepresentation(capturedImage, 0.8);
NSData *jpegData2 = UIImagePNGRepresentation(capturedImage);
NSLog(@"%@==%@",jpegData,jpegData2);
[jpegData2 writeToURL:outputURL atomically:YES];
WebView *webView = [strongSelf getWebView];
NSString *url = webView.wkWebView.URL.absoluteString;
backgroundView.frame = CGRectMake(0, 0, capturedImage.size.width, capturedImage.size.height);
UIImageView *image = [[UIImageView alloc] init];
image.frame = backgroundView.frame;
image.image = capturedImage;
[backgroundView addSubview:image];
NSLog(@"%@==%@",outputURL.path,url);
WKPDFConfiguration *pdfConfiguration = [[WKPDFConfiguration alloc] init];
UIView *contentView = [self getWebView].wkWebView.scrollView.subviews.firstObject.subviews.firstObject;
/// 使用`webView.scrollView.frame` 能够捕获整个页面,而不仅仅是可见部分
pdfConfiguration.rect = CGRectMake(0, 0, contentView.frame.size.width, contentView.frame.size.height);
if (@available(iOS 14.0, *)) {
//__weak __typeof(self)weakSelf = self;
[webView.wkWebView createPDFWithConfiguration:pdfConfiguration completionHandler:^(NSData *pdfData, NSError *error) {
if (pdfData) {
//__strong __typeof(weakSelf)strongSelf = weakSelf;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDownloadsDirectory, NSUserDomainMask, YES);
NSString *downloadsDirectory = [paths firstObject];
if (downloadsDirectory) {
NSString *fileName = title ?: @"PDF";
NSString *savePath = [[downloadsDirectory stringByAppendingPathComponent:fileName] stringByAppendingPathExtension:@"pdf"];
NSError *writeError;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *myPathDocs = [documentsDirectory stringByAppendingPathComponent:@"/Pandora/documents/output_image.jpeg"];
if([[NSFileManager defaultManager] fileExistsAtPath:myPathDocs]) {
[[NSFileManager defaultManager] removeItemAtPath:myPathDocs error:nil];
}
NSURL *outputURL = [NSURL fileURLWithPath:myPathDocs];
UIImage *pdfImage = [strongSelf convertPDFDataToImage:pdfData];
//UIImage *pdfImage = [UIImage imageWithData:pdfData];
NSData *jpegData = UIImageJPEGRepresentation(pdfImage, 0.8);
[jpegData writeToURL:outputURL atomically:YES];
NSLog(@"%@",outputURL.path);
if (!writeError) {
NSLog(@"Successfully created and saved PDF at %@", savePath);
} else {
NSLog(@"Could not save pdf due to %@", writeError.localizedDescription);
}
WebView *webView = [strongSelf getWebView];
// 核算页面停留时间
NSDate *screenshotTimestamp = [NSDate date];
NSTimeInterval timeInterval = [screenshotTimestamp timeIntervalSinceDate:webView.pageLoadTimestamp];
NSLog(@"页面停留时间: %f 秒", timeInterval);
// 转化 timeInterval 为字符串
NSString *timeIntervalString = [NSString stringWithFormat:@"%f", timeInterval];
// 获取当时访问的 url
NSString *url = webView.wkWebView.URL.absoluteString;
NSLog(@"加载PDF前 W==%f H==%f PDF图片大小%f %f",backgroundView.frame.size.width,backgroundView.frame.size.height,pdfImage.size.width,pdfImage.size.height);
CGFloat pdfImgHeight = pdfImage.size.height;
CGFloat pdfImgWidth = pdfImage.size.width;
if (pdfImage.size.width > backgroundView.bounds.size.width) {
pdfImgHeight = backgroundView.bounds.size.width/pdfImage.size.width*pdfImage.size.height;
pdfImgWidth = backgroundView.bounds.size.width;
}
UIImageView *image = [[UIImageView alloc] init];
image.frame = CGRectMake(0, 0, pdfImgWidth, pdfImgHeight);;
image.image = [UIImage imageWithData:jpegData];
//[backgroundView addSubview:image];
if ([[NSString stringWithFormat:@"%@",self->_loadURLStr] containsString:@"https://www.baidu.com"]) {
} else {
[backgroundView addSubview:image];
}
NSLog(@"加载PDF后 W==%f H==%f 截图大小%f==%f",backgroundView.frame.size.width,backgroundView.frame.size.height,capturedImage.size.width,capturedImage.size.height);
//把view转化成image截图
UIImage *imageRet = [self getImageFromView:backgroundView];
NSData *jpegData3 = UIImagePNGRepresentation(imageRet);
[jpegData3 writeToURL:outputURL atomically:YES];
NSLog(@"%@==%@==",outputURL.path,jpegData3);
NSDictionary *params = @{@"detail":@{@"image":outputURL.path,
@"url": url,
@"time": timeIntervalString}};
NSLog(@"截图完结,把参数传递给uniapp %@", params);
self.loadGifImgView.hidden = YES;
//与uni-app交互办法,给uni-app传参
[strongSelf fireEvent:@"screenshotFinish" params:params domChanges:nil];
}
} else {
NSLog(@"%@==", error.localizedDescription);
}
}];
} else {
// Fallback on earlier versions
}
}
}];
} else {
// Fallback on earlier versions
}
}
- (void)snapshotForWKWebView:(WKWebView *)webView CaptureCompletionHandler:(void (^)(UIImage * _Nonnull))completionHandler {
if ([[NSString stringWithFormat:@"%@",self->_loadURLStr] containsString:@"https://www.baidu.com"]) {
//1.增加遮罩层
UIView *snapshotView = [webView snapshotViewAfterScreenUpdates:YES];
snapshotView.frame = webView.frame;
[webView.superview addSubview:snapshotView];
//2.初始化数组
self->_ImageAry = [NSMutableArray array];
//3.进行截图操作
CGPoint savedCurrentContentOffset = webView.scrollView.contentOffset;
webView.scrollView.contentOffset = CGPointZero;
NSLog(@"网页也高度=====%f===w:%f",webView.scrollView.contentSize.height,webView.scrollView.contentSize.width);
[self createSnapshotForWKWebView:webView offset:0.0 remainingOffset_y:webView.scrollView.contentSize.height comletionBlock:^(UIImage *snapshotImg) {
webView.scrollView.contentOffset = savedCurrentContentOffset;
[snapshotView removeFromSuperview];
UIImage *shotImg = [self captureScrollView:webView.scrollView];
completionHandler(snapshotImg);
}];
} else {
// 1.为了截图时对 frame 进行操作不会出现闪屏等现象,咱们需求盖一个“假”的 webView 到现在的方位上,并将真实的 webView “摘下来”。调用 snapshotViewAfterScreenUpdates 即可得到这样一个“假”的 webView
UIView *snapshotView = [webView snapshotViewAfterScreenUpdates:YES];
snapshotView.frame = webView.frame;
[webView.superview addSubview:snapshotView];
// 2. 保存真实的 webView 的偏移、方位等信息,以便截图完结之后“还原现场”
CGPoint currentOffset = webView.scrollView.contentOffset;
CGRect currentFrame = webView.frame;
UIView *currentSuperView = webView.superview;
NSUInteger currentIndex = [webView.superview.subviews indexOfObject:webView];
// 3. 用一个新的视图承载“真实的” webView,这个视图也是绘图所用到的上下文
UIView *containerView = [[UIView alloc] initWithFrame:webView.bounds];
[webView removeFromSuperview];
[containerView addSubview:webView];
// 4. 将 webView 按照实践内容高度和屏幕高度分成 page 页
CGSize totalSize = webView.scrollView.contentSize;
NSLog(@"%f",totalSize.height);
[webView evaluateJavaScript:@"document.body.scrollHeight" completionHandler:^(id _Nullable result,NSError * _Nullable error) {
CGFloat webViewHeight = [result doubleValue];
NSLog(@"%f",webViewHeight);
}];
NSInteger page = ceil(totalSize.height / containerView.bounds.size.height);
webView.scrollView.contentOffset = CGPointZero;
[webView evaluateJavaScript:@"window.scrollTo(0,0)" completionHandler:nil];
webView.frame = CGRectMake(0, 0, containerView.bounds.size.width, webView.scrollView.contentSize.height);
UIGraphicsBeginImageContextWithOptions(totalSize, YES, UIScreen.mainScreen.scale);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self drawContentPage:containerView webView:webView index:0 maxIndex:page completion:^{
UIImage *snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSLog(@"生成的图片%ld==%@",(long)page,snapshotImage);
//[webView removeFromSuperview];
completionHandler(snapshotImage);
[currentSuperView insertSubview:webView atIndex:currentIndex];
webView.frame = currentFrame;
webView.scrollView.contentOffset = currentOffset;
// 8. 调用 UIGraphicsGetImageFromCurrentImageContext 办法从当时上下文中获取到完好截图,将第 2 步中保存的信息重新赋予到 webView 上,“还原现场”
[snapshotView removeFromSuperview];
}];
});
}
}
- (void)drawContentPage:(UIView *)targetView webView:(WKWebView *)webView index:(NSInteger)index maxIndex:(NSInteger)maxIndex completion:(dispatch_block_t)completion
{
// 5. 得到每一页的实践方位,并将 webView 往上推到该方位
CGRect splitFrame = CGRectMake(0, index * CGRectGetHeight(targetView.bounds), targetView.bounds.size.width, targetView.frame.size.height);
CGRect myFrame = webView.frame;
myFrame.origin.y = -(index * targetView.frame.size.height);
webView.frame = myFrame;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 6. 调用 drawViewHierarchyInRect 将当时方位的 webView 烘托到上下文中
[targetView drawViewHierarchyInRect:splitFrame afterScreenUpdates:YES];
// 7. 假如还未抵达最终一页,则递归调用 drawViewHierarchyInRect 办法进行烘托;假如现已烘托完了全部页,则回调告诉截图完结
if (index < maxIndex) {
[self drawContentPage:targetView webView:webView index:index + 1 maxIndex:maxIndex completion:completion];
} else {
completion();
}
});
}
/** 新的截图办法*/
- (void)createSnapshotForWKWebView:(WKWebView *)webView offset:(float)offset_y remainingOffset_y:(float)reOffset_y comletionBlock:(void(^)(UIImage *snapshotImg))completeBlock
{
//判别scrollView是否现已翻滚究竟
if (reOffset_y>0) {
//设置
[webView.scrollView setContentOffset:CGPointMake(0, offset_y) animated:NO];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(),^{
//对页面进行截图操作
UIGraphicsBeginImageContextWithOptions(webView.frame.size, YES, [UIScreen mainScreen].scale);
[webView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//将截图增加进数组
[self->_ImageAry addObject:img];
//修正offsetY偏移量
CGFloat newOffset_y = offset_y + webView.scrollView.frame.size.height;
CGFloat newReOffset_y = reOffset_y - webView.frame.size.height;
[self createSnapshotForWKWebView:webView offset:newOffset_y remainingOffset_y:newReOffset_y comletionBlock:completeBlock];
});
}else {
//组成截图为最终截图
UIView * containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, webView.frame.size.width, webView.scrollView.contentSize.height)];
CGFloat originYOfImgView = 0;
for (int i = 0; i<self->_ImageAry.count; i++) {
UIImageView * imgView = [[UIImageView alloc] initWithFrame:CGRectMake(0, originYOfImgView, webView.frame.size.width, webView.frame.size.height)];
UIImage * img = self->_ImageAry[i];
imgView.image = img;
originYOfImgView += webView.frame.size.height;
[containerView addSubview:imgView];
}
//增加组成视图
[webView.superview addSubview:containerView];
//处理最终合并截图
UIGraphicsBeginImageContextWithOptions(containerView.frame.size, YES, [UIScreen mainScreen].scale);
[containerView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * img2 = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//移除组成视图
[containerView removeFromSuperview];
//回来截图
if (completeBlock) {
completeBlock(img2);
}
}
}
- (UIImage *)captureScrollView:(UIScrollView *)scrollView
{
UIImage* image = nil;
UIGraphicsBeginImageContextWithOptions(scrollView.contentSize, NO, 0.0);
{
CGFloat scale = 1;
CGPoint savedContentOffset = scrollView.contentOffset;
CGRect savedFrame = scrollView.frame;
scrollView.contentOffset = CGPointZero;
scrollView.frame = CGRectMake(0, 0, scrollView.contentSize.width * scale, scrollView.contentSize.height * scale + 30);
[scrollView.layer renderInContext:UIGraphicsGetCurrentContext()];
image = UIGraphicsGetImageFromCurrentImageContext();
scrollView.contentOffset = savedContentOffset;
scrollView.frame = savedFrame;
}
UIGraphicsEndImageContext();
if (image != nil) {
return image;
}
return nil;
}
-(UIImage *)getImageFromView:(UIView *)view{
CGSize s = view.bounds.size;
// 下面办法,第一个参数表示区域大小。第二个参数表示是否是非透明的。假如需求显现半透明效果,需求传 NO,否则传YES。第三个参数便是屏幕密度了
UIGraphicsBeginImageContextWithOptions(s, YES, [UIScreen mainScreen].scale);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage*image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
//pdf转化成image办法
- (UIImage *)convertPDFDataToImage:(NSData *)pdfData {
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)pdfData);
CGPDFDocumentRef pdfDocument = CGPDFDocumentCreateWithProvider(provider);
if (pdfDocument == NULL) {
NSLog(@"Could not create PDF document from data");
return nil;
}
CGPDFPageRef pdfPage = CGPDFDocumentGetPage(pdfDocument, 1);
if (pdfPage == NULL) {
NSLog(@"Could not get first page of PDF document");
return nil;
}
CGRect pdfPageRect = CGPDFPageGetBoxRect(pdfPage, kCGPDFMediaBox);
UIGraphicsBeginImageContext(pdfPageRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextFillRect(context, pdfPageRect);
CGContextTranslateCTM(context, 0.0, pdfPageRect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform(pdfPage, kCGPDFMediaBox, pdfPageRect, 0, YES);
CGContextConcatCTM(context, pdfTransform);
CGContextDrawPDFPage(context, pdfPage);
UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGPDFDocumentRelease(pdfDocument);
CGDataProviderRelease(provider);
return resultingImage;
}
5:装备插件信息
uni-app网站有这些装备信息代码 搬过来就行,选中工程中的HBuilder-uniPlugin-Info.plist文件右键->Open As->Source Code找到dcloud_uniplugins节点,copy下面的内容增加到dcloud_uniplugins节点下,对应填写
<dict>
<key>hooksClass</key>
<string>填写 hooksClass 类名 </string>
<key>plugins</key>
<array>
<dict>
<key>class</key>
<string>填写 module 或 component 的类名</string>
<key>name</key>
<string>填写露出给js端对应的 module 或 component 称号</string>
<key>type</key>
<string>填写 module 或 component</string>
</dict>
</array>
</dict>
在 uni-app 项目中调用 module 办法
6:导入uni-app资源
首要需求生成本地打包资源,在 HBuilderX 中选您的 uni-app 工程,右键->发现->原生App-本地打→生成本地打包App资源
项目编译完结后会在 HBuilderX 控制台输出资源存途径,点击途径会主动翻开资源地点文件 将应用资源导入到插件开发主工程的HBuilder-Hello/Pandora/apps/中,如下图所示,直接拖进去即可
然后翻开工程的 control.xml 文件,将 appid 改成 uni-app项目的 id,info.plist修正id
然后运转项目测验,如下图所示(能调到 module 的办法,并且能够获取 module 回来的数据,则说明功用正常)
7: 生成插件包
编译生成插件库文件(.framework 或 .a) 如下图所示,将编译工程挑选为插件项目(DCTestUniPlugin),运转设备挑选Generic iOS Device
然后点击Edit Scheme…在弹窗中,将Run->Info->Build Configuration切换到Release,然后点击Close关闭弹窗 然后在 Xcode 左边目录中选中插件工程名,查看TARGETS->Build Settings->Architectures,确保 Build Active Architecture Only->Release 为 No Valid Architectures 中至少包含 arm64 和 armv7(一般坚持工程默认装备即可),然后Command + B 编译运转工程 编译完结后,在插件工程 Products 下生成的库(DCTestUniPlugin.framework)即为插件所需求的依靠库文件,右键->Show in Finder,可翻开库地点文件夹
8:编写page.JSON文件,生成规范的插件包
package.json 为插件的装备文件,装备了插件id、格局、插件资源以及插件所需权限等等信息,uni-app官网有规范格局,直接拷贝过来,填入相应的内容
{
"name": "TestUniPlugin",
"id": "DCTestUniPlugin",
"version": "1.0.0",
"description": "uni示例插件",
"_dp_type": "nativeplugin",
"_dp_nativeplugin": {
"ios": {
"plugins": [{
"type": "module",
"name": "DCTestUniPlugin-TestModule",
"class": "TestModule"
}, {
"type": "component",
"name": "dc-testmap",
"class": "TestComponent"
}],
"frameworks": ["MapKit.framework"],
"integrateType": "framework",
"deploymentTarget": "9.0"
}
}
}
插件id为名新建一个文件夹,将修改好的 package.json 放进去,然后在文件夹中在新建一个 ios (小写)文件夹,将刚刚生成的依靠库(DCTestUniPlugin.framework)copy 到 ios 根目录,这样咱们的插件包就构建完结了
9:使用插件
HBuilderX 的 uni-app 项目创立中“nativeplugins”目录(如不存在则创立)将插件装备到uni-app项目下的“nativeplugins”目录
将原生插件装备到uni-app项目的“nativeplugins”下,还需求在manifest.json文件的“App原生插件装备”项下点击“挑选本地插件”,在列表中挑选需求打包收效的插件:
不管时真机测验与发布,都需求打包基座插件,打包流程如下图
截图插件完好流程到这儿就完毕了,关于截图功用便是iOS原生开发,这儿并不过多描绘,首要时把插件开发流程记录下来。