├── .gitignore ├── LICENSE ├── README.md ├── VDWebView.podspec ├── VDWebView.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── VDWebView ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── Source │ └── html │ │ ├── static │ │ └── js │ │ │ └── VDJSWebBridge.js │ │ └── wkweb.html ├── VDWebView │ ├── VDWebView.h │ ├── VDWebView.m │ ├── VDWebViewJSBridge.h │ ├── VDWebViewJSBridge.m │ ├── VDWebViewProtocol.h │ ├── VDWebViewScriptMessageHandler.h │ └── VDWebViewScriptMessageHandler.m ├── ViewController.h ├── ViewController.m ├── WebViewController.h ├── WebViewController.m └── main.m ├── VDWebViewTests ├── Info.plist └── VDWebViewTests.m ├── VDWebViewUITests ├── Info.plist └── VDWebViewUITests.m └── upload_pod_run.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 52 | 53 | fastlane/report.xml 54 | fastlane/Preview.html 55 | fastlane/screenshots/**/*.png 56 | fastlane/test_output 57 | 58 | # Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 duan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![WKWebView](https://upload-images.jianshu.io/upload_images/1951856-240b98abb03b5a65.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 2 | [VDWebView的源码和使用示例](https://github.com/VolientDuan/VDWebView) 3 | 4 | > VDWebView提供了最全的API调用和最方便的JS交互方式,可通过pod更新迭代;设计方案为Protocol和Target-Action;有任何意见或者问题欢迎指出。 5 | 6 | ## CocoaPods 7 | ``` 8 | pod 'VDWebView', '~> 1.0.3' 9 | ``` 10 | ## 基本描述 11 | * 封装WebKit所提供的WKWebView,提供熟悉的代理方法(类UIWebViewDelegate) 12 | * 更加方便和安全的JS与OC方法相互调用(后面我会具体说明解决方案) 13 | * 支持以target-action的方式替代delegate(两者任意选择) 14 | * 不会出现类似于使用WKWebView注册OC方法忘记注销导致循环引用无法释放的问题 15 | * 提供加载进度条的使用、预估进度值的读取等 16 | * cookie的操作 17 | * iOS和Android通用的JS与OC交互方法(通过请求拦截实现) 18 | 19 | ## 为什么使用WKWebView 20 | * `WKWebView`是iOS8后推出的WebKit框架中的控件,由于iOS12后已经弃用`UIWebView`了而且现在的大多数项目只适配到iOS8 21 | * 加载速度优于`UIWebView`且解决了加载网页时的内存泄露问题 22 | * 在和JS交互方面提供了桥梁`WKUserContentController` 23 | * 没好处这东西出来干嘛,所以综上用起来吧 24 | 25 | ## 提供了哪些更加熟悉和更加便捷的属性和方法 26 | #### VDWebViewDelegate 27 | * 类`UIWebView`代理方法,处理对象(`WKNavigationDelegate`) 28 | 29 | ``` 30 | /// 类UIWebView代理方法 31 | - (void)webViewDidStartLoad:(VDWebView *)webView; 32 | - (void)webViewDidFinishLoad:(VDWebView *)webView; 33 | - (void)webView:(VDWebView *)webView didFailLoadWithError:(NSError *)error; 34 | - (BOOL)webView:(VDWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType; 35 | ``` 36 | * 对`WKUIDelegate`和`WKScriptMessageHandler`代理的合并 37 | 38 | ``` 39 | /** 40 | JS调原生代理方法(注册了过的方法将全部通过此方法回调) 41 | */ 42 | - (void)webView:(VDWebView *)webView didReceiveScriptMessage:(WKScriptMessage *)message; 43 | 44 | /** 45 | JS弹框拦截方法--如果需要自定义弹框建议声明此方法 46 | */ 47 | - (void)webView:(VDWebView *)webView showAlertWithType:(VDJSAlertType)type title:(NSString *)title content:(NSString *)content completionHandler:(void (^)(id))completionHandler; 48 | ``` 49 | 50 | #### 新增了哪些属性和方法 51 | 52 | 详情可见`VDWebViewProtocol` 53 | 54 | ``` 55 | ///预估网页加载进度 56 | @property (nonatomic, readonly) CGFloat estimatedProgress; 57 | // 是否显示进度条 默认不显示 58 | @property (nonatomic, assign) BOOL isShowProgressBar; 59 | ///进度条 60 | @property (nonatomic, strong) UIView *progressBar; 61 | /** 62 | web页面加载完毕后的内容高度(在页面加载完成后获取) 63 | */ 64 | @property (nonatomic, readonly) CGFloat *contentHeight; 65 | /// 是否启用js调用原生弹框 默认为NO 禁止(默认加载弹框在根试图) 66 | @property (nonatomic, assign) BOOL enableAllAlert; 67 | @property (nonatomic, assign) BOOL enableAlert; 68 | @property (nonatomic, assign) BOOL enableConfirm; 69 | @property (nonatomic, assign) BOOL enablePrompt; 70 | 71 | ///back 层数 72 | - (NSInteger)countOfHistory; 73 | - (void)gobackWithStep:(NSInteger)step; 74 | 75 | ``` 76 | 77 | ## JS调用OC方法的绑定 78 | 79 | 在使用`WKWebView`时我们需要调用`WKWebView`内`configuration`中的`userContentController`所属类`WKUserContentController`提供的实例方法进行注册,具体方法如下: 80 | 81 | ``` 82 | - (void)addScriptMessageHandler:(id )scriptMessageHandler name:(NSString *)name; 83 | ``` 84 | 对应的注销方法为: 85 | 86 | ``` 87 | - (void)removeScriptMessageHandlerForName:(NSString *)name; 88 | ``` 89 | 90 | #### 已知的循环引用问题 91 | 92 | 在使用`addScriptMessageHandler:name:`方法注册时传入的这个handler被循环引用,如果不调用对应的注销方法就会导致handler这个对象无法被释放,如果你这个handler传入是webView所在的控制器,那么你就要在销毁这个控制器前注销掉你注册的方法. 93 | 94 | tip: 如何知道控制器有没有被释放,重写dealloc(),没走此方法说明未被释放 95 | 96 | #### VDWebView是如何解决循环引用问题 97 | 98 | 简要分析可分为下面三步 99 | 100 | * 使用`VDScripMessageHandler`作为注册的handler 101 | * 继承协议`WKScriptMessageHandler` 102 | * 提供target-action回调方式 103 | * 保存注册记录 104 | * 在`VDWebView`的`dealloc()`方法中获取注册记录并注销 105 | 106 | 这些做的好处在于你在使用VDWebView时无需自己去一个个手动注销了(如果你注册的方法多的话那就是噩梦了) 107 | 108 | #### VDWebView是如何进行方法的注册和回调的 109 | 110 | * 方法的注册 111 | 112 | ``` 113 | - (void)addScriptMessageHandler:(id)scriptMessageHandler name:(NSString *)name; 114 | ``` 115 | 116 | * JS调用说明 117 | 118 | ``` 119 | // 没效果可使用try-catch 120 | window.webkit.messageHandlers.#OC方法名#.postMessage(#参数#) 121 | 122 | ``` 123 | 124 | 回调方式分两种:delegate和target-action; 两种方式只能存一,优先delegate 125 | 126 | * delegate方式,只需在控制器中声明`VDWebViewDelegate`中的方法 127 | 128 | ``` 129 | - (void)webView:(VDWebView *)webView didReceiveScriptMessage:(WKScriptMessage *)message; 130 | ``` 131 | * target-action方式 132 | * 不能声明上述的代理方法 133 | * 在控制器(方法注册传入的scriptMessageHandler)中声明同名的OC方法 134 | 135 | #### 为什么要增加target-action的方式 136 | > target-action:目标-动作模式,拜C语言所赐,更是灵活很多,编译期没有任何检查,都是运行时的绑定 137 | 138 | * 在`VDWebView`中就是通过`NSSelectorFromString()`动态加载方法,再通过`NSMethodSignature`和`NSInvocation`进行方法的签名和调用 139 | * 这样就可以充分的体现JS调用对应的OC方法和一对一更加清晰和方便处理 140 | 141 | ## JS的调用和注入 142 | 143 | 可通过两种方式进行JS方法的调用,推荐第一种 144 | 145 | * WKWebView的同名方法 146 | 147 | ``` 148 | - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *))completionHandler; 149 | 150 | ``` 151 | * UIWebView的同名方法(不建议使用这个办法,因为会在内部等待执行结果) 152 | 153 | ``` 154 | - (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)javaScriptString; 155 | ``` 156 | 157 | 脚本的注入和移除 158 | 159 | ``` 160 | /** 161 | 注入脚本(js...) 162 | */ 163 | - (void)addUserScriptWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)mainFrameOnly; 164 | 165 | /** 166 | 移除所有注入的脚本 167 | */ 168 | - (void)removeAllUserScripts; 169 | ``` 170 | ## cookie的处理(待优化)`VDWebViewCookiesProtocol` 171 | 172 | #### 提供cookie的共享 173 | 由于WKWebView的cookie是和NSHTTPCookieStorage不共享,这就造成使用WKWebView打开的web页面无法获取到通过原生请求登录的cookie,当然其它解决方案有很多种,比如 174 | * 通过URL的拼接把登录信息传递过去 175 | * 通过js方法传值 176 | 177 | 但是用了VDWebView就不需要考虑cookie的问题了,因为它已经默认把cookie带过去了,当然你也可以手动去关闭 178 | 179 | ``` 180 | /** 181 | 不同步NSHTTPCookieStorage存储的cookies 默认同步:NO 182 | 同步NSHTTPCookieStorage中的cookie到WKWebView中,有可能会污染WKWebView中的cookie管理 183 | */ 184 | @property (nonatomic, assign)BOOL httpCookiesDisable; 185 | ``` 186 | 187 | #### 对cookie的操作 188 | ``` 189 | /** 190 | 设置cookie 191 | */ 192 | - (void)setCookieWithKey:(NSString *)key value:(NSString *)value expires:(NSTimeInterval)expires domain:(NSString *)domain; 193 | - (void)setCookies:(NSString *)cookies; 194 | /** 195 | 获取cookie 196 | */ 197 | - (NSArray *)getCookies; 198 | ``` 199 | 200 | ## iOS和Android通用的JS与OC交互方法`VDWebViewJSBridge` 201 | 202 | #### 关于使用方法 203 | 204 | 你只需要调用`VDWebView`继承的协议`VDWebViewProtocol`所提供的初始化方法`bridgeInitialized`即可 205 | 206 | ``` 207 | // 调用初始化 在webView的控制器中实现同名方法 208 | self.webView.delegate = self; 209 | [self.webView bridgeInitialized]; 210 | ``` 211 | 212 | ### js调用原生 213 | 214 | 与对应的js配套使用,此方案适用于iOS和Android对应的js文件如下 215 | 216 | [VDJSWebBridge.js传送门](https://github.com/VolientDuan/VDWebView/blob/master/VDWebView/Source/html/static/js/VDJSWebBridge.js) 217 | ``` 218 | // 在js中直接调用VDJSWebBridge.js提供的方法,详情请查看js 219 | // methodName为控制器中声明的方法名,params为json字符串 220 | vd_jsBridge(methodName, params) 221 | ``` 222 | #### 从JS方法触发开始整个调用的过程如下 223 | 1. 对params进行了base64编码 224 | 2. 最终的请求URL格式为 vdjsbridge://methodName?params=base64(params) 225 | 3. APP拦截请求,解析URL匹配scheme(vdjsbridge),获取方法名,base64解码和json转对象获取参数值 226 | 4. 接受到第三步获取的方法名和参数后通过Target-Action方法调用对应的方法 227 | 228 | ### OC调用JS方法 229 | 230 | ``` 231 | // JS 方法 232 | function jsMethod(param1, param2, param3) { 233 | alert("使用jsBridge调用js方法成功参数为:"+param1+param2+param3) 234 | return "success"; 235 | } 236 | 237 | // OC调用JS方法 238 | [self.webView.bridge executeJsMethod:@"jsMethod" params:@[@"1",@"2",@"3"] completionHandler:^(id result, NSError *error) { 239 | NSLog(@"\njs方法执行h结果回调:%@\n错误信息:%@",result,error); 240 | }]; 241 | 242 | ``` 243 | 244 | ## 后续版本思考和设计中 245 | * ~~cookie的处理~~ 246 | * ~~拦截webView内部请求通过自定义URL的方式进行JS交互~~ 247 | * APP和web资源共享问题:比如图片 248 | 249 | -------------------------------------------------------------------------------- /VDWebView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'VDWebView' 3 | s.version = '1.0.3' 4 | s.summary = 'a great webView for iOS' 5 | s.homepage = 'https://github.com/VolientDuan/VDWebView' 6 | s.license = 'MIT' 7 | s.authors = { 'volientDuan' => 'volientduan@163.com' } 8 | s.platform = :ios, '8.0' 9 | s.framework = "UIKit" 10 | s.source = { :git => 'https://github.com/VolientDuan/VDWebView.git', :tag => s.version } 11 | s.requires_arc = true 12 | s.source_files = 'VDWebView/VDWebView/**.{h,m}' 13 | end 14 | -------------------------------------------------------------------------------- /VDWebView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0702022221EC953B00867C8C /* static in Resources */ = {isa = PBXBuildFile; fileRef = 0702022121EC953B00867C8C /* static */; }; 11 | 072EBB1821E854850086DCDA /* VDWebViewJSBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 072EBB1721E854850086DCDA /* VDWebViewJSBridge.m */; }; 12 | 072EBB1B21E85A730086DCDA /* VDWebViewScriptMessageHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 072EBB1A21E85A730086DCDA /* VDWebViewScriptMessageHandler.m */; }; 13 | 5A94F1A221CCCA11003F92F4 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5A94F1A121CCCA11003F92F4 /* AppDelegate.m */; }; 14 | 5A94F1A521CCCA11003F92F4 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5A94F1A421CCCA11003F92F4 /* ViewController.m */; }; 15 | 5A94F1A821CCCA11003F92F4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A94F1A621CCCA11003F92F4 /* Main.storyboard */; }; 16 | 5A94F1AA21CCCA13003F92F4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5A94F1A921CCCA13003F92F4 /* Assets.xcassets */; }; 17 | 5A94F1AD21CCCA13003F92F4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5A94F1AB21CCCA13003F92F4 /* LaunchScreen.storyboard */; }; 18 | 5A94F1B021CCCA13003F92F4 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 5A94F1AF21CCCA13003F92F4 /* main.m */; }; 19 | 5A94F1BA21CCCA13003F92F4 /* VDWebViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5A94F1B921CCCA13003F92F4 /* VDWebViewTests.m */; }; 20 | 5A94F1C521CCCA13003F92F4 /* VDWebViewUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5A94F1C421CCCA13003F92F4 /* VDWebViewUITests.m */; }; 21 | 5A94F1D521CCCAAE003F92F4 /* VDWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5A94F1D421CCCAAE003F92F4 /* VDWebView.m */; }; 22 | 5A94F1D821CCCAF2003F92F4 /* WebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5A94F1D621CCCAF2003F92F4 /* WebViewController.m */; }; 23 | 5A94F1DC21CCCC98003F92F4 /* wkweb.html in Resources */ = {isa = PBXBuildFile; fileRef = 5A94F1DB21CCCC98003F92F4 /* wkweb.html */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | 5A94F1B621CCCA13003F92F4 /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = 5A94F19521CCCA11003F92F4 /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = 5A94F19C21CCCA11003F92F4; 32 | remoteInfo = VDWebView; 33 | }; 34 | 5A94F1C121CCCA13003F92F4 /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = 5A94F19521CCCA11003F92F4 /* Project object */; 37 | proxyType = 1; 38 | remoteGlobalIDString = 5A94F19C21CCCA11003F92F4; 39 | remoteInfo = VDWebView; 40 | }; 41 | /* End PBXContainerItemProxy section */ 42 | 43 | /* Begin PBXFileReference section */ 44 | 0702022121EC953B00867C8C /* static */ = {isa = PBXFileReference; lastKnownFileType = folder; path = static; sourceTree = ""; }; 45 | 072EBB1521E848380086DCDA /* VDWebViewProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VDWebViewProtocol.h; sourceTree = ""; }; 46 | 072EBB1621E854850086DCDA /* VDWebViewJSBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VDWebViewJSBridge.h; sourceTree = ""; }; 47 | 072EBB1721E854850086DCDA /* VDWebViewJSBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VDWebViewJSBridge.m; sourceTree = ""; }; 48 | 072EBB1921E85A730086DCDA /* VDWebViewScriptMessageHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VDWebViewScriptMessageHandler.h; sourceTree = ""; }; 49 | 072EBB1A21E85A730086DCDA /* VDWebViewScriptMessageHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VDWebViewScriptMessageHandler.m; sourceTree = ""; }; 50 | 5A94F19D21CCCA11003F92F4 /* VDWebView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VDWebView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 5A94F1A021CCCA11003F92F4 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 52 | 5A94F1A121CCCA11003F92F4 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 53 | 5A94F1A321CCCA11003F92F4 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 54 | 5A94F1A421CCCA11003F92F4 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 55 | 5A94F1A721CCCA11003F92F4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 56 | 5A94F1A921CCCA13003F92F4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 57 | 5A94F1AC21CCCA13003F92F4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 58 | 5A94F1AE21CCCA13003F92F4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 59 | 5A94F1AF21CCCA13003F92F4 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 60 | 5A94F1B521CCCA13003F92F4 /* VDWebViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VDWebViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | 5A94F1B921CCCA13003F92F4 /* VDWebViewTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VDWebViewTests.m; sourceTree = ""; }; 62 | 5A94F1BB21CCCA13003F92F4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 63 | 5A94F1C021CCCA13003F92F4 /* VDWebViewUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VDWebViewUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | 5A94F1C421CCCA13003F92F4 /* VDWebViewUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VDWebViewUITests.m; sourceTree = ""; }; 65 | 5A94F1C621CCCA13003F92F4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 66 | 5A94F1D321CCCAAE003F92F4 /* VDWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VDWebView.h; sourceTree = ""; }; 67 | 5A94F1D421CCCAAE003F92F4 /* VDWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VDWebView.m; sourceTree = ""; }; 68 | 5A94F1D621CCCAF2003F92F4 /* WebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebViewController.m; sourceTree = ""; }; 69 | 5A94F1D721CCCAF2003F92F4 /* WebViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebViewController.h; sourceTree = ""; }; 70 | 5A94F1DB21CCCC98003F92F4 /* wkweb.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = wkweb.html; sourceTree = ""; }; 71 | /* End PBXFileReference section */ 72 | 73 | /* Begin PBXFrameworksBuildPhase section */ 74 | 5A94F19A21CCCA11003F92F4 /* Frameworks */ = { 75 | isa = PBXFrameworksBuildPhase; 76 | buildActionMask = 2147483647; 77 | files = ( 78 | ); 79 | runOnlyForDeploymentPostprocessing = 0; 80 | }; 81 | 5A94F1B221CCCA13003F92F4 /* Frameworks */ = { 82 | isa = PBXFrameworksBuildPhase; 83 | buildActionMask = 2147483647; 84 | files = ( 85 | ); 86 | runOnlyForDeploymentPostprocessing = 0; 87 | }; 88 | 5A94F1BD21CCCA13003F92F4 /* Frameworks */ = { 89 | isa = PBXFrameworksBuildPhase; 90 | buildActionMask = 2147483647; 91 | files = ( 92 | ); 93 | runOnlyForDeploymentPostprocessing = 0; 94 | }; 95 | /* End PBXFrameworksBuildPhase section */ 96 | 97 | /* Begin PBXGroup section */ 98 | 5A94F19421CCCA11003F92F4 = { 99 | isa = PBXGroup; 100 | children = ( 101 | 5A94F19F21CCCA11003F92F4 /* VDWebView */, 102 | 5A94F1B821CCCA13003F92F4 /* VDWebViewTests */, 103 | 5A94F1C321CCCA13003F92F4 /* VDWebViewUITests */, 104 | 5A94F19E21CCCA11003F92F4 /* Products */, 105 | ); 106 | sourceTree = ""; 107 | }; 108 | 5A94F19E21CCCA11003F92F4 /* Products */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 5A94F19D21CCCA11003F92F4 /* VDWebView.app */, 112 | 5A94F1B521CCCA13003F92F4 /* VDWebViewTests.xctest */, 113 | 5A94F1C021CCCA13003F92F4 /* VDWebViewUITests.xctest */, 114 | ); 115 | name = Products; 116 | sourceTree = ""; 117 | }; 118 | 5A94F19F21CCCA11003F92F4 /* VDWebView */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 5A94F1D921CCCC98003F92F4 /* Source */, 122 | 5A94F1D221CCCA70003F92F4 /* VDWebView */, 123 | 5A94F1A021CCCA11003F92F4 /* AppDelegate.h */, 124 | 5A94F1A121CCCA11003F92F4 /* AppDelegate.m */, 125 | 5A94F1A321CCCA11003F92F4 /* ViewController.h */, 126 | 5A94F1A421CCCA11003F92F4 /* ViewController.m */, 127 | 5A94F1D721CCCAF2003F92F4 /* WebViewController.h */, 128 | 5A94F1D621CCCAF2003F92F4 /* WebViewController.m */, 129 | 5A94F1A621CCCA11003F92F4 /* Main.storyboard */, 130 | 5A94F1A921CCCA13003F92F4 /* Assets.xcassets */, 131 | 5A94F1AB21CCCA13003F92F4 /* LaunchScreen.storyboard */, 132 | 5A94F1AE21CCCA13003F92F4 /* Info.plist */, 133 | 5A94F1AF21CCCA13003F92F4 /* main.m */, 134 | ); 135 | path = VDWebView; 136 | sourceTree = ""; 137 | }; 138 | 5A94F1B821CCCA13003F92F4 /* VDWebViewTests */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | 5A94F1B921CCCA13003F92F4 /* VDWebViewTests.m */, 142 | 5A94F1BB21CCCA13003F92F4 /* Info.plist */, 143 | ); 144 | path = VDWebViewTests; 145 | sourceTree = ""; 146 | }; 147 | 5A94F1C321CCCA13003F92F4 /* VDWebViewUITests */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 5A94F1C421CCCA13003F92F4 /* VDWebViewUITests.m */, 151 | 5A94F1C621CCCA13003F92F4 /* Info.plist */, 152 | ); 153 | path = VDWebViewUITests; 154 | sourceTree = ""; 155 | }; 156 | 5A94F1D221CCCA70003F92F4 /* VDWebView */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 5A94F1D321CCCAAE003F92F4 /* VDWebView.h */, 160 | 5A94F1D421CCCAAE003F92F4 /* VDWebView.m */, 161 | 072EBB1521E848380086DCDA /* VDWebViewProtocol.h */, 162 | 072EBB1921E85A730086DCDA /* VDWebViewScriptMessageHandler.h */, 163 | 072EBB1A21E85A730086DCDA /* VDWebViewScriptMessageHandler.m */, 164 | 072EBB1621E854850086DCDA /* VDWebViewJSBridge.h */, 165 | 072EBB1721E854850086DCDA /* VDWebViewJSBridge.m */, 166 | ); 167 | path = VDWebView; 168 | sourceTree = ""; 169 | }; 170 | 5A94F1D921CCCC98003F92F4 /* Source */ = { 171 | isa = PBXGroup; 172 | children = ( 173 | 5A94F1DA21CCCC98003F92F4 /* html */, 174 | ); 175 | path = Source; 176 | sourceTree = ""; 177 | }; 178 | 5A94F1DA21CCCC98003F92F4 /* html */ = { 179 | isa = PBXGroup; 180 | children = ( 181 | 0702022121EC953B00867C8C /* static */, 182 | 5A94F1DB21CCCC98003F92F4 /* wkweb.html */, 183 | ); 184 | path = html; 185 | sourceTree = ""; 186 | }; 187 | /* End PBXGroup section */ 188 | 189 | /* Begin PBXNativeTarget section */ 190 | 5A94F19C21CCCA11003F92F4 /* VDWebView */ = { 191 | isa = PBXNativeTarget; 192 | buildConfigurationList = 5A94F1C921CCCA13003F92F4 /* Build configuration list for PBXNativeTarget "VDWebView" */; 193 | buildPhases = ( 194 | 5A94F19921CCCA11003F92F4 /* Sources */, 195 | 5A94F19A21CCCA11003F92F4 /* Frameworks */, 196 | 5A94F19B21CCCA11003F92F4 /* Resources */, 197 | ); 198 | buildRules = ( 199 | ); 200 | dependencies = ( 201 | ); 202 | name = VDWebView; 203 | productName = VDWebView; 204 | productReference = 5A94F19D21CCCA11003F92F4 /* VDWebView.app */; 205 | productType = "com.apple.product-type.application"; 206 | }; 207 | 5A94F1B421CCCA13003F92F4 /* VDWebViewTests */ = { 208 | isa = PBXNativeTarget; 209 | buildConfigurationList = 5A94F1CC21CCCA13003F92F4 /* Build configuration list for PBXNativeTarget "VDWebViewTests" */; 210 | buildPhases = ( 211 | 5A94F1B121CCCA13003F92F4 /* Sources */, 212 | 5A94F1B221CCCA13003F92F4 /* Frameworks */, 213 | 5A94F1B321CCCA13003F92F4 /* Resources */, 214 | ); 215 | buildRules = ( 216 | ); 217 | dependencies = ( 218 | 5A94F1B721CCCA13003F92F4 /* PBXTargetDependency */, 219 | ); 220 | name = VDWebViewTests; 221 | productName = VDWebViewTests; 222 | productReference = 5A94F1B521CCCA13003F92F4 /* VDWebViewTests.xctest */; 223 | productType = "com.apple.product-type.bundle.unit-test"; 224 | }; 225 | 5A94F1BF21CCCA13003F92F4 /* VDWebViewUITests */ = { 226 | isa = PBXNativeTarget; 227 | buildConfigurationList = 5A94F1CF21CCCA13003F92F4 /* Build configuration list for PBXNativeTarget "VDWebViewUITests" */; 228 | buildPhases = ( 229 | 5A94F1BC21CCCA13003F92F4 /* Sources */, 230 | 5A94F1BD21CCCA13003F92F4 /* Frameworks */, 231 | 5A94F1BE21CCCA13003F92F4 /* Resources */, 232 | ); 233 | buildRules = ( 234 | ); 235 | dependencies = ( 236 | 5A94F1C221CCCA13003F92F4 /* PBXTargetDependency */, 237 | ); 238 | name = VDWebViewUITests; 239 | productName = VDWebViewUITests; 240 | productReference = 5A94F1C021CCCA13003F92F4 /* VDWebViewUITests.xctest */; 241 | productType = "com.apple.product-type.bundle.ui-testing"; 242 | }; 243 | /* End PBXNativeTarget section */ 244 | 245 | /* Begin PBXProject section */ 246 | 5A94F19521CCCA11003F92F4 /* Project object */ = { 247 | isa = PBXProject; 248 | attributes = { 249 | LastUpgradeCheck = 1010; 250 | ORGANIZATIONNAME = volientDuan; 251 | TargetAttributes = { 252 | 5A94F19C21CCCA11003F92F4 = { 253 | CreatedOnToolsVersion = 10.1; 254 | }; 255 | 5A94F1B421CCCA13003F92F4 = { 256 | CreatedOnToolsVersion = 10.1; 257 | TestTargetID = 5A94F19C21CCCA11003F92F4; 258 | }; 259 | 5A94F1BF21CCCA13003F92F4 = { 260 | CreatedOnToolsVersion = 10.1; 261 | TestTargetID = 5A94F19C21CCCA11003F92F4; 262 | }; 263 | }; 264 | }; 265 | buildConfigurationList = 5A94F19821CCCA11003F92F4 /* Build configuration list for PBXProject "VDWebView" */; 266 | compatibilityVersion = "Xcode 9.3"; 267 | developmentRegion = en; 268 | hasScannedForEncodings = 0; 269 | knownRegions = ( 270 | en, 271 | Base, 272 | ); 273 | mainGroup = 5A94F19421CCCA11003F92F4; 274 | productRefGroup = 5A94F19E21CCCA11003F92F4 /* Products */; 275 | projectDirPath = ""; 276 | projectRoot = ""; 277 | targets = ( 278 | 5A94F19C21CCCA11003F92F4 /* VDWebView */, 279 | 5A94F1B421CCCA13003F92F4 /* VDWebViewTests */, 280 | 5A94F1BF21CCCA13003F92F4 /* VDWebViewUITests */, 281 | ); 282 | }; 283 | /* End PBXProject section */ 284 | 285 | /* Begin PBXResourcesBuildPhase section */ 286 | 5A94F19B21CCCA11003F92F4 /* Resources */ = { 287 | isa = PBXResourcesBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | 0702022221EC953B00867C8C /* static in Resources */, 291 | 5A94F1AD21CCCA13003F92F4 /* LaunchScreen.storyboard in Resources */, 292 | 5A94F1DC21CCCC98003F92F4 /* wkweb.html in Resources */, 293 | 5A94F1AA21CCCA13003F92F4 /* Assets.xcassets in Resources */, 294 | 5A94F1A821CCCA11003F92F4 /* Main.storyboard in Resources */, 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | }; 298 | 5A94F1B321CCCA13003F92F4 /* Resources */ = { 299 | isa = PBXResourcesBuildPhase; 300 | buildActionMask = 2147483647; 301 | files = ( 302 | ); 303 | runOnlyForDeploymentPostprocessing = 0; 304 | }; 305 | 5A94F1BE21CCCA13003F92F4 /* Resources */ = { 306 | isa = PBXResourcesBuildPhase; 307 | buildActionMask = 2147483647; 308 | files = ( 309 | ); 310 | runOnlyForDeploymentPostprocessing = 0; 311 | }; 312 | /* End PBXResourcesBuildPhase section */ 313 | 314 | /* Begin PBXSourcesBuildPhase section */ 315 | 5A94F19921CCCA11003F92F4 /* Sources */ = { 316 | isa = PBXSourcesBuildPhase; 317 | buildActionMask = 2147483647; 318 | files = ( 319 | 5A94F1A521CCCA11003F92F4 /* ViewController.m in Sources */, 320 | 072EBB1821E854850086DCDA /* VDWebViewJSBridge.m in Sources */, 321 | 072EBB1B21E85A730086DCDA /* VDWebViewScriptMessageHandler.m in Sources */, 322 | 5A94F1D821CCCAF2003F92F4 /* WebViewController.m in Sources */, 323 | 5A94F1D521CCCAAE003F92F4 /* VDWebView.m in Sources */, 324 | 5A94F1B021CCCA13003F92F4 /* main.m in Sources */, 325 | 5A94F1A221CCCA11003F92F4 /* AppDelegate.m in Sources */, 326 | ); 327 | runOnlyForDeploymentPostprocessing = 0; 328 | }; 329 | 5A94F1B121CCCA13003F92F4 /* Sources */ = { 330 | isa = PBXSourcesBuildPhase; 331 | buildActionMask = 2147483647; 332 | files = ( 333 | 5A94F1BA21CCCA13003F92F4 /* VDWebViewTests.m in Sources */, 334 | ); 335 | runOnlyForDeploymentPostprocessing = 0; 336 | }; 337 | 5A94F1BC21CCCA13003F92F4 /* Sources */ = { 338 | isa = PBXSourcesBuildPhase; 339 | buildActionMask = 2147483647; 340 | files = ( 341 | 5A94F1C521CCCA13003F92F4 /* VDWebViewUITests.m in Sources */, 342 | ); 343 | runOnlyForDeploymentPostprocessing = 0; 344 | }; 345 | /* End PBXSourcesBuildPhase section */ 346 | 347 | /* Begin PBXTargetDependency section */ 348 | 5A94F1B721CCCA13003F92F4 /* PBXTargetDependency */ = { 349 | isa = PBXTargetDependency; 350 | target = 5A94F19C21CCCA11003F92F4 /* VDWebView */; 351 | targetProxy = 5A94F1B621CCCA13003F92F4 /* PBXContainerItemProxy */; 352 | }; 353 | 5A94F1C221CCCA13003F92F4 /* PBXTargetDependency */ = { 354 | isa = PBXTargetDependency; 355 | target = 5A94F19C21CCCA11003F92F4 /* VDWebView */; 356 | targetProxy = 5A94F1C121CCCA13003F92F4 /* PBXContainerItemProxy */; 357 | }; 358 | /* End PBXTargetDependency section */ 359 | 360 | /* Begin PBXVariantGroup section */ 361 | 5A94F1A621CCCA11003F92F4 /* Main.storyboard */ = { 362 | isa = PBXVariantGroup; 363 | children = ( 364 | 5A94F1A721CCCA11003F92F4 /* Base */, 365 | ); 366 | name = Main.storyboard; 367 | sourceTree = ""; 368 | }; 369 | 5A94F1AB21CCCA13003F92F4 /* LaunchScreen.storyboard */ = { 370 | isa = PBXVariantGroup; 371 | children = ( 372 | 5A94F1AC21CCCA13003F92F4 /* Base */, 373 | ); 374 | name = LaunchScreen.storyboard; 375 | sourceTree = ""; 376 | }; 377 | /* End PBXVariantGroup section */ 378 | 379 | /* Begin XCBuildConfiguration section */ 380 | 5A94F1C721CCCA13003F92F4 /* Debug */ = { 381 | isa = XCBuildConfiguration; 382 | buildSettings = { 383 | ALWAYS_SEARCH_USER_PATHS = NO; 384 | CLANG_ANALYZER_NONNULL = YES; 385 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 386 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 387 | CLANG_CXX_LIBRARY = "libc++"; 388 | CLANG_ENABLE_MODULES = YES; 389 | CLANG_ENABLE_OBJC_ARC = YES; 390 | CLANG_ENABLE_OBJC_WEAK = YES; 391 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 392 | CLANG_WARN_BOOL_CONVERSION = YES; 393 | CLANG_WARN_COMMA = YES; 394 | CLANG_WARN_CONSTANT_CONVERSION = YES; 395 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 396 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 397 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 398 | CLANG_WARN_EMPTY_BODY = YES; 399 | CLANG_WARN_ENUM_CONVERSION = YES; 400 | CLANG_WARN_INFINITE_RECURSION = YES; 401 | CLANG_WARN_INT_CONVERSION = YES; 402 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 403 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 404 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 405 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 406 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 407 | CLANG_WARN_STRICT_PROTOTYPES = YES; 408 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 409 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 410 | CLANG_WARN_UNREACHABLE_CODE = YES; 411 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 412 | CODE_SIGN_IDENTITY = "iPhone Developer"; 413 | COPY_PHASE_STRIP = NO; 414 | DEBUG_INFORMATION_FORMAT = dwarf; 415 | ENABLE_STRICT_OBJC_MSGSEND = YES; 416 | ENABLE_TESTABILITY = YES; 417 | GCC_C_LANGUAGE_STANDARD = gnu11; 418 | GCC_DYNAMIC_NO_PIC = NO; 419 | GCC_NO_COMMON_BLOCKS = YES; 420 | GCC_OPTIMIZATION_LEVEL = 0; 421 | GCC_PREPROCESSOR_DEFINITIONS = ( 422 | "DEBUG=1", 423 | "$(inherited)", 424 | ); 425 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 426 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 427 | GCC_WARN_UNDECLARED_SELECTOR = YES; 428 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 429 | GCC_WARN_UNUSED_FUNCTION = YES; 430 | GCC_WARN_UNUSED_VARIABLE = YES; 431 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 432 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 433 | MTL_FAST_MATH = YES; 434 | ONLY_ACTIVE_ARCH = YES; 435 | SDKROOT = iphoneos; 436 | }; 437 | name = Debug; 438 | }; 439 | 5A94F1C821CCCA13003F92F4 /* Release */ = { 440 | isa = XCBuildConfiguration; 441 | buildSettings = { 442 | ALWAYS_SEARCH_USER_PATHS = NO; 443 | CLANG_ANALYZER_NONNULL = YES; 444 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 445 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 446 | CLANG_CXX_LIBRARY = "libc++"; 447 | CLANG_ENABLE_MODULES = YES; 448 | CLANG_ENABLE_OBJC_ARC = YES; 449 | CLANG_ENABLE_OBJC_WEAK = YES; 450 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 451 | CLANG_WARN_BOOL_CONVERSION = YES; 452 | CLANG_WARN_COMMA = YES; 453 | CLANG_WARN_CONSTANT_CONVERSION = YES; 454 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 455 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 456 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 457 | CLANG_WARN_EMPTY_BODY = YES; 458 | CLANG_WARN_ENUM_CONVERSION = YES; 459 | CLANG_WARN_INFINITE_RECURSION = YES; 460 | CLANG_WARN_INT_CONVERSION = YES; 461 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 462 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 463 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 464 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 465 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 466 | CLANG_WARN_STRICT_PROTOTYPES = YES; 467 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 468 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 469 | CLANG_WARN_UNREACHABLE_CODE = YES; 470 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 471 | CODE_SIGN_IDENTITY = "iPhone Developer"; 472 | COPY_PHASE_STRIP = NO; 473 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 474 | ENABLE_NS_ASSERTIONS = NO; 475 | ENABLE_STRICT_OBJC_MSGSEND = YES; 476 | GCC_C_LANGUAGE_STANDARD = gnu11; 477 | GCC_NO_COMMON_BLOCKS = YES; 478 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 479 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 480 | GCC_WARN_UNDECLARED_SELECTOR = YES; 481 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 482 | GCC_WARN_UNUSED_FUNCTION = YES; 483 | GCC_WARN_UNUSED_VARIABLE = YES; 484 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 485 | MTL_ENABLE_DEBUG_INFO = NO; 486 | MTL_FAST_MATH = YES; 487 | SDKROOT = iphoneos; 488 | VALIDATE_PRODUCT = YES; 489 | }; 490 | name = Release; 491 | }; 492 | 5A94F1CA21CCCA13003F92F4 /* Debug */ = { 493 | isa = XCBuildConfiguration; 494 | buildSettings = { 495 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 496 | CODE_SIGN_STYLE = Automatic; 497 | DEVELOPMENT_TEAM = X2GM9ZBPQT; 498 | INFOPLIST_FILE = VDWebView/Info.plist; 499 | LD_RUNPATH_SEARCH_PATHS = ( 500 | "$(inherited)", 501 | "@executable_path/Frameworks", 502 | ); 503 | PRODUCT_BUNDLE_IDENTIFIER = com.volient.eleme; 504 | PRODUCT_NAME = "$(TARGET_NAME)"; 505 | TARGETED_DEVICE_FAMILY = "1,2"; 506 | }; 507 | name = Debug; 508 | }; 509 | 5A94F1CB21CCCA13003F92F4 /* Release */ = { 510 | isa = XCBuildConfiguration; 511 | buildSettings = { 512 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 513 | CODE_SIGN_STYLE = Automatic; 514 | DEVELOPMENT_TEAM = X2GM9ZBPQT; 515 | INFOPLIST_FILE = VDWebView/Info.plist; 516 | LD_RUNPATH_SEARCH_PATHS = ( 517 | "$(inherited)", 518 | "@executable_path/Frameworks", 519 | ); 520 | PRODUCT_BUNDLE_IDENTIFIER = com.volient.eleme; 521 | PRODUCT_NAME = "$(TARGET_NAME)"; 522 | TARGETED_DEVICE_FAMILY = "1,2"; 523 | }; 524 | name = Release; 525 | }; 526 | 5A94F1CD21CCCA13003F92F4 /* Debug */ = { 527 | isa = XCBuildConfiguration; 528 | buildSettings = { 529 | BUNDLE_LOADER = "$(TEST_HOST)"; 530 | CODE_SIGN_STYLE = Automatic; 531 | DEVELOPMENT_TEAM = M6Z935H8QD; 532 | INFOPLIST_FILE = VDWebViewTests/Info.plist; 533 | LD_RUNPATH_SEARCH_PATHS = ( 534 | "$(inherited)", 535 | "@executable_path/Frameworks", 536 | "@loader_path/Frameworks", 537 | ); 538 | PRODUCT_BUNDLE_IDENTIFIER = com.volient.VDWebViewTests; 539 | PRODUCT_NAME = "$(TARGET_NAME)"; 540 | TARGETED_DEVICE_FAMILY = "1,2"; 541 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VDWebView.app/VDWebView"; 542 | }; 543 | name = Debug; 544 | }; 545 | 5A94F1CE21CCCA13003F92F4 /* Release */ = { 546 | isa = XCBuildConfiguration; 547 | buildSettings = { 548 | BUNDLE_LOADER = "$(TEST_HOST)"; 549 | CODE_SIGN_STYLE = Automatic; 550 | DEVELOPMENT_TEAM = M6Z935H8QD; 551 | INFOPLIST_FILE = VDWebViewTests/Info.plist; 552 | LD_RUNPATH_SEARCH_PATHS = ( 553 | "$(inherited)", 554 | "@executable_path/Frameworks", 555 | "@loader_path/Frameworks", 556 | ); 557 | PRODUCT_BUNDLE_IDENTIFIER = com.volient.VDWebViewTests; 558 | PRODUCT_NAME = "$(TARGET_NAME)"; 559 | TARGETED_DEVICE_FAMILY = "1,2"; 560 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VDWebView.app/VDWebView"; 561 | }; 562 | name = Release; 563 | }; 564 | 5A94F1D021CCCA13003F92F4 /* Debug */ = { 565 | isa = XCBuildConfiguration; 566 | buildSettings = { 567 | CODE_SIGN_STYLE = Automatic; 568 | DEVELOPMENT_TEAM = M6Z935H8QD; 569 | INFOPLIST_FILE = VDWebViewUITests/Info.plist; 570 | LD_RUNPATH_SEARCH_PATHS = ( 571 | "$(inherited)", 572 | "@executable_path/Frameworks", 573 | "@loader_path/Frameworks", 574 | ); 575 | PRODUCT_BUNDLE_IDENTIFIER = com.volient.VDWebViewUITests; 576 | PRODUCT_NAME = "$(TARGET_NAME)"; 577 | TARGETED_DEVICE_FAMILY = "1,2"; 578 | TEST_TARGET_NAME = VDWebView; 579 | }; 580 | name = Debug; 581 | }; 582 | 5A94F1D121CCCA13003F92F4 /* Release */ = { 583 | isa = XCBuildConfiguration; 584 | buildSettings = { 585 | CODE_SIGN_STYLE = Automatic; 586 | DEVELOPMENT_TEAM = M6Z935H8QD; 587 | INFOPLIST_FILE = VDWebViewUITests/Info.plist; 588 | LD_RUNPATH_SEARCH_PATHS = ( 589 | "$(inherited)", 590 | "@executable_path/Frameworks", 591 | "@loader_path/Frameworks", 592 | ); 593 | PRODUCT_BUNDLE_IDENTIFIER = com.volient.VDWebViewUITests; 594 | PRODUCT_NAME = "$(TARGET_NAME)"; 595 | TARGETED_DEVICE_FAMILY = "1,2"; 596 | TEST_TARGET_NAME = VDWebView; 597 | }; 598 | name = Release; 599 | }; 600 | /* End XCBuildConfiguration section */ 601 | 602 | /* Begin XCConfigurationList section */ 603 | 5A94F19821CCCA11003F92F4 /* Build configuration list for PBXProject "VDWebView" */ = { 604 | isa = XCConfigurationList; 605 | buildConfigurations = ( 606 | 5A94F1C721CCCA13003F92F4 /* Debug */, 607 | 5A94F1C821CCCA13003F92F4 /* Release */, 608 | ); 609 | defaultConfigurationIsVisible = 0; 610 | defaultConfigurationName = Release; 611 | }; 612 | 5A94F1C921CCCA13003F92F4 /* Build configuration list for PBXNativeTarget "VDWebView" */ = { 613 | isa = XCConfigurationList; 614 | buildConfigurations = ( 615 | 5A94F1CA21CCCA13003F92F4 /* Debug */, 616 | 5A94F1CB21CCCA13003F92F4 /* Release */, 617 | ); 618 | defaultConfigurationIsVisible = 0; 619 | defaultConfigurationName = Release; 620 | }; 621 | 5A94F1CC21CCCA13003F92F4 /* Build configuration list for PBXNativeTarget "VDWebViewTests" */ = { 622 | isa = XCConfigurationList; 623 | buildConfigurations = ( 624 | 5A94F1CD21CCCA13003F92F4 /* Debug */, 625 | 5A94F1CE21CCCA13003F92F4 /* Release */, 626 | ); 627 | defaultConfigurationIsVisible = 0; 628 | defaultConfigurationName = Release; 629 | }; 630 | 5A94F1CF21CCCA13003F92F4 /* Build configuration list for PBXNativeTarget "VDWebViewUITests" */ = { 631 | isa = XCConfigurationList; 632 | buildConfigurations = ( 633 | 5A94F1D021CCCA13003F92F4 /* Debug */, 634 | 5A94F1D121CCCA13003F92F4 /* Release */, 635 | ); 636 | defaultConfigurationIsVisible = 0; 637 | defaultConfigurationName = Release; 638 | }; 639 | /* End XCConfigurationList section */ 640 | }; 641 | rootObject = 5A94F19521CCCA11003F92F4 /* Project object */; 642 | } 643 | -------------------------------------------------------------------------------- /VDWebView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /VDWebView/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // VDWebView 4 | // 5 | // Created by volientDuan on 2018/12/21. 6 | // Copyright © 2018 volientDuan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /VDWebView/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // VDWebView 4 | // 5 | // Created by volientDuan on 2018/12/21. 6 | // Copyright © 2018 volientDuan. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | [UINavigationBar appearance].translucent = NO; 21 | return YES; 22 | } 23 | 24 | 25 | - (void)applicationWillResignActive:(UIApplication *)application { 26 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 27 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 28 | } 29 | 30 | 31 | - (void)applicationDidEnterBackground:(UIApplication *)application { 32 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 33 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 34 | } 35 | 36 | 37 | - (void)applicationWillEnterForeground:(UIApplication *)application { 38 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 39 | } 40 | 41 | 42 | - (void)applicationDidBecomeActive:(UIApplication *)application { 43 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 44 | } 45 | 46 | 47 | - (void)applicationWillTerminate:(UIApplication *)application { 48 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 49 | } 50 | 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /VDWebView/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /VDWebView/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /VDWebView/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /VDWebView/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /VDWebView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSAppTransportSecurity 24 | 25 | NSAllowsArbitraryLoads 26 | 27 | 28 | NSLocationWhenInUseUsageDescription 29 | 我们需要通过您的地理位置信息提供周边位置服务 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /VDWebView/Source/html/static/js/VDJSWebBridge.js: -------------------------------------------------------------------------------- 1 | 2 | // scheme为OC中桥对象绑定的值,确保一致,默认为vdjsbridge 3 | let vdScheme = "vdjsbridge" 4 | 5 | //调用原生方法:methodName 为方法名,params为参数值对象对应的json字符串 6 | function vd_jsBridge(methodName, params) { 7 | if (vd_isEmpty(methodName)) { 8 | return; 9 | } 10 | var url = vdScheme+"://"+methodName 11 | if (!vd_isEmpty(params)) { 12 | var b = new vd_Base64() 13 | var str = b.encode(params) 14 | url = url + "?params=" + str; 15 | } 16 | window.location.href = url 17 | } 18 | 19 | //判断字符是否为空的方法 20 | function vd_isEmpty(obj){ 21 | if(typeof obj == "undefined" || obj == null || obj == ""){ 22 | return true; 23 | }else{ 24 | return false; 25 | } 26 | } 27 | 28 | function vd_Base64() { 29 | 30 | // private property 31 | _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 32 | 33 | // public method for encoding 34 | this.encode = function (input) { 35 | var output = ""; 36 | var chr1, chr2, chr3, enc1, enc2, enc3, enc4; 37 | var i = 0; 38 | input = _utf8_encode(input); 39 | while (i < input.length) { 40 | chr1 = input.charCodeAt(i++); 41 | chr2 = input.charCodeAt(i++); 42 | chr3 = input.charCodeAt(i++); 43 | enc1 = chr1 >> 2; 44 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 45 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 46 | enc4 = chr3 & 63; 47 | if (isNaN(chr2)) { 48 | enc3 = enc4 = 64; 49 | } else if (isNaN(chr3)) { 50 | enc4 = 64; 51 | } 52 | output = output + 53 | _keyStr.charAt(enc1) + _keyStr.charAt(enc2) + 54 | _keyStr.charAt(enc3) + _keyStr.charAt(enc4); 55 | } 56 | return output; 57 | } 58 | 59 | // public method for decoding 60 | this.decode = function (input) { 61 | var output = ""; 62 | var chr1, chr2, chr3; 63 | var enc1, enc2, enc3, enc4; 64 | var i = 0; 65 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); 66 | while (i < input.length) { 67 | enc1 = _keyStr.indexOf(input.charAt(i++)); 68 | enc2 = _keyStr.indexOf(input.charAt(i++)); 69 | enc3 = _keyStr.indexOf(input.charAt(i++)); 70 | enc4 = _keyStr.indexOf(input.charAt(i++)); 71 | chr1 = (enc1 << 2) | (enc2 >> 4); 72 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 73 | chr3 = ((enc3 & 3) << 6) | enc4; 74 | output = output + String.fromCharCode(chr1); 75 | if (enc3 != 64) { 76 | output = output + String.fromCharCode(chr2); 77 | } 78 | if (enc4 != 64) { 79 | output = output + String.fromCharCode(chr3); 80 | } 81 | } 82 | output = _utf8_decode(output); 83 | return output; 84 | } 85 | 86 | // private method for UTF-8 encoding 87 | _utf8_encode = function (string) { 88 | string = string.replace(/\r\n/g,"\n"); 89 | var utftext = ""; 90 | for (var n = 0; n < string.length; n++) { 91 | var c = string.charCodeAt(n); 92 | if (c < 128) { 93 | utftext += String.fromCharCode(c); 94 | } else if((c > 127) && (c < 2048)) { 95 | utftext += String.fromCharCode((c >> 6) | 192); 96 | utftext += String.fromCharCode((c & 63) | 128); 97 | } else { 98 | utftext += String.fromCharCode((c >> 12) | 224); 99 | utftext += String.fromCharCode(((c >> 6) & 63) | 128); 100 | utftext += String.fromCharCode((c & 63) | 128); 101 | } 102 | 103 | } 104 | return utftext; 105 | } 106 | 107 | // private method for UTF-8 decoding 108 | _utf8_decode = function (utftext) { 109 | var string = ""; 110 | var i = 0; 111 | var c = c1 = c2 = 0; 112 | while ( i < utftext.length ) { 113 | c = utftext.charCodeAt(i); 114 | if (c < 128) { 115 | string += String.fromCharCode(c); 116 | i++; 117 | } else if((c > 191) && (c < 224)) { 118 | c2 = utftext.charCodeAt(i+1); 119 | string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); 120 | i += 2; 121 | } else { 122 | c2 = utftext.charCodeAt(i+1); 123 | c3 = utftext.charCodeAt(i+2); 124 | string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); 125 | i += 3; 126 | } 127 | } 128 | return string; 129 | } 130 | } -------------------------------------------------------------------------------- /VDWebView/Source/html/wkweb.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 测试WKWebView问题 4 | 5 | 6 | 7 | 8 | 9 |

