微信开发 调用长按识别二维码

2015-08-13 11:26:16 iplaycoder 阅读数 28720

这篇文章是上一篇的延伸与总结,做的一个微信游戏宣传页上要调用长按识别二维码的功能,做的过程中遇到了两个坑,后来在组里分享会上分享了入坑经历,然后再度发现一个坑。本文就是在分享会上发言总结而成。

坑一:iOS 版微信长按识别二维码无法正常识别的bug

以下实测在iOS 版(iPhone)微信2.2 中有此bug,安卓版微信暂时没有发现有此bug。“众所周知”,在一些使用在微信内置浏览器的页面上要调用其长按识别二维码的功能,需要将二维码图片单独切出,img 标签形式展现。长按识别二维码 的原理Jeff 不甚了解,但却发现其有一个实际可识别区域上移的bug:对于二维码区域,实际可识别区域是整体上移64px,64px 的偏移量与二维码大小本身无关

下面是一个重现bug 的Demo页面,请用iPhone 版微信扫描:


红色的色块区域是我单独为了定位区域而写的,不是在二维码本身(具体可以查看上面Demo 的源代码),用手指长按你可以看到实际可识别二维码区域为红色部分:即整体可识别区域上移了64px,而往下拉看到不同大小的二维码你会发现这个64px 的偏移量与二维码本身大小无关。友情提示:你可以用小拇指在边界区域长按以确认。


为什么是神秘的64px 偏移量?答案是:64px 正好是微信内置浏览器标题栏+系统标题栏的高度。可以猜测的是,微信客户端在识别二维码的时候忽略了微信标题栏+系统状态栏的高度。

后来进一步排查的时候,发现二维码大到一定程度就没有“识别上移”的诡异现象了,大概是二维码大小在400px 以上的时候就没有(当然,这个是在iPad 版微信上测出来的),具体可以查看下面这个Demo页面:


人在微信实习,所以后面经过与微信 iOS 开发人员的反馈沟通,确定是微信的 bug,也的确是这个64px 的问题;据他们的说法目前已经修复,但能否在下一个版本中加入么,就不得而知了。

解决方案的话有两个:

1)通过img增加padding 增大可接触面积;这个需要微调。

2)为二维码图片本身增加透明底部背景,这也是我们团队采用的方案。类似下图的样子,前端上用户是看不到的;但有个缺点就是如果用户保存二维码后则不是那么好看。

坑二:两(多)张两张二维码无法在同一屏幕视窗中共存

小标题说的“同一屏幕视窗”是指微信内置浏览器中在当前的手机屏幕上显示的可见范围,我们发现,当同一同一屏幕视窗中存在两个或以上的二维码的时候,微信客户端就会识别错误,无论你按哪个二维码长按识别,识别出来都是同一个目标。这个问题在iOS 版(iPhone)微信2.2及安卓版上均有之。

下面是一个演示Demo页面,请用目标手机(iPhone 或安卓机上)微信扫描访问,然后长按识别二维码:


该页面一共有六个二维码,两两分组为三组,每组的二维码的信息分别是指向qq.com、baidu.com 的url。为了保证每次只有一组二维码在当前屏幕可视范围,页面特意将每组二维码距离拉得很大。每次请保持只有一组二维码在你当前屏幕范围内,然后请依次 长按识别二维码。你会发现无论无论你按哪个二维码长按识别,识别出来都是同一个目标。

然后再尝试最后一组二维码,尝试通过滚动页面使得某个二维码不在你的屏幕可见范围,再执行长按识别二维码。

通过上面的Demo 页面,其实我们可以猜测微信中长按识别二维码的运行原理,就是你长按的时候相当于将当前手机屏幕截屏,识别截屏后的图片,这样一张图片有两个二维码图的时 候当然只会识别出一个。顺着这个思路也可以解释上述坑一,因为截屏的图片当然包括了系统状态栏那部分,也就不难理解为什么会有“识别上移”的诡异现象了。

解决方案的话就是不要将两个二维码共存在同一个页面中。

坑三:多次执行长按二维码的功能会导致内存泄露,手机会变卡

多次执行长按二维码的功能会导致手机(iPhone)变卡。之前我做测试的时候也发现,多次测试后居然右键都识别出来是二维码图片(即没有出现“识别二维码”的按钮)。


2017-11-10 13:50:00 weixin_30399055 阅读数 135

spa(单页应用,vue)中,使用history模式时,微信长按识别二维码在ios下失效的问题。

 

