引子 在默认情况下,长按WKWebView
中的图片,会弹出image save sheet
:
关于这个image save sheet
,在官方文档中可以得到印证:
Safari Web Content Guide
-> Handling Events
-> One-Finger Events
章节中有这样一句话:However, if the user touches and holds an image, the image save sheet appears instead of an information bubble.
此处使用的是WKWebView
,但为什么要提到Safari
相关的内容?原因很简单,iOS
中的Safari
应用、SFSafariViewController
、WKWebView
,底层使用的是相同的东西。
在Handling Events
这一章中,有提及一些具体的事件、如何阻止事件的默认行为,以及支持的事件类型。
参考链接:Safari Web Content Guide 之 Handling Events
思路 为了实现长按图片识别二维码这样的功能,就必须将系统默认的image save sheet
屏蔽掉。
思路如下:
在网页DOM
加载完成后,注入JS
脚本
在JS
脚本中,找到所有的image
元素,并为它们添加touch
相关的事件
长按后阻止其默认行为,获取image
元素的src
,将结果传递给native
进行处理:下载图片,并识别,判断其是否包含二维码
如果包含二维码,则弹出相关的提示
注意事项 :
上面说的是touch
相关的事件,了解前端的开发者应该会知道mouse
相关的事件,这两者在手机端上的表现,还是有区别的:当使用mouse
相关的事件时,如果手指一直按在屏幕上时,会导致无法触发函数的执行,只有松开手指后才会触发,而touch
相关的事件,则不会出现这样的情况。
实现 实现预期的效果,会用到jQuery
、处理JS
回调的WKScriptMessageHandler
。
约定ScriptMessageHandler名称 这里约定其名称为webImgLongPressHandler
。
1 2 3 4 5 6 7 8 private let scriptMessageHandlerName: String = "webImgLongPressHandler" override func viewDidLoad () { super .viewDidLoad() let userContentController = WKUserContentController () userContentController.add(self , name: scriptMessageHandlerName) }
编写JS脚本 这里使用到了jQuery
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 $(document ).ready ( function ( ) { var imageElements = document .images ; for (var i = 0 ; i < imageElements.length ; i++) { var imageElement = imageElements[i]; var intervalID = 0 ; imageElement.ontouchstart = function (e ) { e.preventDefault (); intervalID = window .setInterval ( function ( ) { window .clearInterval (intervalID); window .webkit .messageHandlers .webImgLongPressHandler .postMessage (e.target .src ); }, 1000 ); }; imageElement.ontouchend = function (e ) { window .clearInterval (intervalID); }; imageElement.ontouchcancel = function (e ) { window .clearInterval (intervalID); } }; } );
将上面的脚本存放到名为image-element-long-press.js
的文件中。
将脚本注入到WKWebView
:
1 2 3 4 5 6 7 8 9 10 11 12 13 let jqPath = Bundle .main.path(forResource: "jquery-3.1.1.min" , ofType: "js" )let jsPath = Bundle .main.path(forResource: "image-element-long-press" , ofType: "js" )let jqContent = try! String .init (contentsOfFile: jqPath! )let jsContent = try! String .init (contentsOfFile: jsPath! )let injectionContent = String .init (format: "%@\n %@" , jqContent, jsContent)let userScript = WKUserScript .init (source: injectionContent, injectionTime: .atDocumentEnd, forMainFrameOnly: true ) userContentController.addUserScript(userScript)let config = WKWebViewConfiguration .init () config.userContentController = userContentControllerlet webView = WKWebView .init (frame: self .view.bounds, configuration: config)
处理识别 当长按图片时,就会发webImgLongPressHandler
消息,native
收到后进行处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public func userContentController(_ userContentController : WKUserContentController, didReceive message : WKScriptMessage) { if message.name == self.scriptMessageHandlerName { if let urlString = message.body as ? String { if let url = URL . init(string : urlString) { let sessionConfig = URLSessionConfiguration . default let session = URLSession . init(configuration: sessionConfig) let dataTask = session.dataTask(with : url , completionHandler : { (data , response , error ) in if data != nil { let img = UIImage . init(data: data!) let codes = self.QRcodesInImage(img ) if codes != nil && codes!.count > 0 { print("QRcodes: \(self.QRcodesInImage(img))" ) DispatchQueue . main.async { let sheet = UIAlertController . init(title: nil, message: nil, preferredStyle: .actionSheet) sheet.addAction(UIAlertAction.init (title : "识别二维码" , style : .default , handler : { (action ) in var urls = [URL] () for code in codes! { if let url = URL . init(string : code) { if UIApplication . shared.canOpenURL(url ) { urls.append(url) } } } let urlSheet = UIAlertController . init(title: nil, message: nil, preferredStyle: .actionSheet) for url in urls { let title = String . init(format: "打开%@" , url.absoluteString) urlSheet.addAction(UIAlertAction.init (title : title , style : .default , handler : { (action ) in UIApplication . shared.open (url, options: [:] , completionHandler: nil) })) } urlSheet.addAction(UIAlertAction.init (title : "取消" , style : .cancel , handler : { (action ) in })) self.present(urlSheet, animated: true , completion: nil) })) sheet.addAction(UIAlertAction.init (title : "取消" , style : .cancel , handler : { (action ) in })) self.present(sheet, animated: true , completion: nil) } } } }) dataTask.resume() } } } }
此处使用ZBarSDK
进行二维码的识别(使用系统提供的API
时,对于一张图片上有多个二维码的情况,虽然CIDetector
的featuresInImage
方法返回的是数组,但在测试时,调整了参数,返回的数组一直只包含一个元素):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 private func QRcodesInImage (_ image : UIImage ?) -> Array <String >? { if image == nil { return nil } let scanner = ZBarImageScanner () let barImg = ZBarImage .init (cgImage: image! .cgImage) let count = scanner.scanImage(barImg) if count == 0 { return nil } var codes = [String ]() let symbolSet = scanner.results var symbol: OpaquePointer ? = nil for i in 0 ..< symbolSet! .count { if i == 0 { symbol = zbar_symbol_set_first_symbol(symbolSet! .zbarSymbolSet) } else if symbol != nil { symbol = zbar_symbol_next(symbol) } if symbol != nil { let data = zbar_symbol_get_data(symbol) if let code = String .init (utf8String: data! ) { codes.append(code) } } } return codes.count > 0 ? codes : nil }
-webkit-touch-callout 在阻止默认行为时,上面的示例中使用的是preventDefault()
函数。也有另外一个选择,就是使用-webkit-touch-callout
。
参见苹果官方文档:
Safari CSS Reference
-> Supported CSS Properties
-> User Interface
链接:Safari CSS Reference: -webkit-touch-callout
其它参考:
语法可参考:
也就是:
1 document.getElementById ("id" ).style .property="值"
backgroundColor
与background-color
是对应的,类似地,应是webkitTouchCallout
与-webkit-touch-callout
相对应。
使用时,将其设置为none
:
1 imageElement.style.webkitTouchCallout = "none"
需要注意的是,preventDefault()
与-webkit-touch-callout
,在效果上是有区别的。
二者都可以阻止默认的save image sheet
。
不同点如下:
个人觉得,在这种需求下,长按后界面中出现复制
等Menu
是不太好看的,因此建议使用preventDefault()
。
附上代码链接:https://github.com/Daniate/WKWebViewQRcode