├── _config.yml ├── .DS_Store ├── image ├── image1.png ├── image2.png └── 微信回调Scheme.png ├── WKWebViewDemo ├── .DS_Store ├── WKWebViewDemo │ ├── .DS_Store │ ├── Assets.xcassets │ │ ├── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── XYWKWebView │ │ ├── .DS_Store │ │ ├── wk_backIcon@2x.png │ │ ├── XYScriptMessage.m │ │ ├── XYScriptMessage.h │ │ ├── XYWKTool.h │ │ ├── XYWKWebViewController.h │ │ ├── XYWKWebView.h │ │ ├── XYWKTool.m │ │ ├── XYWKWebView.m │ │ └── XYWKWebViewController.m │ ├── UnifiedAccessViewController.swift │ ├── LaunchScreen.storyboard │ ├── Info.plist │ ├── AppDelegate.swift │ ├── ViewController.swift │ ├── WebViewController.swift │ ├── Main.storyboard │ └── main.html ├── WKWebViewDemo.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── project.pbxproj └── WKWebViewDemo-Bridging-Header.h ├── LICENSE ├── .gitignore ├── iOS App 接入H5 支付.md └── README.md /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-hacker -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyouPrince/XYWKWebView/HEAD/.DS_Store -------------------------------------------------------------------------------- /image/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyouPrince/XYWKWebView/HEAD/image/image1.png -------------------------------------------------------------------------------- /image/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyouPrince/XYWKWebView/HEAD/image/image2.png -------------------------------------------------------------------------------- /image/微信回调Scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyouPrince/XYWKWebView/HEAD/image/微信回调Scheme.png -------------------------------------------------------------------------------- /WKWebViewDemo/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyouPrince/XYWKWebView/HEAD/WKWebViewDemo/.DS_Store -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyouPrince/XYWKWebView/HEAD/WKWebViewDemo/WKWebViewDemo/.DS_Store -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo/XYWKWebView/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyouPrince/XYWKWebView/HEAD/WKWebViewDemo/WKWebViewDemo/XYWKWebView/.DS_Store -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo/XYWKWebView/wk_backIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyouPrince/XYWKWebView/HEAD/WKWebViewDemo/WKWebViewDemo/XYWKWebView/wk_backIcon@2x.png -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo/XYWKWebView/XYScriptMessage.m: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // XYScriptMessage.m 4 | // WKWebViewDemo 5 | // 6 | // Created by 渠晓友 on 2018/6/28. 7 | // 8 | // Copyright © 2018年 xiaoyouPrince. All rights reserved. 9 | // 10 | 11 | #import "XYScriptMessage.h" 12 | 13 | @implementation XYScriptMessage 14 | 15 | - (NSString *)description { 16 | return [NSString stringWithFormat:@"<%@:{method:%@,params:%@,callback:%@}>", NSStringFromClass([self class]),self.method, self.params, self.callback]; 17 | } 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | // 主要借鉴了 XXX,学习一下对方的封装思想。对 WKWebView 学习之后,也顺便完善了一下对应的功能和不足 6 | 7 | // 实例提供主要类 8 | // XYWebViewController -- > 控制XYWebView的UI 和 对应的 WebView 代理(WKUIDelegate,WKNavigationDelegate) 9 | // XYWebView -- > 自定义WebView.加载本地/远程URL方法 10 | // XYScriptMessage -- > JS 回调 OC 的方法 11 | 12 | 13 | #import "XYWKWebViewController.h" 14 | #import "XYWKWebView.h" 15 | #import "XYScriptMessage.h" 16 | -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo/XYWKWebView/XYScriptMessage.h: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // XYScriptMessage.h 4 | // WKWebViewDemo 5 | // 6 | // Created by 渠晓友 on 2018/6/28. 7 | // 8 | // Copyright © 2018年 xiaoyouPrince. All rights reserved. 9 | // 10 | 11 | #import 12 | 13 | /** 14 | * WKWebView与JS调用时参数规范实体 15 | */ 16 | @interface XYScriptMessage : NSObject 17 | 18 | /** 19 | * 方法名 20 | * 用来确定Native App的执行逻辑 21 | */ 22 | @property (nonatomic, copy) NSString *method; 23 | 24 | /** 25 | * 方法参数 26 | * json字符串 27 | */ 28 | @property (nonatomic, copy) NSDictionary *params; 29 | 30 | /** 31 | * 回调函数名 32 | * Native App执行完后回调的JS方法名 33 | */ 34 | @property (nonatomic, copy) NSString *callback; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo/UnifiedAccessViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UnifiedAccessViewController.swift 3 | // WKWebViewDemo 4 | // 5 | // Created by 渠晓友 on 2020/3/16. 6 | // Copyright © 2020 xiaoyou. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class UnifiedAccessViewController: XYWKWebViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | // Do any additional setup after loading the view. 17 | } 18 | 19 | 20 | /* 21 | // MARK: - Navigation 22 | 23 | // In a storyboard-based application, you will often want to do a little preparation before navigation 24 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 25 | // Get the new view controller using segue.destination. 26 | // Pass the selected object to the new view controller. 27 | } 28 | */ 29 | 30 | } 31 | -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo/XYWKWebView/XYWKTool.h: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // XYWKTool.h 4 | // WKWebViewDemo 5 | // 6 | // Created by 渠晓友 on 2018/7/3. 7 | // 8 | // Copyright © 2018年 xiaoyouPrince. All rights reserved. 9 | // 10 | // 可扩展工具类 11 | 12 | #import 13 | #import 14 | 15 | @interface NSDictionary (JSON) 16 | - (NSString *)jsonString; 17 | @end 18 | @interface XYWKTool : NSObject 19 | 20 | /// 调转到App Store 21 | + (void)jumpToAppStoreFromVc:(UIViewController *)fromVc withUrl:(NSURL *)url; 22 | + (void)jumpToAppStoreFromVc:(UIViewController *)fromVc withAppID:(NSString *)appID; 23 | 24 | /// 打开对应App的URL 25 | + (void)openURLFromVc:(UIViewController *)fromVc withUrl:(NSURL *)url; 26 | 27 | /// 选择图片相关 28 | + (void)chooseImageFromVC:(UIViewController *)fromVc sourceType:(UIImagePickerControllerSourceType)type callBackMethod:(NSString *)callback; 29 | + (void)removeTempImages; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 渠晓友 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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo/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 | -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo/XYWKWebView/XYWKWebViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // XYWKWebViewController.h 4 | // WKWebViewDemo 5 | // 6 | // Created by 渠晓友 on 2018/6/28. 7 | // 8 | // Copyright © 2018年 xiaoyouPrince. All rights reserved. 9 | // 10 | 11 | #import 12 | #import "XYWKWebView.h" 13 | #import "XYScriptMessage.h" 14 | 15 | @interface XYWKWebViewController : UIViewController 16 | 17 | @property (nonatomic, strong) XYWKWebView *webView; 18 | @property (nonatomic, copy) NSString *url; 19 | /** 20 | * JS & App 协议的交互名称 21 | * 用于子类化自由设置,默认 @“webViewApp” 22 | */ 23 | @property (nonatomic, copy) NSString * webViewAppName; 24 | 25 | #pragma mark -- navigation 26 | 27 | /** 28 | * 加载时是否显示HUD提示层(默认YES) 29 | */ 30 | @property (nonatomic, assign) BOOL showHudWhenLoading; 31 | 32 | /** 33 | * 是否显示加载进度 (默认YES) 34 | */ 35 | @property (nonatomic, assign) BOOL shouldShowProgress; 36 | 37 | /** 38 | * 是否使用WebPage的title作为导航栏title(默认YES) 39 | */ 40 | @property (nonatomic, assign) BOOL isUseWebPageTitle; 41 | 42 | #pragma mark -- 43 | 44 | /** 45 | * 是否允许WebView内部的侧滑返回(默认YES) 46 | */ 47 | @property (nonatomic, assign) BOOL alwaysAllowSideBackGesture; 48 | 49 | /** 50 | * 是否支持滚动(默认YES) 51 | */ 52 | @property (nonatomic, assign) BOOL scrollEnabled; 53 | 54 | /** 55 | * 是否使用web页面导航栏(默认NO) 56 | */ 57 | @property (nonatomic, assign) BOOL useWebNavigationBar; 58 | 59 | #pragma mark - 微信 & 支付宝 H5支付 60 | 61 | /** 62 | * 微信H5支付的 Referer -- 即完成回跳 App 的 Scheme 63 | * @note 这个参数必须为申请微信支付的”授权安全域名“ 64 | * @note 在 Info.plist 中 @b 必须 设置相同的 App 回调 URL Scheme 65 | */ 66 | @property (nonatomic, copy) NSString * wx_Referer; 67 | 68 | /** 69 | * 支付宝H5支付的 AppUrlScheme -- 即完成回跳 App 的 Scheme 70 | * @note 在 Info.plist 中 @b 必须 设置相同的 App 回调URL Scheme 71 | */ 72 | @property (nonatomic, copy) NSString * zfb_AppUrlScheme; 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo/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 | NSPhotoLibraryUsageDescription 31 | 访问一下相册 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UIMainStoryboardFile 35 | Main 36 | UIRequiredDeviceCapabilities 37 | 38 | armv7 39 | 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UISupportedInterfaceOrientations~ipad 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationPortraitUpsideDown 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo/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 | } -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // AppDelegate.swift 4 | // WKWebViewDemo 5 | // 6 | // Created by 渠晓友 on 2018/6/26. 7 | // 8 | // Copyright © 2018年 xiaoyouPrince. All rights reserved. 9 | // 10 | 11 | import UIKit 12 | 13 | @UIApplicationMain 14 | class AppDelegate: UIResponder, UIApplicationDelegate { 15 | 16 | var window: UIWindow? 17 | 18 | 19 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 20 | // Override point for customization after application launch. 21 | return true 22 | } 23 | 24 | func applicationWillResignActive(_ application: UIApplication) { 25 | // 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. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | func applicationDidEnterBackground(_ application: UIApplication) { 30 | // 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. 31 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 32 | } 33 | 34 | func applicationWillEnterForeground(_ application: UIApplication) { 35 | // 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. 36 | } 37 | 38 | func applicationDidBecomeActive(_ application: UIApplication) { 39 | // 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. 40 | } 41 | 42 | func applicationWillTerminate(_ application: UIApplication) { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | 47 | } 48 | 49 | -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo/XYWKWebView/XYWKWebView.h: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // XYWKWebView.h 4 | // WKWebViewDemo 5 | // 6 | // Created by 渠晓友 on 2018/6/28. 7 | // 8 | // Copyright © 2018年 xiaoyouPrince. All rights reserved. 9 | // 10 | 11 | #define XYWKScreenW [UIScreen mainScreen].bounds.size.width 12 | #define XYWKScreenH [UIScreen mainScreen].bounds.size.height 13 | #define XYWKiPhoneX (XYWKScreenH >= 812) // iPhone X height 14 | #define XYWKNavHeight (XYWKiPhoneX ? (88.f) : (64.f)) // statusBarH + TopBarH 15 | 16 | #ifdef DEBUG 17 | #define XYWKLog(...) NSLog( @"< %s:(第%d行) > %@",__func__ , __LINE__, [NSString stringWithFormat:__VA_ARGS__] ) 18 | #define XYWKFunc DLog(@""); 19 | #else 20 | #define XYWKLog( s, ... ) 21 | #define XYWKFunc; 22 | #endif 23 | 24 | #import 25 | 26 | @class XYWKWebView; 27 | @class XYScriptMessage; 28 | 29 | @protocol XYWKWebViewMessageHandleDelegate 30 | 31 | @optional 32 | - (void)xy_webView:(nonnull XYWKWebView *)webView didReceiveScriptMessage:(nonnull XYScriptMessage *)message; 33 | 34 | @end 35 | 36 | @interface XYWKWebView : WKWebView 37 | 38 | //webview加载的url地址 39 | @property (nullable, nonatomic, copy) NSString *webViewRequestUrl; 40 | //webview加载的参数 41 | @property (nullable, nonatomic, copy) NSDictionary *webViewRequestParams; 42 | 43 | @property (nullable, nonatomic, weak) id xy_messageHandlerDelegate; 44 | 45 | #pragma mark - Load Url 46 | 47 | - (void)loadRequestWithRelativeUrl:(nonnull NSString *)relativeUrl; 48 | 49 | - (void)loadRequestWithRelativeUrl:(nonnull NSString *)relativeUrl params:(nullable NSDictionary *)params; 50 | 51 | /** 52 | * 加载本地HTML页面 53 | * 54 | * @param htmlName html页面文件名称 55 | */ 56 | - (void)loadLocalHTMLWithFileName:(nonnull NSString *)htmlName; 57 | 58 | 59 | /** 60 | 定制化内容,底部添加分享和赞的功能 61 | 62 | @param footerJS 底部的功能部分JS代码 63 | */ 64 | - (void)loadLocalHTML:(nonnull NSString *)htmlName withAddingStyleJS:(nullable NSString *)styleJS funcJS:(nullable NSString *)funcJS FooterJS:(nullable NSString *)footerJS; 65 | 66 | #pragma mark - View Method 67 | 68 | /** 69 | * 重新加载webview 70 | */ 71 | - (void)reloadWebView; 72 | 73 | #pragma mark - JS Method Invoke 74 | 75 | /** 76 | * 调用JS方法(无返回值) 77 | * 78 | * @param jsMethod JS方法名称 79 | */ 80 | - (void)callJS:(nonnull NSString *)jsMethod; 81 | 82 | /** 83 | * 调用JS方法(可处理返回值) 84 | * 85 | * @param jsMethod JS方法名称 86 | * @param handler 回调block 87 | */ 88 | - (void)callJS:(nonnull NSString *)jsMethod handler:(nullable void(^)(__nullable id response))handler; 89 | 90 | @end 91 | -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // ViewController.swift 4 | // WKWebViewDemo 5 | // 6 | // Created by 渠晓友 on 2018/6/26. 7 | // 8 | // Copyright © 2018年 xiaoyouPrince. All rights reserved. 9 | // 10 | 11 | import UIKit 12 | import WebKit 13 | 14 | class ViewController: UIViewController , WKUIDelegate{ 15 | 16 | // var webView: WKWebView! 17 | // lazy var link : CADisplayLink! = { [weak self] in 18 | // let link = CADisplayLink(target: self ?? UIView(), selector: #selector(loadProgerss)) 19 | // return link 20 | // }() 21 | // lazy var progressView : UIProgressView! = { [weak self] in 22 | // let progressView = UIProgressView(progressViewStyle: .default) 23 | // progressView.frame = CGRect(x: 0, y: 100, width: (self?.view.frame.size.width)!, height: 5) 24 | // return progressView 25 | // }() 26 | // 27 | // 28 | // override func loadView() { 29 | // let webConfiguration = WKWebViewConfiguration() 30 | // webView = WKWebView(frame: .zero, configuration: webConfiguration) 31 | // webView.uiDelegate = self 32 | // view = webView 33 | // } 34 | // 35 | // override func viewDidLoad() { 36 | // super.viewDidLoad() 37 | // 38 | // let myURL = URL(string:"https://www.apple.com") 39 | // let myRequest = URLRequest(url: myURL!) 40 | // webView.load(myRequest) 41 | // link.add(to: RunLoop.current, forMode: .commonModes) 42 | // 43 | // webView.allowsBackForwardNavigationGestures = true 44 | // 45 | // // 监听 46 | // webView.addObserver(self, forKeyPath: "isLoading", options: .old, context: nil) 47 | // } 48 | 49 | 50 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 51 | 52 | 53 | // let config = WKWebViewConfiguration() 54 | // let webView = WKWebView(frame: CGRect(x: 0, y: 84, width: UIScreen.main.bounds.size.width, height: 300), configuration:config) 55 | // self.view.addSubview(webView) 56 | // 57 | // let path = Bundle.main.path(forResource: "main", ofType: "html") 58 | // let url = URL(string: path!) 59 | // 60 | // do { 61 | // let str = try String(contentsOfFile: path!, encoding: String.Encoding.utf8) 62 | // print("content is: \(str)") 63 | // webView.loadHTMLString(str, baseURL: nil) 64 | // } 65 | // catch { 66 | // print("file read failed!") 67 | // } 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | } 76 | @IBAction func localHTMLtest(_ sender: Any) { 77 | let webVC = WebViewController() 78 | navigationController?.pushViewController(webVC, animated: true) 79 | } 80 | @IBAction func unifiyTest(_ sender: Any) { 81 | let webVC = UnifiedAccessViewController() 82 | webVC.url = "http://39.107.94.38:8005/h5/#/?code=9940A63EC6DC1F9685FD54955DF51C0DA39F7C0FFCD21C0C47046FCF92ADBEAF193A7B7E12EF6FE0FB8214BAE565D90B67623E9FD8C68FE73E8FE0BB1CEF02F765710F5911633F10CAC5BB3929B5598974C66C54ADE386A30957E6515E1582A6"; 83 | navigationController?.pushViewController(webVC, animated: true) 84 | } 85 | } 86 | 87 | extension ViewController{ 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | // @objc func loadProgerss(){ 97 | // print(webView.estimatedProgress) 98 | // self.view.addSubview(progressView) 99 | // self.progressView.progress = Float(webView.estimatedProgress) 100 | // 101 | // if webView.estimatedProgress == 1.0 { 102 | // link.remove(from: RunLoop.current, forMode: .commonModes) 103 | // progressView.removeFromSuperview() 104 | // } 105 | // } 106 | // 107 | // override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 108 | // print("keypath = \(String(describing: keyPath))") 109 | // print("object = \(String(describing: object))") 110 | // print("change = \(String(describing: change))") 111 | // print("context = \(String(describing: context))") 112 | // 113 | // print("webView.isLoading = \(webView.isLoading)") 114 | // 115 | // 116 | // } 117 | } 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo/WebViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // WebViewController.swift 4 | // WKWebViewDemo 5 | // 6 | // Created by 渠晓友 on 2018/6/28. 7 | // 8 | // Copyright © 2018年 xiaoyouPrince. All rights reserved. 9 | // 10 | 11 | import UIKit 12 | 13 | class WebViewController: XYWKWebViewController { 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | /// #用法0: 直接加载对应的地址 <没有参数> 19 | // self.webView.loadRequest(withRelativeUrl: "https://www.httpbin.org/") 20 | 21 | /// #用法1: 直接加载对应的地址 <有参数> 22 | // let params = ["name":"xiaoyou", 23 | // "password" : "123456#/HTTP_Methods/get_get"] 24 | // self.webView.loadRequest(withRelativeUrl: "https://www.httpbin.org/", params: params) 25 | 26 | /// #用法2: 直接加载本地HTML文件 <没有参数> 27 | self.webView.loadLocalHTML(withFileName: "main") 28 | 29 | /// #用法3: JS 注入,添加一些方法 <这里的原生坐标和JS之间无法直接相对应> 30 | let margin : CGFloat = 6.0 31 | let padding : CGFloat = 10.0 32 | let width = UIScreen.main.bounds.size.width - (margin * 2.0) - (margin * 7.0 + padding) 33 | let btnWidth = (width - padding - 5) / 2.0 34 | 35 | let styleJS = """ 36 | 61 | """ 62 | 63 | let funcJS = """ 64 | \t\t\tfunction testFunc(text){\n 65 | \t\t\t\tvar message = \"点我干什么\";\n 66 | \t\t\t\twindow.webkit.messageHandlers.webViewApp.postMessage(message);\n 67 | \t\t\t\talert(text);\n 68 | \t\t\t}\n 69 | """ 70 | 71 | let footerJS = """ 72 | \t