触发条件:

spa单页应用;

路由模式 history

从其他页面跳转到带有微信二维码识别的页面(不是直接打开该页面)

ios版本的微信(实测版本6.5.19)

 

结果:

二维码长按无法识别,刷新页面后恢复正常,安卓下正常。

 

解决方案:

1. 进入该页面的方式不使用路由跳转,而改为 <a href="xxx">目标二维码页面</a>的方式;

2. 在beforeCreated中重载该页面(例如增加&_r=1);

3. 改为hash模式恢复正常。

 

猜想:

微信的长按识别功能,也需要URL认证,但是ios版本的微信,应该是尚未实现根据popstate状态对微信的接口进行监听注册,导致长按识别的接口在该情况下无法识别单页应用修改的路由,从而调用接口失败,而刷新后接口会重新注册当前的URL(而不是通过pushstate改变的URL),因此导致该bug,后续有望修复。毕竟friendly URL 还是有些用处的,特别是分享等场景。

转载于:https://www.cnblogs.com/aleafo/p/7814258.html

2017-10-27 15:01:01 wozaixiaoximen 阅读数 9863

微信客户端发现用户长按<img>时,会截屏并启动二维码识别,二维码识别的不是<img>而是截屏。这样做的好处是不用下载图片,坏处是识别的图片更复杂了。

猜测:(Android)过程估计是,在WebView里注入JS给第三方页面中的所有图片绑定事件,长按触发JS回调,在回调中调用Java函数,这个Java函数做的工作大概是截屏、识别(在WebView里Java与JS互调就忽略了╮(╯_╰)╭毕竟我不会)

这里写图片描述

然后,就和页面无关了

2018-07-19 11:33:55 lumengabc 阅读数 15342

场景:微信小程序,使用webview控件。需求:点击图片后长按图片出现“识别二维码”

1、JS代码:


<script src="http://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
<script type="text/javascript">
$(function(){
		
	var returnData = false;
	$.ajax({
	  type	: "get",
	  url	: 'http://app.ka.com/m/config.php',
	  data	: [],
	  async	: false,
	  success: function(data,textStatus,jqXHR){
				returnData = data;
				//console.log(returnData);
			}
	});//end ajax

	var returnData = eval('(' + returnData + ')');
	console.log(returnData);

	var appId 		= returnData.appId;
	var timestamp 	= returnData.timestamp;
	var nonceStr 	= returnData.nonceStr;
	var signature 	= returnData.signature;

		  wx.config({
			debug: true, //调试阶段建议开启
			appId: appId,
			timestamp: timestamp,
			nonceStr: nonceStr,
			signature: signature,
			jsApiList: [
				   /*
					* 所有要调用的 API 都要加到这个列表中
					* 这里以图像接口为例
					*/
				  "chooseImage",
				  "previewImage",
				  "uploadImage",
				  "downloadImage",
				  "scanQRCode"
			]
		  });
				  
			wx.ready(function() {
			//alert(3);
				wx.checkJsApi({
					 jsApiList : ['scanQRCode','previewImage'],
					 success : function(res) {
		
					 }
				});
				
					$("img").click(function(){
						var url = "http://app.ka.com/"+$(this).attr("src");
						wx.previewImage({
							current: url, // 当前显示图片的http链接
							urls: [url] // 需要预览的图片http链接列表
						});
					});
		
			});
		
					  
			wx.error(function(res){
			
				// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
				console.log(res);
			});
		
		  
});
</script>

2.服务端代码(获取config配置信息):

<?php 
date_default_timezone_set("Asia/Shanghai");

$jssdk = new Jssdk();
$signPackage = $jssdk->getSignPackage();
echo json_encode($signPackage);exit;

class Jssdk
{
	private $_CI;

    private $appId;
    private $appSecret;

    public function __construct($appId='wx666666', $appSecret='ee32') {
        $this->appId = $appId;
        $this->appSecret = $appSecret;
    }

    public function getSignPackage() {
        $jsapiTicket = $this->getJsApiTicket();

        // 注意 URL 一定要动态获取,不能 hardcode.
        $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
        $url = "$protocol$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";

        $timestamp = time();
        $nonceStr = $this->createNonceStr();

        // 这里参数的顺序要按照 key 值 ASCII 码升序排序
        $string = "jsapi_ticket=$jsapiTicket&noncestr=$nonceStr&timestamp=$timestamp&url=$url";

        $signature = sha1($string);

        $signPackage = array(
            "appId"     => $this->appId,
            "nonceStr"  => $nonceStr,
            "timestamp" => $timestamp,
            "url"       => $url,
            "signature" => $signature,
            "rawString" => $string,
            'jsapiTicket' =>$jsapiTicket,
        );
        return $signPackage;
    }

