现在uni-app使用的越来越多,可是有些功用需求原生开发插件放入uni-app程序中来调用,本篇文章记录以下iOS原生截图插件的开发过程

1:首要创立插件工程

翻开 Xcode,创立一个新的Framework工程,然后点击 Next,然后选中工程名,在TARGETS->Build Settings中,将 Mach-O Type 设置为 Static Library

uni-app原生插件(1)--截图
输入插件工程称号,然后点击Next,然后挑选存放路劲
uni-app原生插件(1)--截图

2:导入插件工程

翻开工程目录,双击目录中的HBuilder-uniPlugin.xcodeproj 文件运转插件开发主工程, 在 Xcode 项目左边目录选中主工程名,然后点击右键挑选Add Files to “HBuilder-uniPlugin”导入插件工程

uni-app原生插件(1)--截图

然后挑选您刚刚创立的插件工程途径中,选中插件工程文件,勾选 Create folder references 和 Add to targets 两项,然后点击Add

uni-app原生插件(1)--截图

3:工程装备

在 Xcode 项目左边目录选中主工程名,在TARGETS->Build Phases->Dependencies中点击+,在弹窗中选中插件工程,然后点击Add,将插件工程增加到Dependencies中;然后在Link Binary With Libraries中点击+,同样在弹窗中选中插件工程,点击Add.

uni-app原生插件(1)--截图

接下来需求在插件工程的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资源

uni-app原生插件(1)--截图

项目编译完结后会在 HBuilderX 控制台输出资源存途径,点击途径会主动翻开资源地点文件 将应用资源导入到插件开发主工程的HBuilder-Hello/Pandora/apps/中,如下图所示,直接拖进去即可

uni-app原生插件(1)--截图

然后翻开工程的 control.xml 文件,将 appid 改成 uni-app项目的 id,info.plist修正id

uni-app原生插件(1)--截图

然后运转项目测验,如下图所示(能调到 module 的办法,并且能够获取 module 回来的数据,则说明功用正常)

uni-app原生插件(1)--截图

7: 生成插件包

编译生成插件库文件(.framework 或 .a) 如下图所示,将编译工程挑选为插件项目(DCTestUniPlugin),运转设备挑选Generic iOS Device

uni-app原生插件(1)--截图

然后点击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,可翻开库地点文件夹

uni-app原生插件(1)--截图

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原生插件(1)--截图

将原生插件装备到uni-app项目的“nativeplugins”下,还需求在manifest.json文件的“App原生插件装备”项下点击“挑选本地插件”,在列表中挑选需求打包收效的插件:

uni-app原生插件(1)--截图

不管时真机测验与发布,都需求打包基座插件,打包流程如下图

uni-app原生插件(1)--截图
uni-app原生插件(1)--截图

截图插件完好流程到这儿就完毕了,关于截图功用便是iOS原生开发,这儿并不过多描绘,首要时把插件开发流程记录下来。