iOS小程序同层渲染到底怎么实现?

自微信小程序火了后, 个大头部厂商也纷纷效仿, 给自己的客户端提供了小程序框架支持. 然而小程序框架为了提升小程序页面的极致用户体验, 做了同层渲染.

    **抛砖引玉:**

    **我们今天的目标就是: 从0到1实现iOS端`WKWebView`同层渲染, 文章末尾有DEMO演示, 也有源码地址.**
weapp.png

同层渲染原理

「同层渲染」就是把原生组件绘制在 WebView 所渲染的页面中,与其他 HTML 控件在同一层级,效果如下图所示:

同层渲染原理.png

这里我给大家找到三篇比较官方的文章, 建议跳过看总结:

总结

这些文章只阐述了基本实现思路, 并没有教你怎么实现, 然并卵.

看完之后各大技术论坛的“同层渲染原理分析”的你, 还是实现不了.
下面我们一起来实现一个.

实现同层渲染

1. 我们在前端H5页面上创建一个需要生成原生组件的DOM节点.

<div class="container cid_1" data-component-type="input" style="width: 200px; height: 40px">
    <div style="width: 101%; height: 101%">&nbsp;</div>
</div>

<style>
    .container { 
        /* insert WKChildView in WKWebView */
        overflow: scroll;
        -webkit-overflow-scrolling: touch;
    }
</style>

2. 前端会把原生组件类型如cidposition等信息通过window.webkit.messageHandlers.handle.postMessage发送给iOS端.

<script>
    // 管理同层渲染组件, 通知原生创建同层渲染组件
    function insertNativeComponents() {
        var containers = document.getElementsByClassName("container");
        console.log(containers);
        for (var i = 0; i < containers.length; i++) {
            var ct = containers[I];
            var component_type = ct.getAttribute("data-component-type");
            if (!component_type) continue;
            var cid = ct.getAttribute("class").split(" ")[1];
            var cid_num = cid.slice("cid_".length);
            var frame = ct.getBoundingClientRect();
            var args = {
                "position": {
                    "top": frame.top,
                    "left": frame.left,
                    "width": frame.width,
                    "height": frame.height
                },
                "cid": cid_num
            };
            switch (component_type) {
                case "input": {
                    nativeInvoke("insertInput", args);
                }
                break;

                case "textArea": {
                    nativeInvoke("insertTextArea", args);
                }
                break;
    
                case "video": {
                    nativeInvoke("insertVideo", args);
                }
                break;
            }
        }
    }

    function nativeInvoke(api, args) {
        console.log(api, args);
        //TODO: judge platform iOS / Android
        var platform = "iOS";
        if (platform === "ios") {
            window.webkit.messageHandlers.handle.postMessage({
                "api": api,
                "args": args
            });
        }
    }   
    
    setTimeout("insertNativeComponents()", 100);
</script>

3. iOS端WKWebView消息代理接收到前端发来的消息

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
        // 收到前端的消息
        // message.name: handle
    // message.body: {"api":"insertInput","args":{"cid":"1","position":{"top":30,"width":200,"left":87.5,"height":40}}}
}

4. iOS端可以通过消息中的cid匹配查找相应WKChildScrollView,并将原生组件挂载到该节点上作为其子View.

图片

5. 最后我们发现WKWebView中插入的原生组件是无法响应交互事件的,需要特殊处理.

这里我们需要处理两件事,一是屏蔽WKContentView手势拦截事件响应链的传递,让事件可以传递到原生组件中,命令如下:

@implementation LVTongCengWebView

- (void)_handleWKContentGestrues {
    UIScrollView *webViewScrollView = self.scrollView;
    if ([webViewScrollView isKindOfClass:NSClassFromString(@"WKScrollView")]) {
        UIView *_WKContentView = webViewScrollView.subviews.firstObject;
        if (![_WKContentView isKindOfClass:NSClassFromString(@"WKContentView")]) return;
        NSArray *gestrues = _WKContentView.gestureRecognizers;
        for (UIGestureRecognizer *gesture in gestrues) {
            gesture.cancelsTouchesInView = NO;
            gesture.delaysTouchesBegan = NO;
            gesture.delaysTouchesEnded = NO;
        }
    }
}

@end

二是重写iOS端原生组件容器LVContainerView协议检测方法-conformsToProtocol:,目的是绕过WKNativelyInteractible协议检测,从而在点击WKWebView的时候,容器LVContainerView及其子view(原生组件)可以正常进行事件响应。

@implementation LVContainerView

- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
    if (aProtocol == NSProtocolFromString(@"WKNativelyInteractible")) {
        return YES;
    }
    return [super conformsToProtocol:aProtocol];
}

@end

到这里,经过前面这些步骤,我们就实现了iOS端WKWebView同层渲染,下面让我们来看看实现效果:

  1. 我们可以看到原生组件UITextFieldUITextView响应聚焦输入,UIButton可以点击变颜色,实现了正常交互;
  2. H5蒙层可以遮挡住原生组件,解决了WKWebView内嵌原生组件的层级限制问题,完美体现了同层的含义。
demo效果

Talk is cheap. Show me the code.
DEMO

极客时间-每日一课
iOS小程序同层渲染到底怎么实现?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270