    private function createNonceStr($length = 16) {
        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }

    private function getJsApiTicket() {
        // jsapi_ticket 应该全局存储与更新,以下代码以写入到文件中做示例
        $data = $this->get_php_file("jsapi_ticket");
        //echo $data['expire_time'].'------'.time();
        //print_r($data);exit;

        if (!isset($data['jsapi_ticket']) ||  (isset($data['expire_time'])  && $data['expire_time'] < time())) {
            //echo 1111;
            $accessToken = $this->getAccessToken();
            // 如果是企业号用以下 URL 获取 ticket
            // $url = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=$accessToken";
            $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=$accessToken";
            //echo $url;
            $res = json_decode($this->httpGet($url), true);

            $ticket = isset($res['ticket']) ? $res['ticket'] : false;
            if ($ticket) {
                $data['expire_time'] = time() + 7160;
                $data['token_value'] = $ticket;
                $this->set_php_file("jsapi_ticket", $data);
            }
        } else {
            //echo 22222;
            $ticket = $data['jsapi_ticket'];
        }

        return $ticket;
    }

    public function getAccessToken() {
        // access_token 应该全局存储与更新,以下代码以写入到文件中做示例
        $data = $this->get_php_file("access_token");
        if (!isset($data['access_token']) ||  (isset($data['expire_time']) && $data['expire_time'] < time())) {
            // 如果是企业号用以下URL获取access_token
            // $url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=$this->appId&corpsecret=$this->appSecret";
            $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$this->appId&secret=$this->appSecret";
            $res = json_decode($this->httpGet($url));
            //var_dump($res);exit;
            $access_token = $res->access_token;
            if ($access_token) {
                $data['expire_time'] = time() + 7160;
                $data['token_value'] = $access_token;
                $this->set_php_file("access_token", $data);
            }
        } else {
            $access_token = $data['access_token'];
        }
        return $access_token;
    }

    private function httpGet($url) {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_TIMEOUT, 500);
        // 为保证第三方服务器与微信服务器之间数据传输的安全性,所有微信接口采用https方式调用,必须使用下面2行代码打开ssl安全校验。
        // 如果在部署过程中代码在此处验证失败,请到 http://curl.haxx.se/ca/cacert.pem 下载新的证书判别文件。
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($curl, CURLOPT_URL, $url);

        $res = curl_exec($curl);
        curl_close($curl);

        return $res;
    }

    private function get_php_file($token_name) {
		if(file_exists($token_name)) return false;
        return json_decode(file_get_contents($token_name), true);
    }
    private function set_php_file($token_name, $data) {
        $fp = fopen($token_name, "w");
        fwrite($fp, "<?php exit();?>" . json_encode($data));
        fclose($fp);
    }

}
/* End of file Jssdk.php */

 

 

2017-12-18 19:41:40 lanqi_x 阅读数 12061

这篇文章就整理下移动端长按识别二维码的实现吧!实现方式可以分为三种

一、长按原生控件,直接获取控件中的图片数据(src或background)

二、长按原生控件,截图识别

三、长按web中的图片,app识别其中的二维码(js互调)


第一二种好像没多少可以说的,但还是按照顺序来吧!首先先说下使用的库,ios使用原生二维码识别库(好像是ios7之后才有的),然后说是WKWebView比UIWebView优化了很多 东西,也解决了内存泄漏的问题那么js交互的部分我们就用WKWebView吧(说到这里必须吐槽下android的webView内存泄漏问题,一个字坑)。android没原生的,我了解的比较大众的就zxing和zbar,经过测试发现在二维码占图片的比例较小识别时zbar的识别比zxing好一些,而且zxing使用截图的方案实现时当二维码放在屏幕的底部时识别不出来,所以这里就直接只贴zbar的代码吧!

然后呢因为是写的demo,代码是没优化过的,怎么方便怎么来,实际使用还是得根据自己的需要优化一下,个人觉得重要的是实现方案和思路。

一、长按原生控件,直接获取控件中的图片数据(src或background)

这里基本上等于在介绍,二维码识别的使用了。

(1)android

