使用 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...

相关推荐
专业开发者11 小时前
调试 iOS 蓝牙应用的新方法
物联网·macos·ios·cocoa
tangbin58308516 小时前
iOS Swift 可选值(Optional)详解
前端·ios
卷心菜加农炮1 天前
基于Python的FastAPI后端开发框架如何使用PyInstaller 进行打包与部署
ios
北极象2 天前
千问大模型接入示例
ios·iphone·qwen
ipad协议开发2 天前
企业微信 iPad 协议应用机器人开发
ios·企业微信·ipad
QuantumLeap丶2 天前
《Flutter全栈开发实战指南:从零到高级》- 26 -持续集成与部署
android·flutter·ios
2501_915918412 天前
TCP 抓包分析在复杂网络问题中的作用,从连接和数据流层面理解系统异常行为
网络·网络协议·tcp/ip·ios·小程序·uni-app·iphone
二流小码农3 天前
鸿蒙开发:个人开发者如何使用华为账号登录
android·ios·harmonyos
wvy3 天前
Xcode 26还没有适配SceneDelegate的app建议尽早适配
ios
游戏开发爱好者83 天前
苹果 App 上架流程,结合 Xcode、CI 等常见工具
macos·ios·ci/cd·小程序·uni-app·iphone·xcode