JS原生互動

杭城小劉發表於2019-03-04

UIWebView載入網頁內容

可以通過本地檔案、url等方式。

NSString *htmlPath = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL fileURLWithPath:htmlPath]];
[self.webView loadRequest:request];
複製程式碼

Native呼叫JavaScript

Native呼叫JS是通過UIWebView的stringByEvaluatingJavaScriptFromString 方法實現的,該方法返回js指令碼的執行結果。

[webView stringByEvaluatingJavaScriptFromString:@"Math.random();"];
複製程式碼

實際上就是呼叫了網頁的Window下的一個物件。如果我們需要讓native端呼叫js方法,那麼這個js方法必須在window下可以訪問到。

JavaScript呼叫Native

反過來,JavaScript呼叫Native,並沒有現成的API可以呼叫,而是間接地通過一些其它手段來實現。UIWebView有個代理方法:在UIWebView內發起的任何網路請求都可以通過delegate函式在Native層得到通知。由此思路,我們就可以在UIWebView內發起一個自定義的網路請求,通常是這樣的格式:jsbridge://methodName?param1=value1&param2=value2…

在UIWebView的delegate函式中,我們判斷請求的scheme,如果request.URL.scheme是jsbridge,那麼就不進行網頁內容的載入,而是去執行相應的方法。方法名稱就是request.URL.host。引數可以通過request.URL.query得到。

問題來了??

發起這樣1個網路請求有2種方式。1:location.href .2:iframe。通過location.href有個問題,就是如果js多次呼叫原生的方法也就是location.href的值多次變化,Native端只能接受到最後一次請求,前面的請求會被忽略掉。

使用ifrmae方式,以呼叫Native端的方法。

var iFrame;
iFrame = document.createElement("iframe");
iFrame.style.height = "1px";
iFrame.style.width = "1px";
iFrame.style.display = "none";
iFrame.src = url;
document.body.appendChild(iFrame);
setTimeout(function(){
iFrame.remove();
},100);
複製程式碼

舉個?:

需求:

原生端提供一個UIWebView,載入一個網頁內容。還有1個按鈕,按鈕點選一下網頁增加一段段落文字。網頁上有2個輸入框,使用者輸入數字,點選按鈕,js將使用者輸入的引數告訴native端,native去執行加法,計算完成後將結果返回給js

//index.html


<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf8">
    <script language="javascript">
    function loadURL(url) {
        var iFrame;
        iFrame = document.createElement("iframe");
        iFrame.style.height = "1px";
        iFrame.style.width = "1px";
        iFrame.style.display = "none";
        iFrame.src = url;
        document.body.appendChild(iFrame);
        setTimeout(function() {
            iFrame.remove();
        }, 100);
    }


    function receiveValue(value) {
        alert("從原生拿到加法結果為:" + value);
    }

    function check() {
        var par1 = document.getElementById("par1").value;
        var par2 = document.getElementById("par2").value;
        loadURL("JSBridge://plus?par1=" + par1 + "&par2=" + par2);
    }
    </script>
</head>

<body>
    <input type="text" placeholder="請輸入數字" id="par1" /> +
    <input type="text" placeholder="請輸入數字" id="par2" />
    <input type="button" value="=" onclick="check()" />
</body>

</html>
複製程式碼
//ViewController.m

-(void)addContentToWebView{
    NSString *jsString = @" var pNode = document.createElement("p"); pNode.innerText = "我是由原生程式碼呼叫js後將一段檔案新增到html上,也就是注入";document.body.appendChild(pNode);";
    [self.webView stringByEvaluatingJavaScriptFromString:jsString];
}


-(NSInteger)plusparm:(NSInteger)par1 parm2:(NSInteger)par2{
    return par1 + par2;
}


#pragma mark -- UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
    NSURL *url = request.URL;
    NSString *scheme = url.scheme;
    NSString *method = url.host;
    NSString *parms =  url.query;
    NSArray *pars = [parms componentsSeparatedByString:@"&"];
    NSInteger par1 = [[pars[0] substringFromIndex:5] integerValue];
    NSInteger par2 = [[pars[1] substringFromIndex:5] integerValue];
    if ([scheme isEqualToString:@"jsbridge"]) {
        //發現scheme是JSBridge,那麼就是自定義的URLscheme,不去載入網頁內容而攔截去處理事件。
        
        if ([method isEqualToString:@"plus"]) {
            NSInteger result = [self plusparm:par1 parm2:par2];
            [self.webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"receiveValue(%@);",@(result)]];
        }
        
        return NO;
    }
    return YES;
}

複製程式碼

同步和非同步問題

js呼叫native是通過在一個網頁上插入一個iframe,這個iframe插入完了就完了,執行的結果需要native另外呼叫stringByEvaluatingJavaScriptString 方法通知js。這明顯是1個非同步的呼叫。而stringByEvaluatingJavaScriptString方法會返回執行js指令碼的結果。本質上是一個同步呼叫

所以js call native是非同步,native call js是同步。

相關文章