WebView.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. //
  2. // WebView.swift
  3. // RainbowPlanet
  4. //
  5. // Created by 南鑫林 on 2018/7/13.
  6. // Copyright © 2018年 南鑫林. All rights reserved.
  7. //
  8. import UIKit
  9. import WebKit
  10. class WebView: UIView {
  11. /// 事件
  12. fileprivate var target: AnyObject?
  13. /// 创建webveiew
  14. var webView = WKWebView()
  15. /// 进度条
  16. var progressView = UIProgressView()
  17. /// 创建一个webiview的配置项
  18. fileprivate let configuretion = WKWebViewConfiguration()
  19. //执行JS 需要实现代理方法
  20. fileprivate var POSTJavaScript = String()
  21. //是否是第一次加载
  22. fileprivate var needLoadJSPOST:Bool?
  23. /// WebView配置项
  24. var webConfig : WKWebViewConfig?
  25. //保存请求链接
  26. fileprivate var snapShotsArray:Array<Any>?
  27. //设置代理
  28. weak var delegate : WKWebViewDelegate?
  29. override public init(frame: CGRect) {
  30. super.init(frame: frame)
  31. }
  32. required public init?(coder aDecoder: NSCoder) {
  33. super.init(coder: aDecoder)
  34. }
  35. open override func layoutSubviews() {
  36. super.layoutSubviews()
  37. webView.frame = CGRect(x: 0, y: 0, width: self.width, height: self.height)
  38. }
  39. fileprivate func setupUI(webConfig:WKWebViewConfig) {
  40. // Webview的偏好设置
  41. configuretion.preferences = WKPreferences()
  42. configuretion.preferences.minimumFontSize = webConfig.minFontSize
  43. configuretion.preferences.javaScriptEnabled = webConfig.isjavaScriptEnabled
  44. configuretion.preferences.javaScriptCanOpenWindowsAutomatically = webConfig.isAutomaticallyJavaScript
  45. configuretion.userContentController = WKUserContentController()
  46. _ = webConfig.scriptMessageHandlerArray.map{configuretion.userContentController.add(self as WKScriptMessageHandler, name: $0)}
  47. webView = WKWebView(frame:frame,configuration: configuretion)
  48. //开启手势交互
  49. webView.allowsBackForwardNavigationGestures = webConfig.isAllowsBackForwardGestures
  50. //滚动条
  51. webView.scrollView.showsVerticalScrollIndicator = webConfig.isShowScrollIndicator
  52. webView.scrollView.showsHorizontalScrollIndicator = webConfig.isShowScrollIndicator
  53. // 监听支持KVO的属性
  54. webView.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)
  55. //内容自适应
  56. webView.sizeToFit()
  57. self.addSubview(webView)
  58. progressView = UIProgressView(progressViewStyle: .default)
  59. progressView.frame = CGRect(x: CGFloat(0.0), y: webView.y, width: CGFloat(webView.width), height: CGFloat(webConfig.progressHeight))
  60. progressView.trackTintColor = webConfig.progressTrackTintColor
  61. progressView.progressTintColor = webConfig.progressTintColor
  62. webView.addSubview(progressView)
  63. progressView.isHidden = webConfig.isProgressHidden
  64. webView.navigationDelegate = self as WKNavigationDelegate
  65. webView.uiDelegate = self as WKUIDelegate
  66. }
  67. /// 加载webView
  68. func webloadType(_ target:AnyObject,_ loadType:WKWebLoadType) {
  69. self.target = target
  70. setupUI(webConfig:webConfig ?? WKWebViewConfig())
  71. switch loadType {
  72. case .URLString(let urltring):
  73. let urlstr = URL(string: urltring)
  74. let request = URLRequest(url: urlstr!)
  75. webView.load(request)
  76. case .HTMLName(let string):
  77. loadHost(string: string)
  78. case .POST(let string, parameters: let postString):
  79. needLoadJSPOST = true
  80. // 给每一个key,value前后加上一个“
  81. let dictMap = postString.map({"\"\($0.key)\":\"\($0.value)\""})
  82. POSTJavaScript = "post('\(string)\',{\(dictMap.joined(separator: ","))})"
  83. loadHost(string: "WKJSPOST")
  84. }
  85. }
  86. fileprivate func loadHost(string:String) {
  87. let path = Bundle.main.path(forResource: string, ofType: "html")
  88. // 获得html内容
  89. do {
  90. let html = try String(contentsOfFile: path!, encoding: String.Encoding.utf8)
  91. // 加载js
  92. webView.loadHTMLString(html, baseURL: Bundle.main.bundleURL)
  93. } catch { }
  94. }
  95. /// 执行JavaScript代码
  96. /// 例如 run_JavaScript(script:"document.getElementById('someElement').innerText")
  97. ///
  98. /// Parameter titleStr: title字符串
  99. public func run_JavaScript(javaScript:String?) {
  100. if let javaScript = javaScript {
  101. webView.evaluateJavaScript(javaScript) { result,error in
  102. NXLLog(error ?? "" as! Error)
  103. self.delegate?.webViewEvaluateJavaScript(result, error: error)
  104. }
  105. }
  106. }
  107. /// 刷新
  108. public func reload() {
  109. webView.reload()
  110. }
  111. /// 后退
  112. public func goBack() {
  113. webView.goBack()
  114. }
  115. /// 前进
  116. public func goForward() {
  117. webView.goForward()
  118. }
  119. /// 遗传webView
  120. public func removeWebView(){
  121. webView.removeObserver(self, forKeyPath: "estimatedProgress")
  122. if let scriptMessage = webConfig?.scriptMessageHandlerArray {
  123. _ = scriptMessage.map{webView.configuration.userContentController .removeScriptMessageHandler(forName: $0)}
  124. }
  125. webView.navigationDelegate = nil
  126. webView.uiDelegate = nil
  127. self.removeFromSuperview()
  128. }
  129. //请求链接处理
  130. fileprivate func pushCurrentSnapshotView(_ request: NSURLRequest) -> Void {
  131. // 连接是否为空
  132. guard let urlStr = snapShotsArray?.last else { return }
  133. // 转换成URL
  134. let url = URL(string: urlStr as! String)
  135. // 转换成NSURLRequest
  136. let lastRequest = NSURLRequest(url: url!)
  137. // 如果url是很奇怪的就不push
  138. if request.url?.absoluteString == "about:blank"{ return }
  139. // 如果url一样就不进行push
  140. if (lastRequest.url?.absoluteString == request.url?.absoluteString) {return}
  141. // snapshotView
  142. let currentSnapShotView = webView.snapshotView(afterScreenUpdates: true);
  143. //向数组添加字典
  144. snapShotsArray = [["request":request,"snapShotView":currentSnapShotView]]
  145. }
  146. open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
  147. if keyPath == "estimatedProgress"{
  148. // 设置进度条透明度
  149. progressView.alpha = CGFloat(1.0 - webView.estimatedProgress)
  150. // 给进度条添加进度和动画
  151. progressView.setProgress(Float(webView.estimatedProgress), animated: true)
  152. // 结束进度
  153. if Float(webView.estimatedProgress) >= 1.0{
  154. progressView.alpha = 0.0
  155. progressView .setProgress(0.0, animated: false)
  156. }
  157. NXLLog(webView.estimatedProgress)
  158. }
  159. }
  160. }
  161. // MARK: - WKScriptMessageHandler
  162. extension WebView: WKScriptMessageHandler{
  163. func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
  164. if let scriptMessage = webConfig?.scriptMessageHandlerArray {
  165. self.delegate?.webViewUserContentController(scriptMessage, didReceive: message)
  166. }
  167. }
  168. }
  169. // MARK: - WKNavigationDelegate
  170. extension WebView: WKNavigationDelegate{
  171. //服务器开始请求的时候调用
  172. func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
  173. self.delegate?.webView(webView, decidePolicyFor: navigationAction, decisionHandler: decisionHandler)
  174. let navigationURL = navigationAction.request.url?.absoluteString
  175. if let requestURL = navigationURL?.removingPercentEncoding {
  176. //拨打电话
  177. //兼容安卓的服务器写法:<a class = "mobile" href = "tel://电话号码"></a>
  178. //或者:<a class = "mobile" href = "tel:电话号码"></a>
  179. if requestURL.hasPrefix("tel://") {
  180. //取消WKWebView 打电话请求
  181. decisionHandler(.cancel);
  182. //用openURL 这个API打电话
  183. if let mobileURL:URL = URL(string: requestURL) {
  184. UIApplication.shared.openURL(mobileURL)
  185. }
  186. }
  187. }
  188. switch navigationAction.navigationType {
  189. case WKNavigationType.linkActivated:
  190. pushCurrentSnapshotView(navigationAction.request as NSURLRequest)
  191. break
  192. case WKNavigationType.formSubmitted:
  193. pushCurrentSnapshotView(navigationAction.request as NSURLRequest)
  194. break
  195. case WKNavigationType.backForward:
  196. break
  197. case WKNavigationType.reload:
  198. break
  199. case WKNavigationType.formResubmitted:
  200. break
  201. case WKNavigationType.other:
  202. pushCurrentSnapshotView(navigationAction.request as NSURLRequest)
  203. break
  204. @unknown default:
  205. break
  206. }
  207. decisionHandler(.allow)
  208. }
  209. //开始加载
  210. func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
  211. self.delegate?.webView(webView, didStartProvisionalNavigation: navigation)
  212. }
  213. //这个是网页加载完成,导航的变化
  214. func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
  215. self.delegate?.webView(webView, didFinish: navigation)
  216. // 判断是否需要加载(仅在第一次加载)
  217. if needLoadJSPOST == true {
  218. // 调用使用JS发送POST请求的方法
  219. run_JavaScript(javaScript: POSTJavaScript)
  220. // 将Flag置为NO(后面就不需要加载了)
  221. needLoadJSPOST = false
  222. }
  223. }
  224. //跳转失败的时候调用
  225. func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
  226. self.delegate?.webView(webView, didFail: navigation, withError: error)
  227. NXLLog(error)
  228. }
  229. // 内容加载失败时候调用
  230. func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
  231. self.delegate?.webView(webView, didFailProvisionalNavigation: navigation, withError: error)
  232. progressView.isHidden = true
  233. NXLLog(error)
  234. }
  235. // 打开新窗口委托
  236. func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
  237. if navigationAction.targetFrame?.isMainFrame == nil {
  238. webView.load(navigationAction.request)
  239. }
  240. return nil
  241. }
  242. }
  243. // MARK: - WKUIDelegate 不实现该代理方法 网页内调用弹窗时会抛出异常,导致程序崩溃
  244. extension WebView: WKUIDelegate{
  245. // 获取js 里面的提示
  246. func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
  247. let alert = UIAlertController(title: "提示", message: message, preferredStyle: .alert)
  248. alert.addAction(UIAlertAction(title: "确定", style: .default, handler: { (_) -> Void in
  249. completionHandler()
  250. }))
  251. alert.addAction(UIAlertAction(title: "取消", style: .cancel, handler: { (_) -> Void in
  252. completionHandler()
  253. }))
  254. target?.present(alert, animated: true, completion: nil)
  255. }
  256. // js 信息的交流
  257. func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
  258. let alert = UIAlertController(title: "提示", message: message, preferredStyle: .alert)
  259. alert.addAction(UIAlertAction(title: "确定", style: .default, handler: { (_) -> Void in
  260. completionHandler(true)
  261. }))
  262. alert.addAction(UIAlertAction(title: "取消", style: .cancel, handler: { (_) -> Void in
  263. completionHandler(false)
  264. }))
  265. target?.present(alert, animated: true, completion: nil)
  266. }
  267. // 交互。可输入的文本。
  268. func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
  269. let alert = UIAlertController(title: prompt, message: defaultText, preferredStyle: .alert)
  270. alert.addTextField { (textField: UITextField) -> Void in
  271. textField.textColor = UIColor.red
  272. }
  273. alert.addAction(UIAlertAction(title: "确定", style: .default, handler: { (_) -> Void in
  274. completionHandler(alert.textFields![0].text!)
  275. }))
  276. target?.present(alert, animated: true, completion: nil)
  277. }
  278. }