\n 73 | \t
底部说明
74 | 75 |
76 |
77 | """ 78 | self.webView.loadLocalHTML("main", withAddingStyleJS: styleJS, funcJS: funcJS, footerJS: footerJS) 79 | // self.webView.backgroundColor = UIColor.red 80 | 81 | /// 设置导航 82 | self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "返回", style: .plain, target: self, action: #selector(backAction)); 83 | self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "调用JS", style: .plain, target: self, action: #selector(callJS)); 84 | } 85 | 86 | 87 | 88 | 89 | 90 | } 91 | 92 | 93 | /// #用法4: OC 调用JS方法。这里可以调用JS,把H5需要的参数传给他们 94 | /// 这里是JS 回调方法 95 | extension WebViewController{ 96 | 97 | @objc func backAction() { 98 | navigationController?.popViewController(animated: true) 99 | } 100 | 101 | @objc func callJS() { 102 | self.webView.callJS("call('Hello World!')") { (response) in 103 | print("\(String(describing: response))") 104 | } 105 | } 106 | 107 | /// 这里是重写了WebView接受到JS消息的回调,需要调用super方法才能执行内部方法,否则这里只是打印 108 | override func xy_webView(_ webView: XYWKWebView, didReceive message: XYScriptMessage) { 109 | 110 | // 如果完全自定义的js方法处理,无需重写父类,自行实现即可 111 | super.xy_webView(webView, didReceive: message) 112 | print(message) 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 29 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /iOS App 接入H5 支付.md: -------------------------------------------------------------------------------- 1 | # iOS App 接入 H5 支付 2 | 3 | **「H5 支付」**是在手机浏览器中购买商品,发起支付的一种应用场景。 4 | 5 | 微信官方不建议在 App 内接入 H5 支付,但实际 App 开发中会有接入 Web 商城的实际需求。 6 | 7 | 本文档即是对 iOS App 中的 H5 支付的统一封装,整体基于 WKWebView,关于WKWebView的封装和使用可以看这篇文章[WKWebView 的封装和使用](README.md) 8 | 9 | 下面先说使用方法,然后再说实现思路 10 | 11 | ### 使用方法 12 | 13 | **1.设置微信/支付宝支付完成回调 App 的 URL Scheme** 14 | 15 | - 支付宝客户端H5支付回跳 App 的 Scheme 可以自定义, 16 | - 微信客户端H5支付回跳 App 的 Scheme **必须为微信商户注册的微信支付安全域名**,此处问产品经理 17 | 18 | **2.生成实例对象,设置对应的 wx_Referer 和 zfb_AppUrlScheme** 19 | 20 | - wx_Referer 即微信支付的回调 Scheme 21 | - zfb_AppUrlScheme 即支付宝支付回调的 Scheme 22 | 23 | 工具基类名:XYWKWebViewController ,建议使用子类继承,这样不同的业务模块可以互不影响,代码示例如下 24 | 25 | ``` 26 | // 直接赋值对应的回调Scheme即可。 27 | XYWKWebViewController * vc = [XYWKWebViewController new]; 28 | vc.wx_Referer = @"wxser.fesco.com.cn"; 29 | vc.zfb_AppUrlScheme = @"testmobilepay"; 30 | [self.navigationController pushViewController:vc]; 31 | ``` 32 | 33 | 34 | **H5 支付核心思路** 35 | 36 | 1. 网页内 H5 调起三方支付,发起统一下单接口。【此步骤为H5开发】 37 | 2. 统一支付返回值中会返回调起支付的中间页面,商户后台会发到支付平台。【此中间页面地址需要我们客户端自己处理,这样才可以在支付完成/取消之后回调到App页面】 38 | 3. 中间页面进行 H5 校验,成功后发起支付链接。【此处监听到支付链接需要我们客户端调起对应的支付App】 39 | 40 | **注意:H5 页面调起三方支付,必须设置回到 App 的 URL scheme,否则回不到自己App** 41 | 42 | 下面是调试时候我这边检测到的支付调用 43 | 44 | ``` 45 | 输入密码后确定支付: 其中每个地址都经过了urlencode处理 46 | 47 | // 1.统一下单接口调用,其中域名为商户申请微信支付的安全域名。 48 | http://wxser.xxxxx.com.cn/pay/index?app=3&sign=BD226DB5ADDC7DE913FAEB83D7A45271&orderno=TF202001170000798372&money=16900&description=%E5%BE%AE%E5%95%86%E5%9F%8E&tencentPayType=3&backurl=https%3A%2F%2Fwmall.fesco.com.cn%2Fpageview%2Fhtml%2FPersonal%2F%E6%94%AF%E4%BB%98%E6%88%90%E5%8A%9F.html 49 | 50 | // 2.统一下单接口返回的微信支付中间页地址,由商户后台调用,发起微信支付 51 | https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx17101746455190817ba211ed1068551700&package=2480363457&redirect_url=https%3a%2f%2fwxser.fesco.com.cn%2fpay%2fH5PayBack 52 | 53 | // 3.中间页通过验证,调起微信支付 54 | weixin://wap/pay?prepayid%3Dwx17101746455190817ba211ed1068551700&package=2480363457&noncestr=1579227520&sign=31577677cb607b31def4781fa2d8be0d 55 | 56 | // 支付宝处理 57 | // 1. 统一支付接口链接 58 | https://www.alipay.com/cooperate/gateway.do?service=alipay.wap.create.direct.pay.by.user&partner=2088011635010164&_input_charset=UTF-8&seller_email=DZSW%40fesco.com.cn&out_trade_no=TF202001190000380271&subject=%e7%a6%8f%e5%88%a9%e5%95%86%e5%93%81%e5%85%91%e6%8d%a2&body=%e7%a6%8f%e5%88%a9%e5%95%86%e5%93%81%e5%85%91%e6%8d%a2&total_fee=0.01&payment_type=1&app_pay=Y&return_url=http%3a%2f%2ffesco3.datayan.cn%2fOrder%2fwxAlipayHsh_New_Return¬ify_url=http%3a%2f%2ffesco3.datayan.cn%2fOrder%2fAlipayHsh_New_Notify&sign=4b55fa10da04831fca8936b272645b57&sign_type=MD5 59 | 60 | // 2. 支付宝反馈的支付中间页面 61 | https://mclient.alipay.com/home/exterfaceAssign.htm?seller_email=DZSW%40fesco.com.cn&_input_charset=UTF-8&subject=%E7%A6%8F%E5%88%A9%E5%95%86%E5%93%81%E5%85%91%E6%8D%A2&sign=4b55fa10da04831fca8936b272645b57&body=%E7%A6%8F%E5%88%A9%E5%95%86%E5%93%81%E5%85%91%E6%8D%A2¬ify_url=http%3A%2F%2Ffesco3.datayan.cn%2FOrder%2FAlipayHsh_New_Notify&alipay_exterface_invoke_assign_model=cashier&alipay_exterface_invoke_assign_target=mapi_direct_trade.htm&payment_type=1&out_trade_no=TF202001190000380271&partner=2088011635010164&alipay_exterface_invoke_assign_sign=_oe_srjnatso%2B_y8%2B_i6_sum_me_b_e_o_c_jgej_kd_w_gn_ivd_qds%2Bya36_p_g_z4_z_c_s_qg%3D%3D&service=alipay.wap.create.direct.pay.by.user&total_fee=0.01&app_pay=Y&return_url=http%3A%2F%2Ffesco3.datayan.cn%2FOrder%2FwxAlipayHsh_New_Return&sign_type=MD5&alipay_exterface_invoke_assign_client_ip=219.239.42.66 62 | 63 | // 3. 调起支付宝支付的最终接口 64 | alipay://alipayclient/?%7B%22requestType%22%3A%22SafePay%22%2C%22fromAppUrlScheme%22%3A%22alipays%22%2C%22dataString%22%3A%22h5_route_token%3D%5C%22RZ110bgnfPGrMUV7a2yVXp8lR31YgImobilecashierRZ11%5C%22%26is_h5_route%3D%5C%22true%5C%22%22%7D 65 | ``` 66 | 67 | 68 | ### 微信H5支付流程和注意点 69 | 70 | 流程直接看文档[官方文档](https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_1) 71 | 72 | iOS 端注意点主要: 73 | 74 | #### 1. Referer 和 redirect_url 说明 75 | 76 | `HTTP Referer` 是 header 的一部分,当浏览器向web服务器发起请求的时,一般会带上 Referer,告诉服务器我是从哪个页面链接过来。微信中间页会对 Referer 进行校验,非安全域名将不能正常加载。 77 | 78 | `redirect_url` 是微信中间页唤起微信支付之后,页面重定向的地址。中间页唤起微信支付后会跳转到指定的 redirect_url。并且微信APP在支付完成时,也是通过 redirect_url 回调结果,redirect_url一般是一个页面地址,所以微信支付完成会打开 Safari 浏览器。本文通过修改 redirect_url,实现微信支付完毕跳回当前APP。 79 | 80 | > **注意: 81 | > 微信会校验 Referer(来源) 和 redirect_url(目标) 是否是安全域名。如果不传redirect_url,微信会将 Referer 当成 redirect_url,唤起支付之后会重定向到 Referer 对应的页面,建议带上 redirect_url
82 | 1.需对redirect_url进行urlencode处理 83 | 2.由于设置redirect_url后,回跳指定页面的操作可能发生在:1,微信支付中间页调起微信收银台后超过5秒 2,用户点击“取消支付“或支付完成后点“完成”按钮。因此无法保证页面回跳时,支付流程已结束,所以商户设置的redirect_url地址不能自动执行查单操作,应让用户去点击按钮触发查单操作** 84 | 85 | 86 | 87 | #### 2. 必须设置微信支付完成回跳 App 的 URL Scheme 88 | 89 | ![微信回调Scheme](image/微信回调Scheme.png) 90 | 91 | ### 微信H5支付流程封装 92 | 93 | 弄清楚了微信支付流程,那封装思路就清晰了,这里只讲思路和接口,具体实现请参考[项目地址](https://www.github.com/xiaoyouPrince/WKWebViewDemo) 94 | 95 | #### 1. wx_Referer 入参设置 96 | 97 | ``` 98 | /** 99 | * 微信H5支付的 Referer -- 即完成回跳 App 的 Scheme 100 | * @note 这个参数必须为申请微信支付的”授权安全域名“ 101 | * @note 在 Info.plist 中 @b 必须 设置相同的 App 回调 URL Scheme 102 | */ 103 | @property (nonatomic, copy) NSString * wx_Referer; 104 | ``` 105 | #### 2. wx_redirect_url 源文件内部变量,存放拉起微信H5支付的回调地址 106 | 107 | ``` 108 | /** 109 | * 微信H5支付的重定向地址 110 | */ 111 | @property (nonatomic, copy) NSString * wx_redirect_url; 112 | ``` 113 | 114 | #### 3. 源文件实现逻辑(详见项目源码) 115 | 116 | ``` 117 | 1. 重写 - webView: decidePolicyForNavigationAction: decisionHandler: 方法,处理每次请求 118 | 2. 处理中间页面地址【Scheme+域名为”https://wx.tenpay.com“】,查看是否包含重定向地址 `redirect_url`参数, 119 | 3. 如果后台没有配置,则手动配置为 self.wx_Referer 如 abc.com:// 停止当前请求并发起新地址的请求 120 | 4. 如果已经配置且不等于 self.wx_Referer 则设置为 self.wx_Referer 如 abc.com:// 并用`wx_redirect_url` 保存原来的重定向地址,停止当前请求并发起新地址的请求 121 | 5. 如果已经配置且重定向地址为 self.wx_Referer 则直接通过本次请求不做处理 122 | ``` 123 | 124 | 详细代码请看项目源码,里面混合了支付宝与微信的H5支付逻辑,有兴趣可以自行查阅。 125 | 126 | ### 支付宝H5支付流程和注意点 127 | 128 | 支付宝的支付逻辑就相对简单了,支付宝调起中间页校验成功之后会拉起支付宝,在拉起支付宝客户端我们对该地址进行处理,将我们的 App Scheme 替换给该地址内部的 `fromAppUrlScheme` 参数即可,支付宝客户端即可在支付完成/取消之后回调到我们的 App 129 | 130 | #### 1. zfb_AppUrlScheme 入参设置 131 | 132 | ``` 133 | /** 134 | * 支付宝H5支付的 AppUrlScheme -- 即完成回跳 App 的 Scheme 135 | * @note 在 Info.plist 中 @b 必须 设置相同的 App 回调URL Scheme 136 | */ 137 | @property (nonatomic, copy) NSString * zfb_AppUrlScheme; 138 | ``` 139 | 140 | #### 2. 实现文件处理调起支付宝客户端 141 | 142 | ``` 143 | 1. 重写 - webView: decidePolicyForNavigationAction: decisionHandler: 方法,处理每次请求 144 | 2. 处理拉起支付宝客户端的地址,标志为 URL Scheme 为 alipay 145 | 3. 将请求URL内的 fromAppUrlScheme 参数替换为我们自己 App Scheme。取消当前请求并发起新地址的请求 146 | ``` 147 | 148 | ## 最后 149 | 150 | 我封装的好的项目地址,可以直接使用[项目地址](https://github.com/xiaoyouPrince/WKWebViewDemo) 151 | 152 | 如果此项目帮助到了你,欢迎点赞! 153 | 154 | 最后祝大家玩的愉快~ 155 | 156 | 参考文章: 157 | [微信H5支付](https://www.jianshu.com/p/65979e8bf251) 158 | [支付宝H5支付](https://www.jianshu.com/p/72e867a7e40e) 159 | 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo/XYWKWebView/XYWKTool.m: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // XYWKTool.m 4 | // WKWebViewDemo 5 | // 6 | // Created by 渠晓友 on 2018/7/3. 7 | // 8 | // Copyright © 2018年 xiaoyouPrince. All rights reserved. 9 | // 10 | // 抽取一个可扩展工具类 11 | 12 | #import "XYWKTool.h" 13 | #import 14 | #import "XYWKWebViewController.h" 15 | 16 | @implementation NSDictionary (JSON) 17 | - (NSString *)jsonString 18 | { 19 | NSMutableString *strM = [[NSMutableString alloc] initWithString:@"{\n"]; 20 | for (NSString *key in self.keyEnumerator) { 21 | [strM appendFormat:@"%@: \"%@\",\n",key,self[key]]; 22 | } 23 | [strM appendString:@"}"]; 24 | return strM; 25 | } 26 | 27 | @end 28 | 29 | @interface XYWKTool() 30 | /** 本地临时图片地址数组 */ 31 | @property (nonatomic, strong) NSMutableArray * tempImagePathArray; 32 | 33 | @end 34 | @implementation XYWKTool 35 | static __weak XYWKWebViewController * _webVC; 36 | static NSString * _webViewCallBackMethod; 37 | static XYWKTool *_tool; 38 | 39 | - (instancetype)init 40 | { 41 | self = [super init]; 42 | if (self) { 43 | self.tempImagePathArray = @[].mutableCopy; 44 | } 45 | return self; 46 | } 47 | 48 | + (void)jumpToAppStoreFromVc:(UIViewController *)fromVc withUrl:(NSURL *)url 49 | { 50 | // 通常App Store的scheme形式为 itms-appss://itunes.apple.com/cn/app/id382201985?mt=8 51 | // 取出appID 52 | NSString *urlLastPathComponent = url.lastPathComponent; 53 | NSString *idStr = [[urlLastPathComponent componentsSeparatedByString:@"?"] firstObject]; 54 | NSString *appID = [idStr substringFromIndex:2]; 55 | 56 | [self jumpToAppStoreFromVc:fromVc withAppID:appID]; 57 | } 58 | 59 | /** 60 | 应用内跳转到App Store页 61 | */ 62 | + (void)jumpToAppStoreFromVc:(UIViewController *)fromVc withAppID:(NSString *)appID 63 | { 64 | 65 | // 直接禁用之前页面,并不可重复点击 66 | if (!fromVc.view.isUserInteractionEnabled) return; 67 | [fromVc.view setUserInteractionEnabled:NO]; 68 | 69 | 70 | // 创建对象 71 | SKStoreProductViewController *storeVC = [[SKStoreProductViewController alloc] init]; 72 | // 设置代理 73 | _tool = _tool ?: [self new]; 74 | storeVC.delegate = _tool; 75 | // 初始化参数 76 | NSDictionary *dict = [NSDictionary dictionaryWithObject:appID forKey:SKStoreProductParameterITunesItemIdentifier]; 77 | 78 | // 跳转App Store页 79 | [storeVC loadProductWithParameters:dict completionBlock:^(BOOL result, NSError * _Nullable error) { 80 | if (error) { 81 | NSLog(@"错误信息:%@",error.userInfo); 82 | } 83 | else 84 | { 85 | // 弹出模态视图 86 | [fromVc presentViewController:storeVC animated:YES completion:^{ 87 | [fromVc.view setUserInteractionEnabled:YES]; 88 | }]; 89 | } 90 | }]; 91 | } 92 | 93 | 94 | #pragma mark -- SKStoreProductViewControllerDelegate 95 | /** 96 | SKStoreProductViewControllerDelegate 方法,选择完成之后的处理 97 | @param viewController SKStoreProductViewController 98 | */ 99 | - (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewController 100 | { 101 | NSLog(@"将要退出 App Store 页面了"); 102 | [viewController dismissViewControllerAnimated:YES completion:^{ 103 | NSLog(@"已经退出 App Store 页面完成了"); 104 | }]; 105 | } 106 | 107 | 108 | + (void)openURLFromVc:(UIViewController *)fromVc withUrl:(NSURL *)url 109 | { 110 | // 处理是否是去AppStore 111 | // 这里进行重定向了,例如 网页内下载APP 链接,起初是https://地址。重定向之后itms-appss:// 这里需要重新让WebView加载一下 112 | NSString *redirectionUrlScheme = url.scheme; 113 | if ([redirectionUrlScheme isEqualToString:@"itms-appss"]) { 114 | [XYWKTool jumpToAppStoreFromVc:fromVc withUrl:url]; 115 | return; 116 | } 117 | 118 | // 处理手机上内部App 119 | BOOL success = [[UIApplication sharedApplication] canOpenURL:url]; 120 | if (success) { 121 | // 打开App 122 | if (__builtin_available(iOS 10.0, *)) { 123 | [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; 124 | } else { 125 | [[UIApplication sharedApplication] openURL:url]; 126 | } 127 | }else 128 | { 129 | // 设置弹窗 130 | NSString *string = [NSString stringWithFormat:@"无法打开%@,因为 iOS 无法识别以\"%@\"开头的互联网地址",url,url.scheme]; 131 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"温馨提示" message:string preferredStyle:UIAlertControllerStyleAlert]; 132 | // 确定按键不带点击事件 133 | [alertController addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]]; 134 | [fromVc presentViewController:alertController animated:YES completion:nil]; 135 | } 136 | } 137 | 138 | #pragma mark - 相册相关 139 | 140 | 141 | + (void)chooseImageFromVC:(UIViewController *)fromVc sourceType:(UIImagePickerControllerSourceType)type callBackMethod:(NSString *)callback 142 | { 143 | if ([fromVc isKindOfClass:XYWKWebViewController.class]) { 144 | _webVC = (XYWKWebViewController *)fromVc; 145 | _webViewCallBackMethod = callback; 146 | } 147 | 148 | // 进行弹出相册 149 | UIImagePickerController *vc = [[UIImagePickerController alloc] init]; 150 | _tool = _tool ?: [self new]; 151 | vc.delegate = _tool; 152 | vc.sourceType = type; 153 | 154 | [_webVC presentViewController:vc animated:YES completion:nil]; 155 | 156 | } 157 | 158 | - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { 159 | 160 | UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage]; 161 | 162 | NSInteger randNUM = arc4random()%100; 163 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES); 164 | NSString *filePath = [[paths objectAtIndex:0] stringByAppendingPathComponent:[NSString stringWithFormat:@"%ld.png",(randNUM)]]; // 保存 165 | [self.tempImagePathArray addObject:filePath]; 166 | [UIImagePNGRepresentation(image) writeToFile: filePath atomically:YES]; 167 | 168 | NSDictionary *dict = @{ 169 | @"localOriginalUri": filePath, 170 | @"localCompressedUri": filePath, 171 | }; 172 | 173 | [picker dismissViewControllerAnimated:YES completion:^{ 174 | [_webVC.webView callJS:[NSString stringWithFormat:@"%@(%@)", _webViewCallBackMethod,dict.jsonString]]; 175 | }]; 176 | } 177 | 178 | 179 | 180 | + (void)removeTempImages 181 | { 182 | if (_tool.tempImagePathArray.count) { 183 | for (NSString *path in _tool.tempImagePathArray) { 184 | if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { 185 | [[NSFileManager defaultManager] removeItemAtPath:path error:NULL]; 186 | } 187 | } 188 | [_tool.tempImagePathArray removeAllObjects]; 189 | } 190 | } 191 | 192 | 193 | #pragma mark - 地理位置相关 194 | 195 | @end 196 | 197 | 198 | -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo/main.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Webview页面标题 16 | 21 | 22 | 150 | 151 | 152 | Hello,World!
153 | 154 | 155 |
156 |


157 |


158 |


159 | 测试新页面打开 W3School


160 | 161 |


162 | 163 |

164 | 165 |

166 | 167 |


171 |


172 | 173 |


174 | 175 |


176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /WKWebViewDemo/WKWebViewDemo/XYWKWebView/XYWKWebView.m: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // XYWKWebView.m 4 | // WKWebViewDemo 5 | // 6 | // Created by 渠晓友 on 2018/6/28. 7 | // 8 | // Copyright © 2018年 xiaoyouPrince. All rights reserved. 9 | // 10 | 11 | #import "XYWKWebView.h" 12 | #import "XYScriptMessage.h" 13 | 14 | //这里可以统一设置WebView的访问域名,方便切换 15 | #ifdef DEBUG 16 | # define BASE_URL_API @"http://****/" //测试环境 17 | #else 18 | # define BASE_URL_API @"http://****/" //正式环境 19 | #endif 20 | 21 | @interface XYWKWebView () 22 | 23 | @property (nonatomic, strong) NSURL *baseUrl; 24 | 25 | @end 26 | 27 | @implementation XYWKWebView 28 | 29 | - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration { 30 | self = [super initWithFrame:frame configuration:configuration]; 31 | if (self) { 32 | 33 | //默认允许系统自带的侧滑后退 34 | self.allowsBackForwardNavigationGestures = YES; 35 | self.baseUrl = [NSURL URLWithString:BASE_URL_API]; 36 | 37 | //设置允许JS自动打开新window 38 | WKPreferences *preference = [WKPreferences new]; 39 | preference.javaScriptCanOpenWindowsAutomatically = YES; 40 | } 41 | 42 | return self; 43 | } 44 | 45 | #pragma mark - Load Url 46 | 47 | - (void)loadRequestWithRelativeUrl:(NSString *)relativeUrl; { 48 | 49 | 50 | // 这里目前只支持HTTP(s)协议,如果需要跳转到App Store就需要验证对应的Scheme : itms-appss 51 | [self loadRequestWithRelativeUrl:relativeUrl params:nil]; 52 | } 53 | 54 | - (void)loadRequestWithRelativeUrl:(NSString *)relativeUrl params:(NSDictionary *)params { 55 | 56 | NSURL *url = [self generateURL:relativeUrl params:params]; 57 | 58 | [self loadRequest:[NSURLRequest requestWithURL:url]]; 59 | } 60 | 61 | /** 62 | * 加载本地HTML页面 63 | * 64 | * @param htmlName html页面文件名称 65 | */ 66 | - (void)loadLocalHTMLWithFileName:(nonnull NSString *)htmlName { 67 | 68 | NSString *path = [[NSBundle mainBundle] bundlePath]; 69 | NSURL *baseURL = [NSURL fileURLWithPath:path]; 70 | NSString * htmlPath = [[NSBundle mainBundle] pathForResource:htmlName 71 | ofType:@"html"]; 72 | NSString * htmlCont = [NSString stringWithContentsOfFile:htmlPath 73 | encoding:NSUTF8StringEncoding 74 | error:nil]; 75 | 76 | [self loadHTMLString:htmlCont baseURL:baseURL]; 77 | } 78 | 79 | - (void)loadLocalHTML:(NSString *)htmlName withAddingStyleJS:(NSString *)styleJS funcJS:(NSString *)funcJS FooterJS:(NSString *)footerJS 80 | { 81 | 82 | if (footerJS == nil) { 83 | return; // 如果html为空直接返回。 84 | } 85 | 86 | 87 | // 找到对应HTML内容 88 | NSString *path = [[NSBundle mainBundle] bundlePath]; 89 | NSURL *baseURL = [NSURL fileURLWithPath:path]; 90 | NSString * htmlPath = [[NSBundle mainBundle] pathForResource:htmlName 91 | ofType:@"html"]; 92 | NSMutableString * htmlCont = [NSMutableString stringWithContentsOfFile:htmlPath 93 | encoding:NSUTF8StringEncoding 94 | error:nil]; 95 | 96 | // 插入Style代码 97 | NSRange rangeOfTitle = [htmlCont rangeOfString:@""]; 98 | [htmlCont insertString:styleJS atIndex:rangeOfTitle.location + rangeOfTitle.length + 1]; 99 | 100 | // 插入JS代码 101 | NSRange rangeOfScript = [htmlCont rangeOfString:@"