美文网首页高能
[iOS] MUI-WebView模式集成到iOS应用

[iOS] MUI-WebView模式集成到iOS应用

作者: objcat | 来源:发表于2018-10-24 14:30 被阅读492次

    前言

    最近在研究 尝试把h5+环境单页面成到iOS端 也就是官方所说的WebView集成模式 但是当你照着官方文档 重新开一个新项目 把里面的静态库和系统库一个一个的导入进去 解决了所有报错问题后 你会得到一片空白 每当我看到官方文档那不严谨不规范的集成方式后 都气的浑身发抖 所以在这里写一篇文章来记录一下 因为篇幅可能比较大 我会从最基本的开始说起

    一.开始集成

    在开始之前需要先准备一批网址 这里可以下载MUI官方Demo

    1.新建一个工程或使用已有的老工程

    步骤省略.

    2.在工程中导入MUI基础静态库

    静态库请前往官方网站下载iOS端的demo解压后找到SDK -> Libs在里面耐心搜索找到

    liblibUI.a
    libcoreSupport.a
    liblibPDRCore.a
    

    除了导入这四个基础静态库以外还需要导入一个.bundle文件也就是资源文件 可以在SDK -> Bundles中找到

    PandoraApi.bundle
    

    接下来导入静态库的头文件(.h)

    SDK -> inc 里面直接拖拽到项目中来
    

    接下来导入页面资源 在官方demo中的Pandora文件夹以folder的形式引入 这个就是页面资源了 这里为了测试使用就都引入进来了

    之后尝试cmd+b编译一下项目发现并没有报错
    之后我们按照官方demo在AppDelegate中初始化5+环境

    #import "PDRCore.h"
    
    [PDRCore initEngineWihtOptions:launchOptions withRunMode:PDRCoreRunModeWebviewClient];
    

    温馨提示:Xcode10的同学请在file -> Project Settings中把编译系统改成 Legacy Build System否则引入文件的时候没有代码提示

    在编译一下发现有密密麻麻的31处错误

    接下来就是导入系统库了 官方文档中所写的并不准确 经本人测试这些系统库可满足项目不会报错

    libc++.tbd
    StoreKit.framework
    QuickLook.framework
    AudioToolbox.framework
    CoreTelephony.framework
    MobileCoreServices.framework
    JavaScriptCore.framework
    MediaPlayer.framework
    WebKit.framework
    

    这里说明一下 因为Xcode10弃用libstdc++.tbd所以需要使用libc++.tbd代替

    3.修改工程配置

    1.在Build Phases -> Other Linker Flags 添加 -ObjC 注意O和C需要大写
    2.修改 bitcodeNO (否则你打包的时候会报错)

    4.代码部分

    代码我们使用官方demo提供的示例 对应工程文件为HBuilder-Integrate 如果没有demo的可以在文章最开始的地方下载

    首先打开 HBuilder-Integrate 之后注释掉AppDelegate中的PDRCoreRunModeAppClient所对应的这行代码或者把该枚举改成PDRCoreRunModeWebviewClient然后直接运行项目

    看似正常的东西 我们点点看


    经测试除了第四个功能可以使用 其余均有问题 而且没有任何错误提示

    有的人会说 只有真机上有指纹 你真机测试一下

    好跟着我们的镜头一起来看吧


    没错 这就是你们看到的真机运行出来的效果 到这里你一定有几个疑问
    1.为什么模拟器上和真机跑出来的效果不一样(导航栏不见了)
    2.为什么官方demo会提示缺失组件

    容我吐槽一句 官方的demo质量真是垃圾的一批!!!

    问题解决方案

    好了我们从这里开始解决问题 首先我们看一下官方代码

    - (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名为中文则需要对路径进行编码
            //NSString* pFilePath =  (NSString *)CFURLCreateStringByAddingPercentEscapes( kCFAllocatorDefault, (CFStringRef)pTempString, NULL, NULL,  kCFStringEncodingUTF8 );
            
            // 单页面集成时可以设置打开的页面是本地文件或者是网络路径
            // NSString* pFilePath = @"http://www.163.com";
            
            
            // 用户在集成5+SDK时,需要在5+内核初始化时设置当前的集成方式,
            // 请参考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];
            }
      
        }
    }
    
    

    我们可以看到 先初始化一个单例PDRCore这个东西是管理5+环境的核心组件 然后创建一个webView 也就是PDRCoreAppFrame 之后添加到self.view上 如果使用arc模式 就是去掉 release retain 关键字就可以了 这里不一一赘述了

    基本原理是这样 我们开始解决模拟器和真机跑出来效果不同的问题 (导航栏会产生缩进问题) 这里的解决方案是把导航栏设置为不透明色

     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"];
    

    这个路径是官方示例中的交互demo 你可以查看该文件中的js代码来了解前后端交互需要调用的一些方法

    之后我们开始解决官方demo组件丢失的问题
    上面的提示为plugintest模块 所以我们就从如何找回这个模块开始入手 经过一番周折 查到了官方相关的页面
    http://ask.dcloud.net.cn/article/67
    如果你有耐心可以自己看看 如果没有就算了 总之了一句话 使用交互之前需要先注册一下 直接上代码

    [pCoreHandle regPluginWithName:@"plugintest" impClassName:@"PGPluginTest" type:PDRExendPluginTypeFrame javaScript:nil];
    

    只有这一行代码还不够 还需要引入一个叫PGPluginTest的自定义类 这里强调自定义是你用任何一个新建的类都可以承担这个角色 我们搜索一下官方demo发现刚好有这个类 把它放入你的新工程 重新运行项目 发现终于可以交互了!!!

    先别急着高兴 交互可以使用了 但是NJS发送消息到原生层在新工程中仍无法使用

    所以我们需要导入相应的静态库

    liblibPGInvocation.a
    

    之后我们添加通知测试一下

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(received:) name:@"SendDataToNative" object:nil];
    
    - (void)received:(NSNotification *)noti {
        NSLog(@"原生界面收到了通知");
    }
    

    到这里h5+基本交互功能的web已经搭建完成了

    下面我会介绍一下基础交互的方法

    在上文中我已经提到了 h5+ 已经把交互的方法封装在了静态库中 我们使用的时候需要两个步骤

    1. 在代码中注册交互实例
    2. 在js中调用交互代码
    3. 在原生自定义类中实现交互代码并处理事件

    本文的交互实例名称为 plugintest 负责交互的类是 PGPluginTest 交互的html页面是 plugin.html

    接下来我们打开plugin.html来查看具体的交互方法 我们可以查看到这样一段代码

    plus.plugintest.PluginTestFunction
    

    plus为5+环境的实例
    plugintest为我们注册的交互实例
    PluginTestFunction则是实例调用的方法

    与此同时在 PGPluginTest 中存在一个叫 PluginTestFunction 的方法

    - (void)PluginTestFunction:(PGMethod *)commands
    

    js中调用该方法的同时 原生类中的方法也随着执行 你可以在这个原生类中做一些自定义操作 这就是所谓的js与原生交互

    同样的交互过程中需要传递一下参数 我们把上文中的js补全一下 传递一些参数

    var a = {
        "name": "第四个参数 - 名字",
        "age": "第四个参数 - 年龄"
    }
    
    plus.plugintest.PluginTestFunction("第一个参数", "第二个参数", "第三个参数", a, function (result) {
        alert(result[0] + "\n" + result[1] + "\n" + result[2] + "\n" + result[3].name + "\n" + result[3].age);
    }, function (result) {
        alert(result)
    });
    
    

    这里需要说一下 官方这种交互方式支持用户自己传递4个参数 超出数量的参数会被舍弃 所以如果参数个数超过4个则可以使用对象的方式传递(例如代码中定义的a) 这样不仅可以节省参数空间 而且方便

    我们可以看到方法中有两个function这两个均为异步回调 其中第一个function表示成功后的回调 第二个function表示失败后的回调

    我们再回到原生PGPluginTest的代码中查看一下响应方法

    - (void)PluginTestFunction:(PGMethod *)commands {
        if (commands) {
            // 异步方法的回调id,H5+ 会根据回调ID通知JS层运行结果成功或者失败
            NSString *cbId = [commands.arguments objectAtIndex:0];
    
            /**
             用户的参数会在第二个参数开始传回
             这里说一下 通过观察控制台可以发现 返回的arguments实际上是一个数组 无论你是否传值 都只有五个参数
             第一个参数为对调id是自动生成的
             所以用户可以控制的参数为实际上为4个 无论是否传值 均存在 若不传值 默认为 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方式传递参数
            NSArray *pResultArray = [NSArray arrayWithObjects:pArgument1, pArgument2, pArgument3, pArgument4, nil];
    
            PDRPluginResult *result = [PDRPluginResult resultWithStatus:PDRCommandStatusOK messageAsArray: pResultArray];
            
            // 通知JS层Native层运行结果
            [self toCallback:cbId withReslut:[result toJSONString]];
        }
    }
    

    我们可以看到与js中相对应的方法中接收参数只有一个commands 在这个对象中 我们可以获取到传递参数的arguments

    我们来看一下接收参数时的具体表现形式

    可以看到arguments其实是一个数组 空间为6 我在里面传递的四个参数分别在它的 1 2 3 4 索引处 索引0所在的参数实际上是一个回调id 通过这个id可以回调到匿名的function中 索引5 指向一个NSNull对象 也就是说我们最多只能传递4个参数 如果再加一个参数是不会出现任何效果的
    PDRCommandStatusOK是代表成功的枚举
    toCallback: withReslut:就是回调方法 传递一个id和需要传递的内容就可以回调给js页面 withReslut参数是一个json类型的字符串 到js页面后会自动转化成js中的对象

    温馨提示:在实际开发中可能并不需要那么多的参数 所以请酌情使用

    PDRPluginResult *result = [PDRPluginResult resultWithStatus:PDRCommandStatusOK messageAsArray: pResultArray];
     [self toCallback:cbId withReslut:[result toJSONString]];
    

    这两句话代码是回调一个数组 同样的你想回调一个字典对象也是可以的 如此即可 在另一面接收的result.key就可以接收到传递的值了

    PDRPluginResult *result = [PDRPluginResult resultWithStatus:PDRCommandStatusOK messageAsDictionary:@{@"key": @"value"}];
    

    到此js交互这一部分内容完结 不再一一赘述

    二.功能拓展

    经过上边的实践 我们已经可以调用最基本的交互了 h5+平台最大的特色就是可以调用封装好的原生交互 比如相机 二维码扫描 系统相册 录音播放 调用原生界面 等 在今后的时间里我会一一列举这些功能和导入的方式

    因此学会功能拓展是非常重要的 功能拓展的思路就是
    1.查看官方demo寻找需要的功能
    2.导入功能所需要的静态库(需要耐心寻找)
    3.使用官方实例html进行调试即可

    下面我会挑选几个功能 列举一下 如何使用

    1.照相/录像

    这个模块需要我们导入

    liblibCamera.a
    

    并导入系统动态库

    Photos.framework
    CoreMedia.framework
    

    然后在Info.plist中开启拍照和麦克风权限

    Privacy - Camera Usage Description
    Privacy - Microphone Usage Description
    

    然后指定路径为

    [NSString stringWithFormat:@"file://%@/%@", path, @"Pandora/apps/HelloH5/www/plus/camera.html"]
    

    运行之后发现提示file模块缺失 不要慌 导入下面静态库即可

    liblibIO.a
    

    运行之后发现拍照和录像都正常 但是照片和录像均无法播放 所以如果想实现在网页上播放的效果 需要自己实现点击方法

    三.个人demo

    个人demo未成品 只包含基础功能 持续更新中...
    https://github.com/objcat/MUI-WebView-Demo

    finally enjoy it.

    write by objcat

    2018.10.24

    相关文章

      网友评论

        本文标题:[iOS] MUI-WebView模式集成到iOS应用

        本文链接:https://www.haomeiwen.com/subject/jbvazftx.html