前言
最近在研究 嘗試把h5+環(huán)境單頁(yè)面成到iOS端 也就是官方所說(shuō)的WebView集成模式
但是當(dāng)你照著官方文檔 重新開(kāi)一個(gè)新項(xiàng)目 把里面的靜態(tài)庫(kù)和系統(tǒng)庫(kù)一個(gè)一個(gè)的導(dǎo)入進(jìn)去 解決了所有報(bào)錯(cuò)問(wèn)題后 你會(huì)得到一片空白 每當(dāng)我看到官方文檔那不嚴(yán)謹(jǐn)不規(guī)范的集成方式后 都?xì)獾臏喩戆l(fā)抖 所以在這里寫(xiě)一篇文章來(lái)記錄一下 因?yàn)槠赡鼙容^大 我會(huì)從最基本的開(kāi)始說(shuō)起
一.開(kāi)始集成
在開(kāi)始之前需要先準(zhǔn)備一批網(wǎng)址 這里可以下載MUI官方Demo
1.新建一個(gè)工程或使用已有的老工程
步驟省略.
2.在工程中導(dǎo)入MUI基礎(chǔ)靜態(tài)庫(kù)
靜態(tài)庫(kù)請(qǐng)前往官方網(wǎng)站下載iOS端的demo解壓后找到SDK
-> Libs
在里面耐心搜索找到
liblibUI.a
libcoreSupport.a
liblibPDRCore.a
除了導(dǎo)入這四個(gè)基礎(chǔ)靜態(tài)庫(kù)以外還需要導(dǎo)入一個(gè).bundle
文件也就是資源文件
可以在SDK
-> Bundles
中找到
PandoraApi.bundle
接下來(lái)導(dǎo)入靜態(tài)庫(kù)的頭文件(.h)
SDK -> inc 里面直接拖拽到項(xiàng)目中來(lái)
接下來(lái)導(dǎo)入頁(yè)面資源 在官方demo中的Pandora
文件夾以folder
的形式引入 這個(gè)就是頁(yè)面資源了 這里為了測(cè)試使用就都引入進(jìn)來(lái)了
之后嘗試cmd+b
編譯一下項(xiàng)目發(fā)現(xiàn)并沒(méi)有報(bào)錯(cuò)
之后我們按照官方demo在AppDelegate中初始化5+環(huán)境
#import "PDRCore.h"
[PDRCore initEngineWihtOptions:launchOptions withRunMode:PDRCoreRunModeWebviewClient];
溫馨提示:Xcode10的同學(xué)請(qǐng)?jiān)?code>file -> Project Settings
中把編譯系統(tǒng)改成 Legacy Build System
否則引入文件的時(shí)候沒(méi)有代碼提示
在編譯一下發(fā)現(xiàn)有密密麻麻的31
處錯(cuò)誤
接下來(lái)就是導(dǎo)入系統(tǒng)庫(kù)了 官方文檔中所寫(xiě)的并不準(zhǔn)確 經(jīng)本人測(cè)試這些系統(tǒng)庫(kù)可滿足項(xiàng)目不會(huì)報(bào)錯(cuò)
libc++.tbd
StoreKit.framework
QuickLook.framework
AudioToolbox.framework
CoreTelephony.framework
MobileCoreServices.framework
JavaScriptCore.framework
MediaPlayer.framework
WebKit.framework
這里說(shuō)明一下 因?yàn)?code>Xcode10棄用libstdc++.tbd
所以需要使用libc++.tbd
代替
3.修改工程配置
1.在Build Phases
-> Other Linker Flags
添加 -ObjC
注意O和C需要大寫(xiě)
2.修改 bitcode
為 NO
(否則你打包的時(shí)候會(huì)報(bào)錯(cuò))
4.代碼部分
代碼我們使用官方demo提供的示例 對(duì)應(yīng)工程文件為HBuilder-Integrate
如果沒(méi)有demo的可以在文章最開(kāi)始的地方下載
首先打開(kāi) HBuilder-Integrate
之后注釋掉AppDelegate中的PDRCoreRunModeAppClient
所對(duì)應(yīng)的這行代碼或者把該枚舉改成PDRCoreRunModeWebviewClient
然后直接運(yùn)行項(xiàng)目
看似正常的東西 我們點(diǎn)點(diǎn)看
經(jīng)測(cè)試除了第四個(gè)功能可以使用 其余均有問(wèn)題 而且沒(méi)有任何錯(cuò)誤提示
有的人會(huì)說(shuō) 只有真機(jī)上有指紋 你真機(jī)測(cè)試一下
好跟著我們的鏡頭一起來(lái)看吧
沒(méi)錯(cuò) 這就是你們看到的真機(jī)運(yùn)行出來(lái)的效果 到這里你一定有幾個(gè)疑問(wèn)
1.為什么模擬器上和真機(jī)跑出來(lái)的效果不一樣(導(dǎo)航欄不見(jiàn)了)
2.為什么官方demo會(huì)提示缺失組件
容我吐槽一句 官方的demo質(zhì)量真是垃圾的一批!!!
問(wèn)題解決方案
好了我們從這里開(kāi)始解決問(wèn)題 首先我們看一下官方代碼
- (void)viewDidLoad
{
PDRCore* pCoreHandle = [PDRCore Instance];
if (pCoreHandle != nil)
{
NSString* pFilePath = [NSString stringWithFormat:@"file://%@/%@", [NSBundle mainBundle].bundlePath, @"Pandora/apps/HelloH5/www/plugin.html"];
[pCoreHandle start];
// 如果路徑中包含中文,或Xcode工程的targets名為中文則需要對(duì)路徑進(jìn)行編碼
//NSString* pFilePath = (NSString *)CFURLCreateStringByAddingPercentEscapes( kCFAllocatorDefault, (CFStringRef)pTempString, NULL, NULL, kCFStringEncodingUTF8 );
// 單頁(yè)面集成時(shí)可以設(shè)置打開(kāi)的頁(yè)面是本地文件或者是網(wǎng)絡(luò)路徑
// NSString* pFilePath = @"http://www.163.com";
// 用戶在集成5+SDK時(shí),需要在5+內(nèi)核初始化時(shí)設(shè)置當(dāng)前的集成方式榛鼎,
// 請(qǐng)參考AppDelegate.m文件的- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法
CGRect StRect = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
appFrame = [[PDRCoreAppFrame alloc] initWithName:@"WebViewID1" loadURL:pFilePath frame:StRect];
if (appFrame) {
[pCoreHandle.appManager.activeApp.appWindow registerFrame:appFrame];
[self.view addSubview:appFrame];
[appFrame release];
}
}
}
我們可以看到 先初始化一個(gè)單例PDRCore
這個(gè)東西是管理5+環(huán)境的核心組件 然后創(chuàng)建一個(gè)webView 也就是PDRCoreAppFrame
之后添加到self.view
上 如果使用arc模式 就是去掉 release
retain
關(guān)鍵字就可以了 這里不一一贅述了
基本原理是這樣 我們開(kāi)始解決模擬器和真機(jī)跑出來(lái)效果不同的問(wèn)題 (導(dǎo)航欄會(huì)產(chǎn)生縮進(jìn)問(wèn)題) 這里的解決方案是把導(dǎo)航欄設(shè)置為不透明色
self.navigationController.navigationBar.translucent = NO;
#import "TestWebViewController.h"
#import "PDRCoreAppFrame.h"
#import "PDRCoreAppManager.h"
@interface TestWebViewController ()
@property (strong, nonatomic) PDRCoreAppFrame *appFrame;
@property (strong, nonatomic) NSString *url;
@end
@implementation TestWebViewController
- (instancetype)initWithTitle:(NSString *)title URL:(NSString *)URL {
self = [super init];
if (self) {
self.navigationItem.title = title;
self.url = URL;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor];
PDRCore *pCoreHandle = [PDRCore Instance];
if (pCoreHandle) {
[pCoreHandle start];
self.appFrame = [[PDRCoreAppFrame alloc] initWithName:@"WebViewID1" loadURL:self.url frame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height - 64)];
if (self.appFrame) {
[pCoreHandle.appManager.activeApp.appWindow registerFrame:self.appFrame];
[pCoreHandle regPluginWithName:@"plugintest" impClassName:@"PGPluginTest" type:PDRExendPluginTypeFrame javaScript:nil];
[self.view addSubview:self.appFrame];
}
}
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(received:) name:@"SendDataToNative" object:nil];
}
- (void)received:(NSNotification *)noti {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"原生界面收到了通知" message:@"" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *determin = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {}];
[alert addAction:determin];
[self presentViewController:alert animated:YES completion:nil];
}
- (void)dealloc {
[[PDRCore Instance] setContainerView:nil];
}
@end
注意這里指定的路徑為
NSString *pFilePath = [NSString stringWithFormat:@"file://%@/%@", [NSBundle mainBundle].bundlePath, @"Pandora/apps/HelloH5/www/plugin.html"];
這個(gè)路徑是官方示例中的交互demo 你可以查看該文件中的js代碼來(lái)了解前后端交互需要調(diào)用的一些方法
之后我們開(kāi)始解決官方demo組件丟失的問(wèn)題
上面的提示為plugintest
模塊 所以我們就從如何找回這個(gè)模塊開(kāi)始入手 經(jīng)過(guò)一番周折 查到了官方相關(guān)的頁(yè)面
http://ask.dcloud.net.cn/article/67
如果你有耐心可以自己看看 如果沒(méi)有就算了 總之了一句話 使用交互之前需要先注冊(cè)一下 直接上代碼
[pCoreHandle regPluginWithName:@"plugintest" impClassName:@"PGPluginTest" type:PDRExendPluginTypeFrame javaScript:nil];
只有這一行代碼還不夠 還需要引入一個(gè)叫PGPluginTest
的自定義類 這里強(qiáng)調(diào)自定義是你用任何一個(gè)新建的類都可以承擔(dān)這個(gè)角色 我們搜索一下官方demo發(fā)現(xiàn)剛好有這個(gè)類 把它放入你的新工程 重新運(yùn)行項(xiàng)目 發(fā)現(xiàn)終于可以交互了!!!
先別急著高興 交互可以使用了 但是NJS發(fā)送消息到原生層
在新工程中仍無(wú)法使用
所以我們需要導(dǎo)入相應(yīng)的靜態(tài)庫(kù)
liblibPGInvocation.a
之后我們添加通知測(cè)試一下
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(received:) name:@"SendDataToNative" object:nil];
- (void)received:(NSNotification *)noti {
NSLog(@"原生界面收到了通知");
}
到這里h5+基本交互功能的web已經(jīng)搭建完成了
下面我會(huì)介紹一下基礎(chǔ)交互的方法
在上文中我已經(jīng)提到了 h5+ 已經(jīng)把交互的方法封裝在了靜態(tài)庫(kù)中 我們使用的時(shí)候需要兩個(gè)步驟
- 在代碼中注冊(cè)交互實(shí)例
- 在js中調(diào)用交互代碼
- 在原生自定義類中實(shí)現(xiàn)交互代碼并處理事件
本文的交互實(shí)例名稱為 plugintest
負(fù)責(zé)交互的類是 PGPluginTest
交互的html頁(yè)面是 plugin.html
接下來(lái)我們打開(kāi)plugin.html
來(lái)查看具體的交互方法 我們可以查看到這樣一段代碼
plus.plugintest.PluginTestFunction
plus
為5+環(huán)境的實(shí)例
plugintest
為我們注冊(cè)的交互實(shí)例
PluginTestFunction
則是實(shí)例調(diào)用的方法
與此同時(shí)在 PGPluginTest
中存在一個(gè)叫 PluginTestFunction
的方法
- (void)PluginTestFunction:(PGMethod *)commands
當(dāng)js
中調(diào)用該方法的同時(shí) 原生類中的方法也隨著執(zhí)行 你可以在這個(gè)原生類中做一些自定義操作 這就是所謂的js與原生交互
同樣的交互過(guò)程中需要傳遞一下參數(shù) 我們把上文中的js
補(bǔ)全一下 傳遞一些參數(shù)
var a = {
"name": "第四個(gè)參數(shù) - 名字",
"age": "第四個(gè)參數(shù) - 年齡"
}
plus.plugintest.PluginTestFunction("第一個(gè)參數(shù)", "第二個(gè)參數(shù)", "第三個(gè)參數(shù)", a, function (result) {
alert(result[0] + "\n" + result[1] + "\n" + result[2] + "\n" + result[3].name + "\n" + result[3].age);
}, function (result) {
alert(result)
});
這里需要說(shuō)一下 官方這種交互方式支持用戶自己傳遞4個(gè)參數(shù) 超出數(shù)量的參數(shù)會(huì)被舍棄 所以如果參數(shù)個(gè)數(shù)超過(guò)4個(gè)則可以使用對(duì)象
的方式傳遞(例如代碼中定義的a) 這樣不僅可以節(jié)省參數(shù)空間 而且方便
我們可以看到方法中有兩個(gè)function
這兩個(gè)均為異步回調(diào) 其中第一個(gè)function
表示成功后的回調(diào) 第二個(gè)function
表示失敗后的回調(diào)
我們?cè)倩氐皆?code>PGPluginTest的代碼中查看一下響應(yīng)方法
- (void)PluginTestFunction:(PGMethod *)commands {
if (commands) {
// 異步方法的回調(diào)id,H5+ 會(huì)根據(jù)回調(diào)ID通知JS層運(yùn)行結(jié)果成功或者失敗
NSString *cbId = [commands.arguments objectAtIndex:0];
/**
用戶的參數(shù)會(huì)在第二個(gè)參數(shù)開(kāi)始傳回
這里說(shuō)一下 通過(guò)觀察控制臺(tái)可以發(fā)現(xiàn) 返回的arguments實(shí)際上是一個(gè)數(shù)組 無(wú)論你是否傳值 都只有五個(gè)參數(shù)
第一個(gè)參數(shù)為對(duì)調(diào)id是自動(dòng)生成的
所以用戶可以控制的參數(shù)為實(shí)際上為4個(gè) 無(wú)論是否傳值 均存在 若不傳值 默認(rèn)為 NSNull
*/
NSString *pArgument1 = [commands.arguments objectAtIndex:1];
NSString *pArgument2 = [commands.arguments objectAtIndex:2];
NSString *pArgument3 = [commands.arguments objectAtIndex:3];
NSDictionary *pArgument4 = [commands.arguments objectAtIndex:4];
// 如果使用Array方式傳遞參數(shù)
NSArray *pResultArray = [NSArray arrayWithObjects:pArgument1, pArgument2, pArgument3, pArgument4, nil];
PDRPluginResult *result = [PDRPluginResult resultWithStatus:PDRCommandStatusOK messageAsArray: pResultArray];
// 通知JS層Native層運(yùn)行結(jié)果
[self toCallback:cbId withReslut:[result toJSONString]];
}
}
我們可以看到與js中相對(duì)應(yīng)的方法中接收參數(shù)只有一個(gè)commands
在這個(gè)對(duì)象中 我們可以獲取到傳遞參數(shù)的arguments
我們來(lái)看一下接收參數(shù)時(shí)的具體表現(xiàn)形式
可以看到arguments
其實(shí)是一個(gè)數(shù)組 空間為6 我在里面?zhèn)鬟f的四個(gè)參數(shù)分別在它的 1 2 3 4
索引處 索引0
所在的參數(shù)實(shí)際上是一個(gè)回調(diào)id 通過(guò)這個(gè)id可以回調(diào)到匿名的function中 索引5
指向一個(gè)NSNull對(duì)象 也就是說(shuō)我們最多只能傳遞4個(gè)參數(shù) 如果再加一個(gè)參數(shù)是不會(huì)出現(xiàn)任何效果的
PDRCommandStatusOK
是代表成功的枚舉
toCallback: withReslut:
就是回調(diào)方法 傳遞一個(gè)id和需要傳遞的內(nèi)容就可以回調(diào)給js頁(yè)面 withReslut參數(shù)是一個(gè)json類型的字符串 到j(luò)s頁(yè)面后會(huì)自動(dòng)轉(zhuǎn)化成js中的對(duì)象
溫馨提示:在實(shí)際開(kāi)發(fā)中可能并不需要那么多的參數(shù) 所以請(qǐng)酌情使用
PDRPluginResult *result = [PDRPluginResult resultWithStatus:PDRCommandStatusOK messageAsArray: pResultArray];
[self toCallback:cbId withReslut:[result toJSONString]];
這兩句話代碼是回調(diào)一個(gè)數(shù)組 同樣的你想回調(diào)一個(gè)字典對(duì)象也是可以的 如此即可 在另一面接收的result.key就可以接收到傳遞的值了
PDRPluginResult *result = [PDRPluginResult resultWithStatus:PDRCommandStatusOK messageAsDictionary:@{@"key": @"value"}];
到此js交互這一部分內(nèi)容完結(jié) 不再一一贅述
二.功能拓展
經(jīng)過(guò)上邊的實(shí)踐 我們已經(jīng)可以調(diào)用最基本的交互了 h5+平臺(tái)最大的特色就是可以調(diào)用封裝好的原生交互 比如相機(jī)
二維碼掃描
系統(tǒng)相冊(cè)
錄音播放
調(diào)用原生界面
等 在今后的時(shí)間里我會(huì)一一列舉這些功能和導(dǎo)入的方式
因此學(xué)會(huì)功能拓展是非常重要的 功能拓展的思路就是
1.查看官方demo尋找需要的功能
2.導(dǎo)入功能所需要的靜態(tài)庫(kù)(需要耐心尋找)
3.使用官方實(shí)例html進(jìn)行調(diào)試即可
下面我會(huì)挑選幾個(gè)功能 列舉一下 如何使用
1.照相/錄像
這個(gè)模塊需要我們導(dǎo)入
liblibCamera.a
并導(dǎo)入系統(tǒng)動(dòng)態(tài)庫(kù)
Photos.framework
CoreMedia.framework
然后在Info.plist
中開(kāi)啟拍照和麥克風(fēng)權(quán)限
Privacy - Camera Usage Description
Privacy - Microphone Usage Description
然后指定路徑為
[NSString stringWithFormat:@"file://%@/%@", path, @"Pandora/apps/HelloH5/www/plus/camera.html"]
運(yùn)行之后發(fā)現(xiàn)提示file
模塊缺失 不要慌 導(dǎo)入下面靜態(tài)庫(kù)即可
liblibIO.a
運(yùn)行之后發(fā)現(xiàn)拍照和錄像都正常 但是照片和錄像均無(wú)法播放 所以如果想實(shí)現(xiàn)在網(wǎng)頁(yè)上播放的效果 需要自己實(shí)現(xiàn)點(diǎn)擊方法
三.個(gè)人demo
個(gè)人demo未成品 只包含基礎(chǔ)功能 持續(xù)更新中...
https://github.com/objcat/MUI-WebView-Demo