返回上一页

10 |

更改导航title

11 |

reload页面

12 |

给我一封私信

13 |

JS弹框显示问题

14 |

alert显示

15 |

confirm显示

16 |

prompt显示

17 |

注入JS脚本

18 |

这是我注入的JS

19 |

Cookie

20 |

设置cookie

21 |

显示cookie

22 |

JSBridege测试

23 |

使用bridge调用OC方法

24 |

使用bridge调用JS方法

25 |

JS注入和拦截测试

26 |

JS注入和拦截测试

27 |

改变字体大小

28 |

大号

29 |

中号

30 |

小号

31 | 32 | 40 | 41 | 153 | 154 | -------------------------------------------------------------------------------- /VDWebView/VDWebView/VDWebView.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDWebView.h 3 | // Common 4 | // 5 | // Created by volientDuan on 2018/12/19. 6 | // Copyright © 2018 volientDuan. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | #import "VDWebViewProtocol.h" 13 | #import "VDWebViewJSBridge.h" 14 | 15 | @class VDWebView; 16 | @interface VDWebView : UIView 17 | 18 | #pragma mark - VD的基本属性和方法 19 | @property(nonatomic, assign) id delegate; 20 | 21 | #pragma mark - UI || WK 的API 22 | ///UI || WK 的API 23 | @property (nonatomic, readonly) UIScrollView *scrollView; 24 | 25 | - (id)loadRequest:(NSURLRequest *)request; 26 | - (id)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL; 27 | 28 | @property (nonatomic, readonly, copy) NSString *title; 29 | @property (nonatomic, readonly) NSURLRequest *currentRequest; 30 | @property (nonatomic, readonly) NSURL *URL; 31 | 32 | @property (nonatomic, readonly, getter=isLoading) BOOL loading; 33 | @property (nonatomic, readonly) BOOL canGoBack; 34 | @property (nonatomic, readonly) BOOL canGoForward; 35 | 36 | - (id)goBack; 37 | - (id)goForward; 38 | - (id)reload; 39 | - (id)reloadFromOrigin; 40 | - (void)stopLoading; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /VDWebView/VDWebView/VDWebView.m: -------------------------------------------------------------------------------- 1 | // 2 | // VDWebView.m 3 | // Common 4 | // 5 | // Created by volientDuan on 2018/12/19. 6 | // Copyright © 2018 volientDuan. All rights reserved. 7 | // 8 | #import "VDWebView.h" 9 | #import 10 | #import 11 | #import "VDWebViewScriptMessageHandler.h" 12 | 13 | @interface VDWebView()< WKNavigationDelegate, WKUIDelegate> 14 | 15 | @property (nonatomic, assign) CGFloat innerHeight; 16 | @property (nonatomic, assign) CGFloat estimatedProgress; 17 | @property (nonatomic, strong) NSURLRequest *originRequest; 18 | @property (nonatomic, strong) NSURLRequest *currentRequest; 19 | 20 | @property (nonatomic, copy) NSString *title; 21 | 22 | @property (nonatomic, strong) VDWebViewScriptMessageHandler *innerScriptMessageHandler; 23 | @property (nonatomic, weak) id scriptMessageHandler; 24 | 25 | @end 26 | 27 | 28 | @implementation VDWebView 29 | 30 | @synthesize realWebView = _realWebView; 31 | @synthesize scalesPageToFit = _scalesPageToFit; 32 | @synthesize httpCookiesDisable; 33 | @synthesize contentHeight; 34 | @synthesize enableAlert; 35 | @synthesize enablePrompt; 36 | @synthesize enableConfirm; 37 | @synthesize enableAllAlert; 38 | @synthesize isShowProgressBar; 39 | @synthesize progressBar = _progressBar; 40 | @synthesize bridge = _bridge; 41 | //@synthesize jsHandler; 42 | 43 | - (instancetype)initWithCoder:(NSCoder *)coder { 44 | self = [super initWithCoder:coder]; 45 | if (self) { 46 | 47 | [self _initMyself]; 48 | } 49 | return self; 50 | } 51 | 52 | - (void)encodeWithCoder:(nonnull NSCoder *)aCoder { 53 | [super encodeWithCoder:aCoder]; 54 | } 55 | 56 | 57 | - (instancetype)initWithFrame:(CGRect)frame { 58 | 59 | self = [super initWithFrame:frame]; 60 | if (self) { 61 | 62 | [self _initMyself]; 63 | } 64 | return self; 65 | 66 | } 67 | 68 | - (void)_initMyself { 69 | 70 | [self initWKWebView]; 71 | [self.realWebView setFrame:self.bounds]; 72 | [self.realWebView setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight]; 73 | [self addSubview:self.realWebView]; 74 | } 75 | - (void)initWKWebView { 76 | 77 | WKWebViewConfiguration* configuration = [[ WKWebViewConfiguration alloc] init]; 78 | configuration.preferences = [NSClassFromString(@"WKPreferences") new]; 79 | configuration.userContentController = [WKUserContentController new]; 80 | 81 | WKWebView* webView = [[WKWebView alloc] initWithFrame:self.bounds configuration:configuration]; 82 | webView.UIDelegate = self; 83 | webView.navigationDelegate = self; 84 | 85 | webView.backgroundColor = [UIColor clearColor]; 86 | webView.opaque = NO; 87 | 88 | [webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil]; 89 | [webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil]; 90 | _realWebView = webView; 91 | 92 | } 93 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 94 | 95 | if([keyPath isEqualToString:@"estimatedProgress"]) { 96 | 97 | self.estimatedProgress = [change[NSKeyValueChangeNewKey] floatValue]; 98 | // 判断是否显示进度条 99 | if (self.isShowProgressBar) { 100 | 101 | self.progressBar.frame = CGRectMake(0, 0, self.bounds.size.width*self.estimatedProgress, self.progressBar.bounds.size.height); 102 | if (self.estimatedProgress == 1) { 103 | 104 | self.progressBar.hidden = YES; 105 | } else { 106 | 107 | self.progressBar.hidden = NO; 108 | } 109 | } 110 | } 111 | if ([keyPath isEqualToString:@"title"]) { 112 | 113 | self.title = change[NSKeyValueChangeNewKey]; 114 | } 115 | } 116 | 117 | #pragma mark - 加载进度条 118 | - (UIView *)progressBar { 119 | if (!_progressBar) { 120 | _progressBar = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 0, 2)]; 121 | _progressBar.backgroundColor = UIColor.greenColor; 122 | [self addSubview:_progressBar]; 123 | } 124 | return _progressBar; 125 | } 126 | 127 | #pragma mark- WKNavigationDelegate 128 | 129 | - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { 130 | 131 | BOOL resultBOOL = [self callback_webViewShouldStartLoadWithRequest:navigationAction.request navigationType:navigationAction.navigationType]; 132 | if (resultBOOL) { 133 | 134 | self.currentRequest = navigationAction.request; 135 | if (navigationAction.targetFrame == nil) { 136 | 137 | [webView loadRequest:navigationAction.request]; 138 | } 139 | decisionHandler(WKNavigationActionPolicyAllow); 140 | } else { 141 | 142 | decisionHandler(WKNavigationActionPolicyCancel); 143 | } 144 | } 145 | - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler { 146 | 147 | if (!self.httpCookiesDisable) { 148 | // 同步NSHTTPCookieStorage中的cookie到WKWebView中,有可能会污染WKWebView中的cookie管理 149 | // WKWebView有自己的cookie管理 150 | NSArray *cookies = [NSHTTPCookieStorage sharedHTTPCookieStorage].cookies; 151 | for (NSHTTPCookie *cookie in cookies) { 152 | if ([navigationResponse.response.URL.host containsString:cookie.domain]) { 153 | [self setCookieWithKey:cookie.name value:cookie.value expires:[cookie.expiresDate timeIntervalSinceNow] domain:cookie.domain]; 154 | } 155 | } 156 | } 157 | decisionHandler(WKNavigationResponsePolicyAllow); 158 | } 159 | - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation { 160 | 161 | [self callback_webViewDidStartLoad]; 162 | } 163 | - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { 164 | [webView evaluateJavaScript:@"document.body.offsetHeight" completionHandler:^(id data, NSError *error) { 165 | self.innerHeight = [data floatValue]; 166 | if ([self.delegate respondsToSelector:@selector(webView:innerHeight:)]) { 167 | [self.delegate webView:self innerHeight:self.innerHeight]; 168 | } 169 | }]; 170 | [self callback_webViewDidFinishLoad]; 171 | } 172 | - (void)webView:(WKWebView *) webView didFailProvisionalNavigation: (WKNavigation *) navigation withError: (NSError *) error { 173 | 174 | [self callback_webViewDidFailLoadWithError:error]; 175 | } 176 | - (void)webView: (WKWebView *)webView didFailNavigation:(WKNavigation *) navigation withError: (NSError *) error { 177 | 178 | [self callback_webViewDidFailLoadWithError:error]; 179 | } 180 | 181 | #pragma mark- WKUIDelegate 182 | - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler { 183 | 184 | if (self.enableAlert||self.enableAllAlert) { 185 | 186 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:message message:nil preferredStyle:UIAlertControllerStyleAlert]; 187 | [alertController addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { 188 | 189 | completionHandler(); 190 | }]]; 191 | [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil]; 192 | } else { 193 | 194 | if ([self.delegate respondsToSelector:@selector(webView:showAlertWithType:title:content:completionHandler:)]) { 195 | 196 | [self.delegate webView:self showAlertWithType:VDJSAlertTypeAlert title:message content:nil completionHandler:^(id data) { 197 | completionHandler(); 198 | }]; 199 | } 200 | } 201 | } 202 | - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler { 203 | 204 | if (self.enableConfirm||self.enableAllAlert) { 205 | 206 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert]; 207 | [alertController addAction:([UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { 208 | completionHandler(NO); 209 | }])]; 210 | [alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 211 | completionHandler(YES); 212 | }])]; 213 | [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil]; 214 | } else { 215 | 216 | if ([self.delegate respondsToSelector:@selector(webView:showAlertWithType:title:content:completionHandler:)]) { 217 | 218 | [self.delegate webView:self showAlertWithType:VDJSAlertTypeConfirm title:@"提示" content:message completionHandler:^(id data) { 219 | completionHandler([data boolValue]); 220 | }]; 221 | } 222 | } 223 | 224 | } 225 | - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler { 226 | 227 | if (self.enablePrompt||self.enableAllAlert) { 228 | 229 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:@"" preferredStyle:UIAlertControllerStyleAlert]; 230 | [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) { 231 | textField.text = defaultText; 232 | }]; 233 | [alertController addAction:([UIAlertAction actionWithTitle:@"完成" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 234 | completionHandler(alertController.textFields[0].text?:@""); 235 | }])]; 236 | 237 | [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil]; 238 | } else { 239 | 240 | if ([self.delegate respondsToSelector:@selector(webView:showAlertWithType:title:content:completionHandler:)]) { 241 | 242 | [self.delegate webView:self showAlertWithType:VDJSAlertTypePrompt title:prompt content:defaultText completionHandler:^(id data) { 243 | 244 | completionHandler(data == nil ? @"":[data stringValue]); 245 | }]; 246 | } 247 | } 248 | } 249 | #pragma mark- CALLBACK WebView Delegate 250 | 251 | - (void)callback_webViewDidFinishLoad { 252 | 253 | if([self.delegate respondsToSelector:@selector(webViewDidFinishLoad:)]) { 254 | 255 | [self.delegate webViewDidFinishLoad:self]; 256 | } 257 | } 258 | - (void)callback_webViewDidStartLoad { 259 | 260 | if([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) { 261 | 262 | [self.delegate webViewDidStartLoad:self]; 263 | } 264 | } 265 | - (void)callback_webViewDidFailLoadWithError:(NSError *)error { 266 | 267 | if ([self.delegate respondsToSelector:@selector(webView:didFailLoadWithError:)]) { 268 | 269 | [self.delegate webView:self didFailLoadWithError:error]; 270 | } 271 | } 272 | -(BOOL)callback_webViewShouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(NSInteger)navigationType { 273 | 274 | BOOL resultBOOL = YES; 275 | if ([self.delegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) { 276 | 277 | if (navigationType == -1) { 278 | 279 | navigationType = UIWebViewNavigationTypeOther; 280 | } 281 | resultBOOL = [self.delegate webView:self shouldStartLoadWithRequest:request navigationType:navigationType]; 282 | } 283 | return resultBOOL; 284 | } 285 | 286 | #pragma mark - VDWebViewScriptProtocol 287 | - (VDWebViewScriptMessageHandler *)innerScriptMessageHandler { 288 | 289 | if (!_innerScriptMessageHandler) { 290 | _innerScriptMessageHandler = [[VDWebViewScriptMessageHandler alloc]initWithTarget:self selector:@selector(didReceiveScriptMessage:)]; 291 | } 292 | return _innerScriptMessageHandler; 293 | } 294 | 295 | /** 296 | * 添加js回调oc通知方式,适用于 iOS8 之后 297 | */ 298 | - (void)addScriptMessageHandler:(id)scriptMessageHandler name:(NSString *)name { 299 | 300 | // 先只允许添加一个hander对象 301 | self.scriptMessageHandler = scriptMessageHandler; 302 | __block BOOL isHaved = NO; 303 | [self.innerScriptMessageHandler.scriptMessageNames enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 304 | 305 | if ([obj isEqualToString:name]) { 306 | isHaved = YES; 307 | } 308 | }]; 309 | if (!isHaved) { 310 | 311 | [self.innerScriptMessageHandler.scriptMessageNames addObject:name]; 312 | [self.realWebView.configuration.userContentController addScriptMessageHandler:self.innerScriptMessageHandler name:name]; 313 | } 314 | } 315 | 316 | /** 317 | * 注销 注册过的js回调oc通知方式,适用于 iOS8 之后 318 | */ 319 | - (void)removeScriptMessageHandlerForName:(NSString *)name { 320 | 321 | [_realWebView.configuration.userContentController removeScriptMessageHandlerForName:name]; 322 | } 323 | 324 | /** 325 | * 注销 注册过的js回调oc通知方式,适用于 iOS8 之后 326 | */ 327 | - (void)removeScriptMessageHandler { 328 | if (!_innerScriptMessageHandler) { 329 | return; 330 | } 331 | for (NSString *name in self.innerScriptMessageHandler.scriptMessageNames) { 332 | 333 | [self removeScriptMessageHandlerForName:name]; 334 | } 335 | } 336 | - (void)didReceiveScriptMessage:(WKScriptMessage *)message { 337 | 338 | if ((![self.delegate isKindOfClass:[VDWebViewJSBridge class]] && [self.delegate respondsToSelector:@selector(webView:didReceiveScriptMessage:)]) || ([self.delegate isKindOfClass:[VDWebViewJSBridge class]] && ((VDWebViewJSBridge *)self.delegate).didReceiveScriptMessage)) { 339 | 340 | [self.delegate webView:self didReceiveScriptMessage:message]; 341 | }else { 342 | 343 | SEL sel = NSSelectorFromString(message.name); 344 | NSMethodSignature *sign = [self.scriptMessageHandler methodSignatureForSelector:sel]; 345 | BOOL isHavedParam = NO; 346 | if (!sign) { 347 | isHavedParam = YES; 348 | sel = NSSelectorFromString([NSString stringWithFormat:@"%@:",message.name]); 349 | sign = [self.scriptMessageHandler methodSignatureForSelector:sel]; 350 | } 351 | if (sign) { 352 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sign]; 353 | [invocation setTarget:self.scriptMessageHandler]; 354 | [invocation setSelector:sel]; 355 | if (isHavedParam) { 356 | id param = message.body; 357 | [invocation setArgument:&(param) atIndex:2]; 358 | } 359 | [invocation invoke]; 360 | }else { 361 | NSLog(@"未定义方法: %@或%@:\nwaring:最多支持一个参数的自定义方法",message.name,message.name 362 | ); 363 | } 364 | } 365 | } 366 | 367 | - (void)addUserScriptWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)mainFrameOnly { 368 | 369 | WKUserScript *script = [[WKUserScript alloc]initWithSource:source injectionTime:injectionTime forMainFrameOnly:mainFrameOnly]; 370 | [self.realWebView.configuration.userContentController addUserScript:script]; 371 | } 372 | 373 | - (void)removeAllUserScripts { 374 | 375 | [self.realWebView.configuration.userContentController removeAllUserScripts]; 376 | } 377 | 378 | #pragma mark- 基础方法 379 | - (UIScrollView *)scrollView { 380 | 381 | return [(id)self.realWebView scrollView]; 382 | } 383 | 384 | - (id)loadRequest:(NSURLRequest *)request { 385 | 386 | self.originRequest = request; 387 | self.currentRequest = request; 388 | return [(WKWebView*)self.realWebView loadRequest:request]; 389 | } 390 | - (id)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL { 391 | 392 | return [(WKWebView*)self.realWebView loadHTMLString:string baseURL:baseURL]; 393 | } 394 | - (NSURLRequest *)currentRequest { 395 | 396 | return _currentRequest; 397 | } 398 | - (NSURL *)URL { 399 | 400 | return [(WKWebView*)self.realWebView URL]; 401 | } 402 | - (BOOL)isLoading { 403 | 404 | return [self.realWebView isLoading]; 405 | } 406 | - (BOOL)canGoBack { 407 | 408 | return [self.realWebView canGoBack]; 409 | } 410 | - (BOOL)canGoForward { 411 | 412 | return [self.realWebView canGoForward]; 413 | } 414 | - (id)goBack { 415 | 416 | return [(WKWebView*)self.realWebView goBack]; 417 | } 418 | - (id)goForward { 419 | 420 | return [(WKWebView*)self.realWebView goForward]; 421 | } 422 | - (id)reload { 423 | 424 | return [(WKWebView*)self.realWebView reload]; 425 | } 426 | - (id)reloadFromOrigin { 427 | 428 | return [(WKWebView*)self.realWebView reloadFromOrigin]; 429 | } 430 | - (void)stopLoading { 431 | 432 | [self.realWebView stopLoading]; 433 | } 434 | 435 | - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *))completionHandler { 436 | 437 | return [(WKWebView*)self.realWebView evaluateJavaScript:javaScriptString completionHandler:completionHandler]; 438 | } 439 | 440 | - (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)javaScriptString { 441 | 442 | __block NSString* result = nil; 443 | __block BOOL isExecuted = NO; 444 | [(WKWebView*)self.realWebView evaluateJavaScript:javaScriptString completionHandler:^(id obj, NSError *error) { 445 | 446 | result = obj; 447 | isExecuted = YES; 448 | }]; 449 | 450 | while (isExecuted == NO) { 451 | 452 | [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 453 | } 454 | return result; 455 | } 456 | 457 | - (void)setScalesPageToFit:(BOOL)scalesPageToFit { 458 | 459 | if (_scalesPageToFit == scalesPageToFit) { 460 | 461 | return; 462 | } 463 | WKWebView* webView = _realWebView; 464 | 465 | NSString *jScript = @"var meta = document.createElement('meta'); \ 466 | meta.name = 'viewport'; \ 467 | meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'; \ 468 | var head = document.getElementsByTagName('head')[0];\ 469 | head.appendChild(meta);"; 470 | 471 | if (scalesPageToFit) { 472 | 473 | WKUserScript *wkUScript = [[NSClassFromString(@"WKUserScript") alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:NO]; 474 | [webView.configuration.userContentController addUserScript:wkUScript]; 475 | } else { 476 | 477 | NSMutableArray* array = [NSMutableArray arrayWithArray:webView.configuration.userContentController.userScripts]; 478 | for (WKUserScript *wkUScript in array) { 479 | 480 | if ([wkUScript.source isEqual:jScript]) { 481 | 482 | [array removeObject:wkUScript]; 483 | break; 484 | } 485 | } 486 | for (WKUserScript *wkUScript in array) { 487 | 488 | [webView.configuration.userContentController addUserScript:wkUScript]; 489 | } 490 | } 491 | 492 | _scalesPageToFit = scalesPageToFit; 493 | } 494 | - (BOOL)scalesPageToFit { 495 | 496 | return _scalesPageToFit; 497 | } 498 | 499 | - (NSInteger)countOfHistory { 500 | 501 | WKWebView* webView = self.realWebView; 502 | return webView.backForwardList.backList.count; 503 | } 504 | - (void)gobackWithStep:(NSInteger)step { 505 | if (self.canGoBack == NO) { 506 | return; 507 | } 508 | if (step > 0) { 509 | NSInteger historyCount = self.countOfHistory; 510 | if (step >= historyCount) { 511 | step = historyCount - 1; 512 | } 513 | WKWebView* webView = self.realWebView; 514 | WKBackForwardListItem* backItem = webView.backForwardList.backList[step]; 515 | [webView goToBackForwardListItem:backItem]; 516 | } else { 517 | [self goBack]; 518 | } 519 | } 520 | 521 | - (VDWebViewJSBridge *)bridgeInitialized { 522 | 523 | _bridge = [[VDWebViewJSBridge alloc]initWithWebView:self delegate:self.delegate]; 524 | return _bridge; 525 | } 526 | 527 | - (void)setDelegate:(id)delegate { 528 | 529 | if (_bridge) { 530 | _bridge.delegate = delegate; 531 | } else { 532 | _delegate = delegate; 533 | } 534 | } 535 | 536 | - (CGFloat)contentHeight { 537 | return self.innerHeight; 538 | } 539 | 540 | 541 | #pragma mark- 如果没有找到方法 去realWebView 中调用 542 | - (BOOL)respondsToSelector:(SEL)aSelector { 543 | 544 | BOOL hasResponds = [super respondsToSelector:aSelector]; 545 | if (hasResponds == NO) { 546 | 547 | hasResponds = [self.delegate respondsToSelector:aSelector]; 548 | } 549 | 550 | if (hasResponds == NO) { 551 | 552 | hasResponds = [self.realWebView respondsToSelector:aSelector]; 553 | } 554 | return hasResponds; 555 | } 556 | 557 | - (NSMethodSignature*)methodSignatureForSelector:(SEL)selector { 558 | 559 | NSMethodSignature* methodSign = [super methodSignatureForSelector:selector]; 560 | if (methodSign == nil) { 561 | if ([self.realWebView respondsToSelector:selector]) { 562 | 563 | methodSign = [self.realWebView methodSignatureForSelector:selector]; 564 | } else { 565 | 566 | methodSign = [(id)self.delegate methodSignatureForSelector:selector]; 567 | } 568 | } 569 | return methodSign; 570 | } 571 | 572 | - (void)forwardInvocation:(NSInvocation*)invocation { 573 | 574 | if ([self.realWebView respondsToSelector:invocation.selector]) { 575 | 576 | [invocation invokeWithTarget:self.realWebView]; 577 | } else { 578 | 579 | [invocation invokeWithTarget:self.delegate]; 580 | } 581 | } 582 | 583 | 584 | #pragma mark - VDWebViewCookiesProtocol 585 | 586 | - (void)setCookieWithKey:(NSString *)key value:(NSString *)value expires:(NSTimeInterval)expires domain:(NSString *)domain { 587 | // 其实就是通过注入设置cookie的js代码并在load前执行(iOS11以上也可通过WKHTTPCookieStore进行cookie的设置) 588 | NSString *jsSource = [NSString stringWithFormat:@"document.cookie = \"%@=%@",key,value]; 589 | if (expires > 0) { 590 | jsSource = [NSString stringWithFormat:@"%@;expires=%lf",jsSource,expires*1000]; 591 | } 592 | if (domain) { 593 | jsSource = [NSString stringWithFormat:@"%@;domain=%@",jsSource,domain]; 594 | } 595 | jsSource = [NSString stringWithFormat:@"%@;domain=%@\"",jsSource,domain]; 596 | [self addUserScriptWithSource:jsSource injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]; 597 | 598 | } 599 | 600 | - (NSArray *)getCookies { 601 | __block NSArray *cookieArr; 602 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 603 | if (@available(iOS 11.0, *)) { 604 | __block BOOL isExecuted = NO; 605 | [self.realWebView.configuration.websiteDataStore.httpCookieStore getAllCookies:^(NSArray * cookies) { 606 | cookieArr = cookies; 607 | isExecuted = YES; 608 | }]; 609 | while (isExecuted == NO) { 610 | [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 611 | } 612 | } 613 | #else 614 | 615 | #endif 616 | return cookieArr; 617 | } 618 | 619 | - (void)setCookies:(NSString *)cookies { 620 | NSString *str = [cookies stringByReplacingOccurrencesOfString:@" " withString:@""]; 621 | str = [str stringByReplacingOccurrencesOfString:@"Domain=" withString:@"domain="]; 622 | str = [str stringByReplacingOccurrencesOfString:@"Max-Age=" withString:@"expires="]; 623 | str = [str stringByReplacingOccurrencesOfString:@"Path=" withString:@"path="]; 624 | NSArray *array = [str componentsSeparatedByString:@";"]; 625 | if (array.count > 0) { 626 | NSString *key,*value,*domain,*expires,*path; 627 | for (NSString *itemStr in array) { 628 | NSArray *items = [itemStr componentsSeparatedByString:@"="]; 629 | if (items.count == 2) { 630 | if ([items[0] isEqualToString:@"domain"]) { 631 | domain = items[1]; 632 | }else if ([items[0] isEqualToString:@"expires"]) { 633 | expires = items[1]; 634 | }else if ([items[0] isEqualToString:@"path"]) { 635 | path = items[1]; 636 | }else { 637 | if (key) { 638 | [self setCookieWithKey:key value:value expires:-1 domain:domain]; 639 | } 640 | key = items[0]; 641 | value = items[1]; 642 | domain = nil; 643 | } 644 | } 645 | } 646 | if (key) { 647 | [self setCookieWithKey:key value:value expires:-1 domain:domain]; 648 | } 649 | } 650 | } 651 | 652 | #pragma mark- 清理 653 | -(void)dealloc { 654 | WKWebView* webView = _realWebView; 655 | webView.UIDelegate = nil; 656 | webView.navigationDelegate = nil; 657 | 658 | [webView removeObserver:self forKeyPath:@"estimatedProgress"]; 659 | [webView removeObserver:self forKeyPath:@"title"]; 660 | 661 | // 如果添加JS调用OC的监听 dealloc 一定要移除所有 否则handler将无法释放 662 | [self removeScriptMessageHandler]; 663 | [self removeAllUserScripts]; 664 | [_realWebView scrollView].delegate = nil; 665 | [_realWebView stopLoading]; 666 | [_realWebView removeFromSuperview]; 667 | _realWebView = nil; 668 | 669 | 670 | } 671 | 672 | @end 673 | -------------------------------------------------------------------------------- /VDWebView/VDWebView/VDWebViewJSBridge.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDWebViewJSBridge.h 3 | // VDWebView 4 | // 5 | // Created by volientDuan on 2019/1/11. 6 | // Copyright © 2019 volientDuan. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "VDWebView.h" 11 | 12 | /** 13 | js与原生交互的桥; 14 | 调用原生方法的原理:通过拦截请求,对URL进行解析获取方法名和参数值 15 | 调用js方法原理:使用的是VDWebView的evaluateJavaScript方法即WKWebView所提供的方法 (iOS8.0以后) 16 | */ 17 | @interface VDWebViewJSBridge : NSObject 18 | @property (nonatomic, strong)NSString *scheme; 19 | @property (nonatomic, weak)VDWebView *webView; 20 | @property (nonatomic, weak)id delegate; 21 | @property (nonatomic, assign)BOOL didReceiveScriptMessage; 22 | /** 23 | 初始化桥,在webView的代理对象绑定后初始化桥,否则会造成桥无法搭建成功 24 | 25 | @param webView webView 26 | @param delegate 代理对象 27 | @return bridge 28 | */ 29 | - (instancetype)initWithWebView:(VDWebView *)webView delegate:(id)delegate; 30 | 31 | - (void)executeJsMethod:(NSString *)method params:(NSArray *)params completionHandler:(void (^)(id result, NSError *error))completionHandler; 32 | 33 | /** 34 | 绑定方法 - (调用此方法后将只监听绑定的方法) 35 | 36 | @param method 方法名 37 | */ 38 | - (void)bindMethod:(NSString *)method; 39 | 40 | /** 41 | 移除方法 42 | 43 | @param method 方法名 44 | */ 45 | - (void)removeMethod:(NSString *)method; 46 | 47 | /** 48 | 移除所有方法 (默认监听所有方法,只需要在代理对象中实现对应的OC方法) 49 | */ 50 | - (void)removeAllMethod; 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /VDWebView/VDWebView/VDWebViewJSBridge.m: -------------------------------------------------------------------------------- 1 | // 2 | // VDWebViewJSBridge.m 3 | // VDWebView 4 | // 5 | // Created by volientDuan on 2019/1/11. 6 | // Copyright © 2019 volientDuan. All rights reserved. 7 | // 8 | 9 | #import "VDWebViewJSBridge.h" 10 | #import 11 | #import 12 | 13 | #define VDWebViewJSBridgeScheme @"vdjsbridge" 14 | 15 | @interface VDURLParse : NSObject 16 | @property (nonatomic, strong)NSString *method; 17 | @property (nonatomic, strong)id params; 18 | - (instancetype)initWithUrl:(NSURL *)url; 19 | 20 | @end 21 | @implementation VDURLParse 22 | - (instancetype)initWithUrl:(NSURL *)url { 23 | 24 | self = [super init]; 25 | if (self) { 26 | [self parseUrl:url]; 27 | } 28 | return self; 29 | } 30 | 31 | - (void)parseUrl:(NSURL *)url { 32 | self.method = url.host; 33 | NSURLComponents *urlComponents = [[NSURLComponents alloc] initWithString:url.absoluteString]; 34 | //回调遍历所有参数,添加入字典 35 | __block id value = nil; 36 | [urlComponents.queryItems enumerateObjectsUsingBlock:^(NSURLQueryItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 37 | if ([obj.name isEqualToString:@"params"]) { 38 | value = obj.value; 39 | *stop = YES; 40 | } 41 | }]; 42 | if (value) { 43 | // base64解码 44 | NSData *data = [[NSData alloc]initWithBase64EncodedString:value options:NSDataBase64DecodingIgnoreUnknownCharacters]; 45 | NSString *string = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; 46 | self.params = [self paramsWithJsonString:string]; 47 | } 48 | } 49 | 50 | - (id)paramsWithJsonString:(NSString *)jsonString { 51 | 52 | if (jsonString == nil) { 53 | return nil; 54 | } 55 | NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; 56 | NSError *err; 57 | id obj = [NSJSONSerialization JSONObjectWithData:jsonData 58 | options:NSJSONReadingMutableContainers error:&err]; 59 | 60 | if (err) { 61 | 62 | return jsonString; 63 | } 64 | return obj; 65 | } 66 | 67 | @end 68 | 69 | @interface VDWebViewJSBridge() 70 | @property (nonatomic, strong)NSMutableArray *methods; 71 | 72 | @end 73 | @implementation VDWebViewJSBridge 74 | - (NSString *)scheme { 75 | 76 | if (!_scheme) { 77 | _scheme = VDWebViewJSBridgeScheme; 78 | } 79 | return _scheme; 80 | } 81 | 82 | - (NSMutableArray *)methods { 83 | 84 | if (!_methods) { 85 | _methods = [NSMutableArray array]; 86 | } 87 | return _methods; 88 | } 89 | - (instancetype)initWithWebView:(VDWebView *)webView delegate:(id)delegate { 90 | 91 | self = [super init]; 92 | if (self) { 93 | self.webView = webView; 94 | self.delegate = delegate; 95 | self.didReceiveScriptMessage = [delegate respondsToSelector:@selector(webView:didReceiveScriptMessage:)]; 96 | self.webView.delegate = nil; 97 | self.webView.delegate = self; 98 | } 99 | return self; 100 | } 101 | 102 | - (void)executeJsMethod:(NSString *)method params:(NSArray *)params completionHandler:(void (^)(id, NSError *))completionHandler{ 103 | NSMutableString *js = [[NSMutableString alloc]initWithString:method]; 104 | NSInteger idx = 0; 105 | for (NSString *p in params) { 106 | if (idx == 0) { 107 | 108 | [js appendString:@"("]; 109 | } else { 110 | 111 | [js appendString:@", "]; 112 | } 113 | [js appendString:@"\""]; 114 | [js appendString:p]; 115 | [js appendString:@"\""]; 116 | idx ++; 117 | } 118 | [js appendString:@")"]; 119 | [self.webView evaluateJavaScript:js completionHandler:completionHandler]; 120 | } 121 | 122 | - (void)bindMethod:(NSString *)method { 123 | 124 | [self.methods addObject:method]; 125 | 126 | } 127 | 128 | - (void)removeMethod:(NSString *)method { 129 | [self.methods enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 130 | if ([obj isEqualToString:method]) { 131 | [self.methods removeObjectAtIndex:idx]; 132 | *stop = YES; 133 | } 134 | }]; 135 | } 136 | 137 | - (void)removeAllMethod { 138 | [self.methods removeAllObjects]; 139 | } 140 | 141 | - (void)executeMethodWithUrl:(NSURL *)url { 142 | if (self.methods.count) { 143 | __block BOOL isHaved = NO; 144 | [self.methods enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 145 | if ([obj isEqualToString:url.host]) { 146 | isHaved = YES; 147 | *stop = YES; 148 | } 149 | }]; 150 | if (isHaved == NO) { 151 | return; 152 | } 153 | } 154 | VDURLParse *parse = [[VDURLParse alloc]initWithUrl:url]; 155 | // 调用OC方法 156 | SEL sel = NSSelectorFromString(parse.method); 157 | NSMethodSignature *sign = [(id)self.delegate methodSignatureForSelector:sel]; 158 | BOOL isHavedParam = NO; 159 | if (!sign) { 160 | isHavedParam = YES; 161 | sel = NSSelectorFromString([NSString stringWithFormat:@"%@:",parse.method]); 162 | sign = [(id)self.delegate methodSignatureForSelector:sel]; 163 | } 164 | if (sign) { 165 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sign]; 166 | [invocation setTarget:self.delegate]; 167 | [invocation setSelector:sel]; 168 | if (isHavedParam) { 169 | id param = parse.params; 170 | [invocation setArgument:&(param) atIndex:2]; 171 | } 172 | [invocation invoke]; 173 | }else { 174 | NSLog(@"未定义方法: %@或%@:\nwaring:最多支持一个参数的自定义方法",parse.method,parse.method); 175 | } 176 | } 177 | 178 | #pragma mark - VDWebViewDelegate 179 | 180 | - (void)webViewDidStartLoad:(VDWebView *)webView { 181 | 182 | if ([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) { 183 | [self.delegate webViewDidStartLoad:webView]; 184 | } 185 | } 186 | 187 | - (void)webViewDidFinishLoad:(VDWebView *)webView { 188 | 189 | if ([self.delegate respondsToSelector:@selector(webViewDidFinishLoad:)]) { 190 | [self.delegate webViewDidFinishLoad:webView]; 191 | } 192 | } 193 | 194 | - (void)webView:(VDWebView *)webView didFailLoadWithError:(NSError *)error { 195 | 196 | if ([self.delegate respondsToSelector:@selector(webView:didFailLoadWithError:)]) { 197 | [self.delegate webView:webView didFailLoadWithError:error]; 198 | } 199 | } 200 | - (BOOL)webView:(VDWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { 201 | 202 | if ([request.URL.scheme isEqualToString:self.scheme]) { 203 | [self executeMethodWithUrl:request.URL]; 204 | return NO; 205 | } else { 206 | if ([self.delegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) { 207 | return [self.delegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType]; 208 | } 209 | } 210 | return YES; 211 | } 212 | 213 | - (void)webView:(VDWebView *)webView didReceiveScriptMessage:(WKScriptMessage *)message { 214 | 215 | if ([self.delegate respondsToSelector:@selector(webView:didReceiveScriptMessage:)]) { 216 | [self.delegate webView:webView didReceiveScriptMessage:message]; 217 | } 218 | } 219 | 220 | - (void)webView:(VDWebView *)webView showAlertWithType:(VDJSAlertType)type title:(NSString *)title content:(NSString *)content completionHandler:(void (^)(id))completionHandler { 221 | 222 | if ([self.delegate respondsToSelector:@selector(webView:showAlertWithType:title:content:completionHandler:)]) { 223 | [self.delegate webView:webView showAlertWithType:type title:title content:content completionHandler:completionHandler]; 224 | } 225 | } 226 | 227 | - (void)webView:(VDWebView *)webView innerHeight:(CGFloat)height { 228 | if ([self.delegate respondsToSelector:@selector(webView:innerHeight:)]) { 229 | [self.delegate webView:self innerHeight:height]; 230 | } 231 | } 232 | 233 | @end 234 | -------------------------------------------------------------------------------- /VDWebView/VDWebView/VDWebViewProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDWebViewProtocol.h 3 | // VDWebView 4 | // 5 | // Created by volientDuan on 2019/1/11. 6 | // Copyright © 2019 volientDuan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSInteger, VDJSAlertType) { 12 | VDJSAlertTypeAlert = 0, 13 | VDJSAlertTypeConfirm, 14 | VDJSAlertTypePrompt 15 | }; 16 | 17 | @class VDWebView; 18 | @class VDWebViewJSBridge; 19 | @class VDWebViewScriptMessageHandler; 20 | /** 21 | 常用方法和属性 22 | */ 23 | @protocol VDWebViewProtocol 24 | ///内部使用的webView 25 | @property (nonatomic, readonly) WKWebView *realWebView; 26 | @property (nonatomic, readonly) NSURLRequest *originRequest; 27 | ///预估网页加载进度 28 | @property (nonatomic, readonly) CGFloat estimatedProgress; 29 | // 是否显示进度条 默认不显示 30 | @property (nonatomic, assign) BOOL isShowProgressBar; 31 | ///进度条 32 | @property (nonatomic, strong) UIView *progressBar; 33 | ///back 层数 34 | - (NSInteger)countOfHistory; 35 | - (void)gobackWithStep:(NSInteger)step; 36 | ///是否根据视图大小来缩放页面 默认为NO 37 | @property (nonatomic) BOOL scalesPageToFit; 38 | /// web页面加载完毕后的内容高度(在页面加载完成后获取) 39 | @property (nonatomic, readonly) CGFloat contentHeight; 40 | /// 是否启用js调用原生弹框 默认为NO 禁止(默认加载弹框在根试图) 41 | @property (nonatomic, assign) BOOL enableAllAlert; 42 | @property (nonatomic, assign) BOOL enableAlert; 43 | @property (nonatomic, assign) BOOL enableConfirm; 44 | @property (nonatomic, assign) BOOL enablePrompt; 45 | 46 | ///// JS调用OC方法的的接受对象 47 | //@property (nonatomic, weak) id jsHandler; 48 | 49 | - (VDWebViewJSBridge *)bridgeInitialized; 50 | @property (nonatomic, strong, readonly) VDWebViewJSBridge *bridge; 51 | 52 | @end 53 | 54 | /** 55 | script相关方法 56 | */ 57 | @protocol VDWebViewScriptProtocol 58 | /** 59 | * 添加js回调oc通知方式 60 | * 关于js调用说明:JS通过window.webkit.messageHandlers..postMessage(<参数>) 调用OC方法 其中方法名和参数为必填项 61 | * 关于回调的说明:如果实现了相应的代理方法(webView:didReceiveScriptMessage:)则走代理,否则会通过target-action调用同名的OC方法(定义的方法保证同名,参数最多为1个,参数类型可以和前端进行约束) 62 | */ 63 | - (void)addScriptMessageHandler:(id)scriptMessageHandler name:(NSString *)name; 64 | /** 65 | * 注销 注册过的js回调oc通知方式 66 | */ 67 | - (void)removeScriptMessageHandlerForName:(NSString *)name; 68 | 69 | - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id result, NSError *error))completionHandler; 70 | 71 | ///不建议使用这个办法 因为会在内部等待webView 的执行结果 72 | - (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)javaScriptString; 73 | 74 | /** 75 | 注入脚本,注意避免重复注入(js...) 76 | 77 | @param source 注入的内容 78 | @param injectionTime 注入时间 79 | @param mainFrameOnly 只作用主框架 80 | */ 81 | - (void)addUserScriptWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)mainFrameOnly; 82 | /** 83 | 移除所有注入的脚本 84 | */ 85 | - (void)removeAllUserScripts; 86 | 87 | @end 88 | 89 | /** 90 | Cookie相关 91 | */ 92 | @protocol VDWebViewCookiesProtocol 93 | /** 94 | 不同步NSHTTPCookieStorage存储的cookies 默认同步:NO 95 | 同步NSHTTPCookieStorage中的cookie到WKWebView中,有可能会污染WKWebView中的cookie管理 96 | */ 97 | @property (nonatomic, assign)BOOL httpCookiesDisable; 98 | /** 99 | 设置cookie 100 | 101 | @param key 键 102 | @param value 值 103 | @param expires 有效时间单位为秒:当值小于等于0时为临时cookie 104 | @param domain 域名 105 | */ 106 | - (void)setCookieWithKey:(NSString *)key value:(NSString *)value expires:(NSTimeInterval)expires domain:(NSString *)domain; 107 | - (void)setCookies:(NSString *)cookies; 108 | - (NSArray *)getCookies; 109 | 110 | @end 111 | 112 | 113 | /** 114 | 代理方法 115 | */ 116 | @protocol VDWebViewDelegate 117 | @optional 118 | /// 类UIWebView代理方法 119 | - (void)webViewDidStartLoad:(VDWebView *)webView; 120 | - (void)webViewDidFinishLoad:(VDWebView *)webView; 121 | - (void)webView:(VDWebView *)webView didFailLoadWithError:(NSError *)error; 122 | - (BOOL)webView:(VDWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType; 123 | /** 124 | JS调原生代理方法(注册了过的方法将全部通过此方法回调) 125 | 126 | @param webView VDWebView 127 | @param message 回调消息 128 | */ 129 | - (void)webView:(VDWebView *)webView didReceiveScriptMessage:(WKScriptMessage *)message; 130 | 131 | /** 132 | JS弹框拦截方法--如果需要自定义弹框建议声明此方法(前提条件:不开启默认系统弹框) 133 | 134 | @param webView VDWebView 135 | @param type 弹框类型 136 | @param title 标题 137 | @param content 内容 138 | @param completionHandler 结果处理必须执行completionHandler(data) 139 | */ 140 | - (void)webView:(VDWebView *)webView showAlertWithType:(VDJSAlertType)type title:(NSString *)title content:(NSString *)content completionHandler:(void (^)(id))completionHandler; 141 | 142 | /** 143 | webView 高度获取 144 | 145 | @param webView VDWebView 146 | @param height 内容高度 147 | */ 148 | - (void)webView:(VDWebView *)webView innerHeight:(CGFloat)height; 149 | 150 | @end 151 | -------------------------------------------------------------------------------- /VDWebView/VDWebView/VDWebViewScriptMessageHandler.h: -------------------------------------------------------------------------------- 1 | // 2 | // VDWebViewScriptMessageHandler.h 3 | // VDWebView 4 | // 5 | // Created by volientDuan on 2019/1/11. 6 | // Copyright © 2019 volientDuan. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface VDWebViewScriptMessageHandler: NSObject 13 | @property (nonatomic, weak)id target; 14 | @property (nonatomic, assign)SEL sel; 15 | @property (nonatomic, strong) NSMutableArray *scriptMessageNames; 16 | 17 | - (instancetype)initWithTarget:(id)target selector:(SEL)selector; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /VDWebView/VDWebView/VDWebViewScriptMessageHandler.m: -------------------------------------------------------------------------------- 1 | // 2 | // VDWebViewScriptMessageHandler.m 3 | // VDWebView 4 | // 5 | // Created by volientDuan on 2019/1/11. 6 | // Copyright © 2019 volientDuan. All rights reserved. 7 | // 8 | 9 | #import "VDWebViewScriptMessageHandler.h" 10 | 11 | @implementation VDWebViewScriptMessageHandler 12 | 13 | - (NSMutableArray *)scriptMessageNames { 14 | if (!_scriptMessageNames) { 15 | _scriptMessageNames = [NSMutableArray array]; 16 | } 17 | return _scriptMessageNames; 18 | } 19 | 20 | - (instancetype)initWithTarget:(id)target selector:(SEL)selector { 21 | self = [super init]; 22 | if (self) { 23 | self.target = target; 24 | self.sel = selector; 25 | } 26 | return self; 27 | } 28 | 29 | - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { 30 | IMP imp = [self.target methodForSelector:self.sel]; 31 | void (*func)(id, SEL, WKScriptMessage *) = (void *)imp; 32 | func(self.target,self.sel, message); 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /VDWebView/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // VDWebView 4 | // 5 | // Created by volientDuan on 2018/12/21. 6 | // Copyright © 2018 volientDuan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /VDWebView/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // VDWebView 4 | // 5 | // Created by volientDuan on 2018/12/21. 6 | // Copyright © 2018 volientDuan. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "WebViewController.h" 11 | 12 | @interface ViewController () 13 | 14 | @end 15 | 16 | @implementation ViewController 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | 21 | UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake([UIScreen mainScreen].bounds.size.width/2-100, 100, 200, 50)]; 22 | [btn setTitle:@"调试VDWebView" forState:UIControlStateNormal]; 23 | [btn setTitleColor:UIColor.whiteColor forState:UIControlStateNormal]; 24 | btn.titleLabel.font = [UIFont systemFontOfSize:20]; 25 | btn.backgroundColor = UIColor.blackColor; 26 | [self.view addSubview:btn]; 27 | [btn addTarget:self action:@selector(btnClick) forControlEvents:UIControlEventTouchUpInside]; 28 | // Do any additional setup after loading the view, typically from a nib. 29 | } 30 | 31 | - (void)btnClick { 32 | [self.navigationController pushViewController:[WebViewController new] animated:YES]; 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /VDWebView/WebViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewController.h 3 | // Common 4 | // 5 | // Created by volientDuan on 2018/12/19. 6 | // Copyright © 2018 volientDuan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface WebViewController : UIViewController 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /VDWebView/WebViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewController.m 3 | // Common 4 | // 5 | // Created by volientDuan on 2018/12/19. 6 | // Copyright © 2018 volientDuan. All rights reserved. 7 | // 8 | 9 | #import "WebViewController.h" 10 | #import "VDWebView.h" 11 | @interface WebViewController () 12 | @property (nonatomic, strong)VDWebView *webView; 13 | 14 | @end 15 | 16 | @implementation WebViewController 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | self.view.backgroundColor = UIColor.whiteColor; 21 | self.title = @"加载本地html调试"; 22 | 23 | [self.view addSubview:self.webView]; 24 | self.webView.isShowProgressBar = YES; 25 | self.webView.enableAllAlert = YES; 26 | [self.webView bridgeInitialized]; 27 | 28 | NSString *path = [[NSBundle mainBundle]pathForResource:@"wkweb.html" ofType:nil]; 29 | [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:path]]]; 30 | // [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost:8080"]]]; 31 | [self addJSHandle]; 32 | 33 | // test 注入拦截 postMessage 方法 34 | [self.webView addUserScriptWithSource:@"function postMessage(msg) { window.webkit.messageHandlers.postMessage.postMessage(msg)}" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]; 35 | // reload生效 36 | [self.webView reload]; 37 | 38 | // Do any additional setup after loading the view. 39 | } 40 | 41 | - (void)viewWillAppear:(BOOL)animated { 42 | [super viewWillAppear:animated]; 43 | 44 | } 45 | 46 | #pragma mark - delegate 47 | 48 | - (void)webViewDidStartLoad:(VDWebView *)webView { 49 | 50 | } 51 | 52 | - (void)webViewDidFinishLoad:(VDWebView *)webView { 53 | NSLog(@"%f",webView.contentHeight); 54 | } 55 | 56 | - (BOOL)webView:(VDWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { 57 | NSLog(@"\nshould start loading:%@",request.URL.absoluteString); 58 | return YES; 59 | } 60 | 61 | - (void)webView:(VDWebView *)webView innerHeight:(CGFloat)height { 62 | NSLog(@"innerHeight:%f",height); 63 | } 64 | 65 | #pragma mark - 注册方法 66 | - (void)addJSHandle { 67 | [self.webView addScriptMessageHandler:self name:@"popView"]; 68 | [self.webView addScriptMessageHandler:self name:@"reloadView"]; 69 | [self.webView addScriptMessageHandler:self name:@"changeTitle"]; 70 | [self.webView addScriptMessageHandler:self name:@"sendMessage"]; 71 | [self.webView addScriptMessageHandler:self name:@"injectJS"]; 72 | 73 | [self.webView addScriptMessageHandler:self name:@"postMessage"]; 74 | } 75 | 76 | #pragma mark - js->oc的一些方法 77 | - (void)popView:(NSDictionary *)info { 78 | [self.navigationController popViewControllerAnimated:YES]; 79 | } 80 | - (void)reloadView { 81 | [self.webView reload]; 82 | } 83 | - (void)changeTitle:(NSString *)title { 84 | self.title = title; 85 | } 86 | - (void)sendMessage:(NSString *)msg { 87 | NSLog(@"%@",msg); 88 | } 89 | - (void)injectJS { 90 | // 注入 91 | [self.webView addUserScriptWithSource:@"alert(\"简单的注入个Alert\")" injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]; 92 | [self.webView addUserScriptWithSource:@"function postMessage(msg) { window.webkit.messageHandlers.postMessage.postMessage(msg)}" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]; 93 | // reload生效 94 | [self.webView reload]; 95 | } 96 | 97 | - (void)postMessage:(NSString *)msg { 98 | NSLog(@"%@",msg); 99 | } 100 | 101 | #pragma mark - jsBridge 102 | 103 | - (void)jsBridgeTest:(NSDictionary *)params { 104 | 105 | NSLog(@"\njsBridge:%@",params); 106 | } 107 | 108 | - (void)testJSBridgeToJS:(NSDictionary *)params { 109 | [self.webView.bridge executeJsMethod:params[@"method"] params:@[@"1",@"2",@"3"] completionHandler:^(id result, NSError *error) { 110 | NSLog(@"\njs方法执行h结果回调:%@\n错误信息:%@",result,error); 111 | }]; 112 | } 113 | 114 | #pragma mark - property 115 | - (VDWebView *)webView { 116 | if (!_webView) { 117 | _webView = [[VDWebView alloc]initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height-88)]; 118 | _webView.delegate = self; 119 | } 120 | return _webView; 121 | } 122 | 123 | - (void)dealloc { 124 | NSLog(@"释放webViewController"); 125 | } 126 | 127 | @end 128 | -------------------------------------------------------------------------------- /VDWebView/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // VDWebView 4 | // 5 | // Created by volientDuan on 2018/12/21. 6 | // Copyright © 2018 volientDuan. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /VDWebViewTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /VDWebViewTests/VDWebViewTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // VDWebViewTests.m 3 | // VDWebViewTests 4 | // 5 | // Created by volientDuan on 2018/12/21. 6 | // Copyright © 2018 volientDuan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface VDWebViewTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation VDWebViewTests 16 | 17 | - (void)setUp { 18 | // Put setup code here. This method is called before the invocation of each test method in the class. 19 | } 20 | 21 | - (void)tearDown { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | - (void)testExample { 26 | // This is an example of a functional test case. 27 | // Use XCTAssert and related functions to verify your tests produce the correct results. 28 | } 29 | 30 | - (void)testPerformanceExample { 31 | // This is an example of a performance test case. 32 | [self measureBlock:^{ 33 | // Put the code you want to measure the time of here. 34 | }]; 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /VDWebViewUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /VDWebViewUITests/VDWebViewUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // VDWebViewUITests.m 3 | // VDWebViewUITests 4 | // 5 | // Created by volientDuan on 2018/12/21. 6 | // Copyright © 2018 volientDuan. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface VDWebViewUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation VDWebViewUITests 16 | 17 | - (void)setUp { 18 | // Put setup code here. This method is called before the invocation of each test method in the class. 19 | 20 | // In UI tests it is usually best to stop immediately when a failure occurs. 21 | self.continueAfterFailure = NO; 22 | 23 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 24 | [[[XCUIApplication alloc] init] launch]; 25 | 26 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 27 | } 28 | 29 | - (void)tearDown { 30 | // Put teardown code here. This method is called after the invocation of each test method in the class. 31 | } 32 | 33 | - (void)testExample { 34 | // Use recording to get started writing UI tests. 35 | // Use XCTAssert and related functions to verify your tests produce the correct results. 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /upload_pod_run.sh: -------------------------------------------------------------------------------- 1 | a=`grep -E 's.version.*=' VDWebView.podspec` 2 | b=${a#*\'} 3 | version=${b%\'*} 4 | LineNumber=`grep -nE 's.version.*=' VDWebView.podspec | cut -d : -f1` 5 | echo "current version is ${version}, please enter the new version:" 6 | read newVersion 7 | # 修改VDCommon.podspec文件中的version为指定值 8 | sed -i "" "${LineNumber}s/${version}/${newVersion}/g" VDWebView.podspec 9 | # 修改readme版本号 10 | sed -i "" "s/${version}/${newVersion}/g" README.md 11 | echo "git commit and git push origin master ? (y/n):" 12 | read isCommit 13 | if [[ ${isCommit} = "y" ]] || [[ ${isCommit} = "Y" ]]; then 14 | git add . 15 | echo "please ender commit info:" 16 | read commitInfo 17 | # 先commit再pull后push 18 | git commit -am ${commitInfo} 19 | echo "push origin master..." 20 | git pull origin master 21 | git push origin master 22 | fi 23 | echo "add tag and push tag..." 24 | git tag ${newVersion} 25 | git push origin master --tags 26 | pod spec lint VDWebView.podspec --allow-warnings 27 | pod trunk push VDWebView.podspec --allow-warnings --------------------------------------------------------------------------------