使用 CXYWebScript,简化 iOS App 与 H5 交互

CXYWebScript

简化 iOS App 与 H5 交互,H5 直接调用 window.App?.onSayHello('Hello'),即可完成对原生 App onSayHello: 方法的调用。如此,与 H5 在 Android 端调用一致。

H5 端的简化
javascript 复制代码
// 以前 需要区分环境
if (isAndroid) {
    window.App.onSayHello('Hello'); 
}
 
if (isiOS) {
    window.webkit.messageHandlers.onSayHello.postMessage('Hello');   
}
​
// 现在 iOS使用CXYWebScript后, H5无需引入任何库,iOS和Android统一调用:
window.App?.onSayHello('Hello')

Tip1: 以上 window.App 是可以自定义的,如 window.CXY

Tip2: 判断当前环境是客户端还是其他H5端,可直接使用 if (window.App) 就行

iOS 端的简化
swift 复制代码
// 以前
CXYWeakScriptMsgHander *weakSelf = [[CXYWeakScriptMsgHander alloc] initWithHandler:self];
WKUserContentController *contentController = self.webView.configuration.userContentController;
[contentController addScriptMessageHandler:weakSelf name:@"onSayHello"];
[contentController addScriptMessageHandler:weakSelf name:@"onPreviewImages"];
[contentController addScriptMessageHandler:weakSelf name:@"onShareObj"];
​
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
   
    NSString *selector = message.name;
    id body = message.body;
  
    if ([selector isEqualToString:@"onSayHello"]) {
        
    } else if ([selector isEqualToString:@"onPreviewImages"]) {
        
    } else if ([selector isEqualToString:@"onShareObj"]) {
        
    }
}
​
// 现在
self.webScript = [[CXYWebScript alloc] initWithWebView:self.webView];
[self.webScript useUIDelegate];
​
// block 方式
[self.webScript addJsFunc:@"onSayHello" block:^NSString * _Nullable(NSArray *args) {
    return @"支持返回字符串或nil,给JS";
}];
​
// async-block 方式,支持异步返回值
[self.webScript addJsFunc:@"onPreviewImages" asyncBlock:^(NSArray * _Nonnull args, CXYStrBlock  _Nonnull returnBlock) {  
  
    cxy_dispatch_after_main_delay_block(2, ^{    
        returnBlock(@"我是异步返回值,2秒后才返回"); // returnBlock 必须要执行
    });
}];
​
// 或者 action-target 方式
[self.webScript addTarget:self
                   jsFunc:@"onPreviewImages"
                    ocSel:@selector(onPreviewImages:currentIndex:)];
​
[self.webScript addTarget:self
                   jsFunc:@"onShareObj"
                    ocSel:@selector(onShareObject:)];
​

CXYWebScript 有以下特点:

  • H5 在调用原生 App 方法是时,不需要区分是 Android 还是 iOS 环境
  • 支持使用 block,async-block 和 target-action 方式注册 js 方法
  • 支持原生 App 传递返回值给 H5 (限字符串类型或 nil )
  • 支持 OC 与 Swift, iOS 10+
  • 整个实现不到 200 行代码

原理解释:

1、让 WKWebView 注入下面的代码到 JS 环境中:

ini 复制代码
NSString *js = 
  @"window.App = new Proxy({},{      \
    get: function (target, name) {   \
        return function (...args) {  \
            return window.prompt(name,JSON.stringify(args)); \
        }; \
    }      \
  });"
​
WKUserScript *script = [[WKUserScript alloc] initWithSource:js injectionTime (WKUserScriptInjectionTimeAtDocumentStart) forMainFrameOnly:NO];    
[self.webView.configuration.userContentController addUserScript:script];

提示:

使用window.webkit.messageHandlers[name].postMessage(args); 亦可,只是后续实现略有不同。

问:为什么选用window.prompt方式呢?

答:因为它能直接同步返回值。

Proxy 的作用是拦截对 window.App 对象属性的访问。重写window.Appget 方法,当访问它的属性(name)时,会返回一个匿名函数

  • 该函数的参数是可变的,类数组

  • 该函数返回了window.prompt(window.App.属性名, args的json字符串数组);

    那么当 H5 端 js 调用:

dart 复制代码
window.App.onSayHello('Hello')  等价于 => window.prompt('onSayHello',"['Hello']"); 

2、而在 iOS H5 里调用 window.prompt 会执行 WKWebView.UIDelegate 的方法:

objectivec 复制代码
- (void)webView:(WKWebView *)webView
runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt
    defaultText:(nullable NSString *)defaultText
initiatedByFrame:(WKFrameInfo *)frame
completionHandler:(void (^)(NSString * _Nullable result))completionHandler;
​
// 调用completionHandler后,能同步返回值给H5端。
completionHandler('返回值') 

其中的两个参数分别对应方法名参数列表JSON数组

ini 复制代码
(NSString *)prompt => 'onSayHello'
(nullable NSString *)defaultText => '['Hello']'

3、对于 target-action 方式,根据方法名得到对应的 SEL,使用NSInvocation类,可以构造一个表示方法调用的对象,包括方法选择器、目标对象、参数和返回值。可以处理具有多个参数的方法调用。

ini 复制代码
// self.scriptMap = {@"onSayHello": NSStringFromSelector(onSayHello:)}
NSString *selString = self.scriptMap[prompt];  
SEL sel = NSSelectorFromString(selString);
NSArray *args = [self arrayWithJSON:defaultText];
​
NSMethodSignature *signature = [self methodSignatureForSelector:aSelector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:self];
[invocation setSelector:aSelector];
... 详细见源码

4、对于 blockasync-block 方式,根据方法名找到对应的 block,直接执行 block 就行:

ini 复制代码
// self.blockMap = {@"onSayHello": CXYBlock}
CXYBlock block = self.blockMap[prompt];
NSArray *args = [self arrayWithJSON:defaultText];
block(args);

源码

更多介绍和使用,请看 Github , 欢迎 star

github.com/iHongRen/CX...

相关推荐
tangweiguo030519877 小时前
SwiftUI布局完全指南:从入门到精通
ios·swift
T1an-111 小时前
最右IOS岗一面
ios
坏小虎13 小时前
Expo 快速创建 Android/iOS 应用开发指南
android·ios·rn·expo
光影少年15 小时前
Android和iOS原生开发的基础知识对RN开发的重要性,RN打包发布时原生端需要做哪些配置?
android·前端·react native·react.js·ios
北京自在科技15 小时前
Find My 修复定位 BUG,AirTag 安全再升级
ios·findmy·airtag
Digitally15 小时前
如何不用 USB 线将 iPhone 照片传到电脑?
ios·电脑·iphone
Sim14801 天前
iPhone将内置本地大模型,手机端AI实现0 token成本时代来临?
人工智能·ios·智能手机·iphone
Digitally1 天前
如何将 iPad 上的照片传输到 U 盘(4 种解决方案)
ios·ipad
报错小能手1 天前
ios开发方向——swift并发进阶核心 @MainActor 与 DispatchQueue.main 解析
开发语言·ios·swift
LcGero1 天前
Cocos Creator 业务与原生通信详解
android·ios·cocos creator·游戏开发·jsb