获取图片android就比较简单了。长按事件就不说了,图片通常会用ImageView,直接获取src就行了,特殊点的放background,那么就获取background就好了。直接贴代码吧!

//src
Bitmap mBitmap=((BitmapDrawable) imageView.getDrawable()).getBitmap();

//background
mBitmap=((BitmapDrawable) imageView.getBackground()).getBitmap();

下面就是关键zbar 识别图片中二维码的代码了

-----------------------------------------------
public String parseRQ(Bitmap bitmap) {
        String text = null;
        ImageScanner scanner=new ImageScanner();
        scanner.setConfig(0, Config.X_DENSITY,3);
        scanner.setConfig(0, Config.Y_DENSITY, 3);
        //设置扫描的图片
        Image barcode = new Image(bitmap.getWidth(), bitmap.getHeight(), "Y800");
        //设置扫描的图片的区域,因为我们不知道二维码在哪,所以直接设置整张图片
        barcode.setCrop(0, 0, bitmap.getWidth(), bitmap.getHeight());
        int[] data = new int[bitmap.getWidth() * bitmap.getHeight()];
        byte[] bitmapPixels =
                new byte[bitmap.getWidth() * bitmap.getHeight()];
        bitmap.getPixels(data, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());

        for (int i = 0; i < data.length; i++) {
            bitmapPixels[i] = (byte) data[i];
        }
        barcode.setData(bitmapPixels);
        //识别图片中的二维码,result是二维码的个数(这点比zxing好,zxing只获取从左上开始找到的第一个,不过也有可能是我调用的api不对也不一定)
        int result = scanner.scanImage(barcode);
        if (result != 0)
        {
            SymbolSet syms = scanner.getResults();
            for (Symbol sym : syms)
            {
                text=sym.getData().trim();
                //我们只获取第一个非空二维码,习惯性判空,没测过几个空字符串可不可以生成二维码
                if(!text.isEmpty())
                {
                    break;
                }
            }
        }

        return text;

    }

--------------------------------------------------

拿到解码后的数据,就可以根据需求取实现功能了。

(2)ios

ios获取UIImageView的图片更容易直接就是imageView.image就可以了,原生识别二维码的操作也简单,个人觉得设置长按事件比这两个加起来都麻烦点。所以这里主要就是设置长按事件的代码了。贴码。

//创建长按,imageLongClick即为长按响应的函数
UILongPressGestureRecognizer *longClick=[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(imageLongClick:)];
    //触摸点数,即多少手指点击
    longClick.numberOfTouchesRequired=1;
    //开启触发事件处理
    imageView.userInteractionEnabled=YES;
    //imageView添加长按事件
    [imageView addGestureRecognizer:longClick];

ios原生识别二维码,比zxing和zbar都简单多了,当然这没做优化策略的,android zbar那那个代码也一样

----------------------------------------------
-(void)imageLongClick:(UILongPressGestureRecognizer *)sender{
    //按下时
    if ([sender state]==UIGestureRecognizerStateBegan) {
        NSLog(@"image long click....");
        //创建识别器
        CIDetector *detector=[CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:nil];
        //image转为CGImage进行识别,结果为所有二维码结果的对象数组
        NSArray *results=[detector featuresInImage:[CIImage imageWithCGImage:[self snapshotView].CGImage]];
        if (results.count>0) {
            //这里只拿第一个
            CIQRCodeFeature *feature=[results firstObject];
            //feature.messageString即为解码后的字符串,这里直接打开浏览器
            [[UIApplication sharedApplication] openURL:[NSURL URLWithString:feature.messageString]];
        }else{
            NSLog(@"找不到二维码");
        }
    }
}
--------------------------------------------


在此直接获取控件的图片直接识别的就这样结束了,如果要获取相册的也一样,只需将从相册获取到的图片转为对应的Bitmap(ios UIImage),其他的都不变就可。

这里需注意的是背景色如果是透明色是无法识别出来的,所以如果二维码的来源是自己app的这种方式就很好了,如果是用户上传的建议用第二种方式,不可保证不会有哪个坑上传个透明背景的图片或上传个长图。


二、长按原生控件,截图识别

长按事件和识别二维码的代码是一样的,就不重复,即获取到截屏的图片后调用识别的方法就可以了,所以这里就只剩截屏功能的代码了,一样直接上代码

(1)android

-----------------------------------------------------
//其实这里直接传个View进来也是可以的,比如第三种的长按网页的就可以将WebView传就来就可以了
public Bitmap snapshotView(Window window) {
        if (window != null) {
            //找到当前页面的根布局
            View view = window.getDecorView().getRootView();
            //获取当前屏幕的大小
            int width = view.getWidth();
            int height = view.getHeight();

            //设置缓存
            view.setDrawingCacheEnabled(true);
            view.buildDrawingCache();
            /*1、从缓存中获取当前屏幕的图片,创建一个DrawingCache的拷贝,因为DrawingCache得到的位图在禁用后会被回收
             *2、这里的88是去掉无用的部分即你确定是不会有二维码的部分(当然不做任何操作也是可以的),这里直接写死是状态栏的高度,
             *实际真正使用不会这么写,而是是去获取状态栏的高度(这里懒就不写了),我记得如果直接是控件调用buildDrawingCache
             *是该控件当前显示在屏幕上的部分就不用减去状态栏的高度了
             */
            Bitmap temBitmap = Bitmap.createBitmap(view.getDrawingCache(), 0, 88, width, height - 88);
            //禁用DrawingCahce否则会影响性能 ,而且不禁止会导致每次截图到保存的是缓存的位图
            view.destroyDrawingCache();
            view.setDrawingCacheEnabled(false);

            return temBitmap;
        }
        return null;
    }
---------------------------------------------------

(2)ios

---------------------------------------------
//跟android一样这里的UIWindow也可以改成UIView,函数接受UIView的参数,外部就可以直接调用截取指定控件显示在屏幕的部分截图了
- (UIImage *)snapshotView {
    UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
    //这里只获取大小,所以bounds还是frame都是一样的
    CGRect rect = [keyWindow bounds];
    if(UIGraphicsBeginImageContextWithOptions != NULL)
    {
        //iphone4之后采用Retina屏幕调用这个(不知道有没有记反,也有其他的截图方式,只是我就记得这种)
        UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0);
    } else {
        UIGraphicsBeginImageContext(rect.size);
    }
    CGContextRef context = UIGraphicsGetCurrentContext();
    [keyWindow.layer renderInContext:context];
    UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return img;
}
-----------------------------------------------------------------------------------------------


三、长按web中的图片,app识别其中的二维码

这里也是截图实现,为什么不是拿原始图片识别,1是上面说的有可能是长图或背景透明,2是截图免下载,在速度体验上好点。我记得微信也是这样实现的,在哪提过我忘了。

既然涉及js,那我们就必须先来段js呀,js长按图片功能代码(本来是想用jquery的,但想想网页不一定是自己的,有可能是别人的静态网页,根本没导入jquery库,而直接 注入<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>的形式是会有跨域的问题的,所以不用jquery,直接写好了,代码量其实也差不多):

-----------------------------------------------------
//闭包,这里个人当成跟java的匿名对象差不多记忆
(function () {
        //获取所有图片标签
        var allImage = document.getElementsByTagName("img");
        var img;
        for (var i = 0; i < allImage.length; i++) {
            img = allImage[i];
            //添加触摸事件
            img.addEventListener('touchstart', function(event) {
                touch = event.touches[0];
                startevent = event;
            //保存触摸点的x,y轴
                startX = Number(touch.pageX);
                startY = Number(touch.pageY);
            //设置定时器,js没长按事件,就是使用定时器实现的,800毫秒后触发,img.src为图片地址,这里可以拿到后做保存图片发大图等功能
                timeout = setTimeout('longClick('+img.src+');', 800);
            });
            //移动事件
            img.addEventListener('touchmove', function(event) {
                touch = event.touches[0];
                scx = Math.abs(Number(touch.pageX) - startX);
                scy = Math.abs(Number(touch.pageY) - startY);
                //过滤掉移动事件,不这样做,当你手指放在这个图片往上或往下划的时候,800毫秒后也会触发长按事件,精确度可以自己调
                if (scx > 10 || scy > 10) { 
                   //取消定时器
                    clearTimeout(timeout);
                } else {
                    //相当android的拦截分发
                    event.preventDefault();
                }
            });
            //手指放开时取消定时器
            img.addEventListener('touchend', function(event) {
                clearTimeout(timeout);
            });
        }
    })();//立即执行
-------------------------------------------------------

app要做的就是两件事,1、将截图识别二维码对象注入js中。2、网页加载结束后,加载执行上面那个js函数代码即可。

(1)android

android相对简单点,创建注入对象

---------------------------------------------------
public class DemoJSBridge{
      @JavascriptInterface
       public void longClickImage(String imgSrc){
              //调用截图识别二维码代码
       }
}

//注入
wb.addJavascriptInterface(new DemoJSBridge(), "demoJSBridge");

---------------------------------------------------

这样就可以注入代码了,其他功能直接在类中加方法即可,而前端js调用则是

//对象是注入到window对象中的,所以是window.对象.方法
window.demoJSBridge.longClickImage(img.src);

所以上面的js函数中的'longClick('+img.src+');'改掉,然后页面加载完成后注入执行即可。即

------------------------------------------------------
        wb.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
               //加载js,而我们那个js函数是立即执行的,所以一加载就会自动执行,getQRJs()即为上面js函数的String格式
                wb.loadUrl("javascript:" + getQRJs());
            }
        });
------------------------------------------------------


(2)ios个人觉得麻烦点,但安全点

注入js对象

-------------------------------------------------------------------------
//构建script对象,配置页面加载完成后加载上面js函数的并执行,getJSString即为上面js函数的字符串,
//WKUserScriptInjectionTimeAtDocumentEnd页面加载结束后注入
WKUserScript *script=[[WKUserScript alloc] initWithSource:[self getJSString] injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:NO];
    //WKWebView配置对象
    WKWebViewConfiguration *config=[[WKWebViewConfiguration alloc] init];
    config.preferences=[WKPreferences new];
    //允许执行javaScript
    config.preferences.javaScriptEnabled=YES;
    //WKWebView自带长按事件,会拦截掉我们添加的事件,所以屏蔽掉
    NSMutableString *javascript = [NSMutableString string];
    //禁止webkitTouchCallout
    [javascript appendString:@"document.documentElement.style.webkitTouchCallout='none';"];
    [javascript appendString:@"document.documentElement.style.webkitUserSelect='none';"];//禁止选择
    WKUserScript *noneSelectScript = [[WKUserScript alloc] initWithSource:javascript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];

    [config.userContentController addUserScript:noneSelectScript];
    [config.userContentController addUserScript: script];
    //注入对象addScriptMessageHandler响应处理者self
    [config.userContentController addScriptMessageHandler:self name:@"demoJSBridge"];
    WKWebView *webView=[[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
---------------------------------------------------------------------------------

然后注意

1、script即我们上面代码中加载js函数的形式也可以用第二种方式,跟android的差不多,即

-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
    [self.webView evaluateJavaScript:[self getJSString] completionHandler:nil];
}
 

2、实现协议WKNavigationDelegate,WKUIDelegate,WKScriptMessageHandler,第一个页面加载进度等事件的回调。第二个js对话框的回调,WKWebView把js的对话框就是alert()这些给屏蔽了,我们需实现WKUIDelegate协议自己去弹窗。第三个js调用原生的方法。


3、WKWebView注入的对象跟android和UIWebView都不一样了,他放在了window.webkit.messageHandlers里,所以前端js调用时为

window.webkit.messageHandlers.对象名.postMessage(参数);
所以所以上面的js函数中的'longClick('+img.src+');'改掉,参数直接传js对象,如{functionName:"longClickImage",data:img.src},实现,如果为减少与android的差异性,android也可以改为只有postMessage(String msg)方法,然后根据functionName去执行对应的功能。js对象传到app后,ios会自动转为字典,android为json字符串,自己转成json就可以了。

个人理解是WKWebView取消了直接注入对象了,即没有将self注入到js中,而是于注入一个假对象,里面只有postMessage函数,当js调用这个函数时他对应的再去调用原生的回调。


4、响应js调用事件,即实现WKScriptMessageHandler协议

------------------------------------------------------------
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
     //message中的name即为注入的对象名,body为传过来的数据
    if ([message.name isEqualToString:@"demoJSBridge"]) {
        //js对象传过来后会自动转为字典
        NSDictionary *d=message.body;
        if ([@"longClickImage" isEqualToString:[d objectForKey:@"functionName"]]) {
            //调用截屏,识别二维码方法
        }
    }
}
-------------------------------------------------------------------

理论上js与原生的互调就这样可以了,但为了保险一点的话,原生接到js的调用以后再回调一下js比较好一点,因为有可能有些功能js需要app回传数据做下一步操作。相当于待人接物而言你叫我做一件事,做好了还是做不了,我得告知你一下,做个有交代有责任的人。


这篇好像有点长,写得不好的地方还多请见谅,也可在评论指导一下。