WebView與JS的幾種交互

最近整理了一下原生與H5之間的交互方式,簡單的做個總結(jié)。
OC端與JS的交互,大致有這幾種:攔截協(xié)議、JavaScriptCore庫、WKWebView、自定義NSURLProtocol攔截、WebViewJavascriptBridge。

  1. JavaScriptCore一個iOS7引進的標準庫,iOS7以前也有開發(fā)者自行導入使用。Web端也比較容易統(tǒng)一。
  2. WebViewJavascriptBridge是一個第三方庫,其原理還是使用了對web view的請求攔截,支持WKWebview。封裝了完整的OC與JS相互調(diào)用的方法,不過需要Web端配合編寫相應的方法。安卓方面聽說有一個同名的庫,如果不是統(tǒng)一使用的話,需要Web端寫2套JS的話,那就有點蛋疼了。
  3. WKWebView,iOS8加入的WebKit。相對于UIWebView,具有更強大的功能。提供一個WKScriptMessageHandler,可以實現(xiàn)JS對WebView的調(diào)用。
  4. 協(xié)議的攔截,比較常用的一種方式。
  5. 在自定義NSURLProtocol中,攔截請求,也可以實現(xiàn)相應的方法調(diào)用。

1. JavaScriptCore


JavaScriptCore中類及協(xié)議:

  • JSContext:給JavaScript提供運行的上下文環(huán)境,通過-evaluateScript:方法就可以執(zhí)行一JS代碼
  • JSValue:封裝了JS與ObjC中的對應的類型,以及調(diào)用JS的API等
  • JSManagedValue:管理數(shù)據(jù)和方法的類
  • JSVirtualMachine:處理線程相關,使用較少
  • JSExport:這是一個協(xié)議,如果采用協(xié)議的方法交互,自己定義的協(xié)議必須遵守此協(xié)議,在協(xié)議中聲明的API都會在JS中暴露出來,才能調(diào)用

對于JSContext和JSValue的更多使用方式可以看下這篇,介紹的比較完整ios7 JavaScriptCore.framework 本文主要簡單總結(jié)下交互相關內(nèi)容。

ObjC調(diào)用JS

在JavaScriptCore中提供的調(diào)用JS的方法
- (JSValue *)evaluateScript:(NSString *)script;方法就可以執(zhí)行一段JavaScript腳本,并且如果其中有方法、變量等信息都會被存儲在其中以便在需要的時候使用。
JSValue提供了- (JSValue *)callWithArguments:(NSArray *)arguments;方法可以反過來將參數(shù)傳進去來調(diào)用方法

 // 一個JSContext對象,就類似于Js中的window,
 // 只需要創(chuàng)建一次即可。
JSContext *context = [[JSContext alloc] init];
// 執(zhí)行一段js
[context evaluateScript:@"function add(a, b) { return a + b; }"];
// 根據(jù)下標取出方法
JSValue *add = context[@"add"];
NSLog(@"Func: %@", add);
// 傳入?yún)?shù) 調(diào)用取到的方法
JSValue *sum = [add callWithArguments:@[@(7), @(21)]];
NSLog(@"Sum: %d",[sum toInt32]);
//OutPut:
// Func: function add(a, b) { return a + b; }
// Sum: 28

再來個栗子

 self.jsContext = [[JSContext alloc] init];

 [self.jsContext evaluateScript:@"var num = 10"];
 [self.jsContext evaluateScript:@"var squareFunc = function(value) { return value * 2 }"];
 // 調(diào)用 計算面積
 JSValue *square = [self.jsContext evaluateScript:@"squareFunc(num)"];

 // 可以通過下標的方式獲取到方法
 JSValue *squareFunc = self.jsContext[@"squareFunc"];
 // 傳入?yún)?shù) 調(diào)用取到的方法
 JSValue *value = [squareFunc callWithArguments:@[@"20"]];
 NSLog(@"%@", square.toNumber);
 NSLog(@"%@", value.toNumber); 

JS調(diào)用OC

使用JavaScriptCore進行原生與js的交互主要是2種方式,block注入模型使用協(xié)議代理

Block方式

JSContext *context = [[JSContext alloc] init];
// 定義一個block
context[@"log"] = ^() {
  NSLog(@"+++++++Begin Log+++++++");  
               
  NSArray *args = [JSContext currentArguments];
  for (JSValue *jsVal in args) {
   NSLog(@"%@", jsVal);
  }
  
  JSValue *this = [JSContext currentThis];
  NSLog(@"this: %@",this);
  NSLog(@"-------End Log-------");
};
// 調(diào)用js執(zhí)行l(wèi)og方法
 [context evaluateScript:@"log('ider', [7, 21],     
 { hello:'world', js:100 });"];

當web端調(diào)用log方法,傳入相關參數(shù),就能調(diào)用OC端的block。實現(xiàn)交互。

通過注入模型的方式交互

  • OC端要做的事情

首先,我們自定義一個協(xié)議,而且這個協(xié)議必須要遵守JSExport協(xié)議
協(xié)議暴露的方法,是供JS調(diào)用的方法。還可以實現(xiàn)回調(diào)

@protocol JavaScriptObjectiveCDelegate <JSExport>

// JS調(diào)用此方法來調(diào)用OC的share
- (void)share:(NSDictionary *)params ;

// JS調(diào)用此方法來調(diào)用OC的相機
- (void)callCamera ;

// 在JS中調(diào)用時,多個參數(shù)需要使用駝峰方式
// 這里是多個個參數(shù)的。
- (void)showAlert:(NSString *)title msg:(NSString *)msg;

// 通過JSON傳過來
- (void)callWithDict:(NSDictionary *)params;
// JS調(diào)用Oc,然后在OC中通過調(diào)用JS方法來傳值給JS。
- (void)jsCallObjcAndObjcCallJsWithDict:(NSDictionary *)params;

@end

接下來,我們還需要定義一個模型:

// 此模型用于注入JS的模型,這樣就可以通過模型來調(diào)用方法。
@interface HYBJsObjCModel : NSObject <JavaScriptObjectiveCDelegate>

@property (nonatomic, weak) JSContext *jsContext;
@property (nonatomic, weak) UIWebView *webView;

@end

模型的實現(xiàn):

@implementation HYBJsObjCModel

- (void)share:(NSDictionary *)params {
 NSLog(@"Js調(diào)用了OC的share方法,參數(shù)為:%@", params);
}

- (void)callWithDict:(NSDictionary *)params {
 NSLog(@"Js調(diào)用了OC的方法,參數(shù)為:%@", params);
}

// JS調(diào)用了callCamera
- (void)callCamera {
 NSLog(@"JS調(diào)用了OC的方法,調(diào)起系統(tǒng)相冊");

 // JS調(diào)用后OC后,可以傳一個回調(diào)方法的參數(shù),進行回調(diào)JS
 JSValue *jsFunc = self.jsContext[@"jsFunc"];
 [jsFunc callWithArguments:nil];
}

- (void)jsCallObjcAndObjcCallJsWithDict:(NSDictionary *)params {
 NSLog(@"jsCallObjcAndObjcCallJsWithDict was called, params is %@", params);

 // 調(diào)用JS的方法
 JSValue *jsParamFunc = self.jsContext[@"jsParamFunc"];
 [jsParamFunc callWithArguments:@[@{@"age": @10, @"name": @"lili", @"height": @158}]];
}

// 指定參數(shù)的用法
// 在JS中調(diào)用時,函數(shù)名應該為showAlertMsg(arg1, arg2)
- (void)showAlert:(NSString *)title msg:(NSString *)msg {
 dispatch_async(dispatch_get_main_queue(), ^{
   UIAlertView *a = [[UIAlertView alloc] initWithTitle:title message:msg delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil];
   [a show];
 });
}
@end

JavaScriptCore使用注意

JavaScript調(diào)用本地方法是在子線程中執(zhí)行的,這里要根據(jù)實際情況考慮線程之間的切換。

模型實現(xiàn)完了,在哪里注入呢。在controller的webView加載完成后
我們是通過webView的valueForKeyPath獲取的,其路徑為documentView.webView.mainFrame.javaScriptContext
這樣就可以獲取到JS的context,然后為這個context注入我們的模型對象。

#pragma mark - UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView 
{
 self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
  // 通過模型調(diào)用方法,這種方式更好些。
  HYBJsObjCModel *model  = [[HYBJsObjCModel alloc] init];
  // 模型
  self.jsContext[@"OCModel"] = model;
  model.jsContext = self.jsContext;
  model.webView = self.webView;
  // 增加異常的處理
  self.jsContext.exceptionHandler = ^(JSContext *context,   
 JSValue *exceptionValue) {
    context.exception = exceptionValue;
    NSLog(@"異常信息:%@", exceptionValue);
 };
}

另外關于模型,也可根據(jù)需求直接將模型作為controller,去實現(xiàn)相關的方法實現(xiàn),省去模型這一層。 如下:

#pragma mark - UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView 
{
  self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; 
  self.jsContext[@"OCModel"] = self; 
  self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
  context.exception = exceptionValue; 
  NSLog(@"異常信息:%@", exceptionValue);
 };
}

  • WEB端

代碼內(nèi)容

<!DOCTYPE html>
<html>
<head lang="en">
     <meta charset="UTF-8">
</head>
<body>
     <div style="margin-top: 100px"> 
        <h1>Objective-C和JavaScript交互的那些事</h1> 
        <input type="button" value="CallCamera" onclick="OCModel.callCamera()"> 
      </div>

       <div>
         <input type="button" value="Share" onclick="callShare()"> 
       </div>
<script>
       var callShare = function() { 
          OCModel.share({'title': '標題', 'desc': '內(nèi)容', 'shareUrl': 'http://www.lxweimin.com/p/f896d73c670a');
       }

</script>
</body>
</html>

注意下 如果調(diào)用的方法是多個參數(shù)的,必須使用駝峰寫法并去掉冒號

- (void)showAlert:(NSString *)title msg:(NSString *)msg;
需要這樣調(diào)用
 OCModel. showAlertMsg('title','msg');

以上就是簡單的利用JavaScriptCore framework進行JS交互的用法,
感謝
iOS與JS交互實戰(zhàn)篇(ObjC)
Objective-C與JavaScript交互的那些事
提供的資料參考,僅僅是做個總結(jié)

2. WebViewJavascriptBridge

這個第三方庫起先是在UIWebView與JS的深度交互大神文中知悉。其還是使用攔截WebView請求方法,但是做了完整的封裝后,使用起來還是很簡單的。

1) 導入

#import "WKWebViewJavascriptBridge.h"

2) 初始化

self.bridge = [WebViewJavascriptBridge bridgeForWebView:webView];
// 開啟日志,方便調(diào)試
[WebViewJavascriptBridge enableLogging];

3) Web端setupWebViewJavascriptBridge

function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
window.WVJBCallbacks = [callback];
var WVJBIframe = document.createElement('iframe');
WVJBIframe.style.display = 'none';
WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__';
document.documentElement.appendChild(WVJBIframe);
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}

4)call setupWebViewJavascriptBridge

setupWebViewJavascriptBridge(function(bridge) {

/* Initialize your app here */

bridge.registerHandler('JS Echo', function(data, responseCallback) {
    console.log("JS Echo called with:", data)
    responseCallback(data)
})
bridge.callHandler('ObjC Echo', function responseCallback(responseData) {
    console.log("JS received response:", responseData)
})
})

  • ObjC API

OC端初始化時 默認消息處理器

實例化WebViewJavascriptBridge并定義native端的默認消息處理器。

JS調(diào)用bridge.send()即可觸發(fā)默認處理。

   _bridge = [WebViewJavascriptBridge bridgeForWebView:webView handler:^(id data, WVJBResponse *response) {  
       NSLog(@"ObjC received message from JS: %@", data);  
       UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"ObjC got message from Javascript:" message:data delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];  
       [alert show];  
   }];

OC端調(diào)用[self.bridge send];即可觸發(fā)JS端的默認處理。

    [self.bridge send:@"Give me a response, will you?" responseCallback:^(id responseData) {
    NSLog(@"ObjC got its response! %@", responseData);
    }];

OC端registerHandler接收JS調(diào)用

在JS中調(diào)用了bridge.callHandler('getScreenHeight')就會觸發(fā)OC注冊的對應的handler,responseCallback中回調(diào)JS傳遞參數(shù)

    [self.bridge registerHandler:@"getScreenHeight" handler:^(id data, WVJBResponseCallback responseCallback) {
      NSLog(@"ObjC Echo called with: %@", data);
      responseCallback([NSNumber numberWithInt:[UIScreen              
      mainScreen].bounds.size.height]);
    }];

或者 JS傳遞data給OC,OC打印

    [self.bridge registerHandler:@"log" handler:^(id data, WVJBResponseCallback responseCallback) {
    NSLog(@"Log: %@", data);
    }];

OC端callHandler調(diào)用JS

調(diào)用JS showAlert,傳遞data

[self.bridge callHandler:@"showAlert" data:@"Hi from ObjC to JS!"];

調(diào)用JS getCurrentPageUrl,在block中獲取參數(shù)

[self.bridge callHandler:@"getCurrentPageUrl" data:nil responseCallback:^(id responseData) {
    NSLog(@"Current UIWebView page URL is: %@", responseData);
}];
還可設置代理監(jiān)聽
[bridge setWebViewDelegate:(UIWebViewDelegate*)webViewDelegate];
  • Javascript API

JS registerHandler接收OC調(diào)用

注冊handle,OC可以通過[bridge callHandler:"handlerName" data:@"Foo"][bridge callHandler:"handlerName" data:@"Foo" responseCallback:^(id responseData) { ... }]進行調(diào)用JS

OC傳遞data進行調(diào)用

bridge.registerHandler("showAlert", function(data) { alert(data) })

參數(shù)結(jié)果回傳給OC

bridge.registerHandler("getCurrentPageUrl", function(data, responseCallback) {
responseCallback(document.location.toString())
})
bridge.callHandler("handlerName", data)

JS 調(diào)用OC

JS調(diào)用bridge.callHandler("handlerName", data)bridge.callHandler("handlerName", data, function responseCallback(responseData) { ... })

調(diào)用OC端打印

bridge.callHandler("Log", "Foo")

調(diào)用OC端獲取高度,在block中使用

bridge.callHandler("getScreenHeight", null, function(response) {
alert('Screen height:' + response)
})

3.WKWebView - iOS8 or Later

iOS8,蘋果新推出了WebKit,用WKWebView代替UIWebView和WebView。相關的使用和特性可以細讀。
WKWeb?View
iOS 8 WebKit框架概覽(下)
WKWebView特性及使用

  • WKWebView新特性

    性能、穩(wěn)定性、功能大幅度提升
    允許JavaScript的Nitro庫加載并使用(UIWebView中限制)
    支持了更多的HTML5特性
    高達60fps的滾動刷新率以及內(nèi)置手勢
    GPU硬件加速
    KVO
    重構(gòu)UIWebView成14類與3個協(xié)議,查看官方文檔

需要注意的是WKWebView貌似不支持NSURLProtocol和NSURLCache。不能做緩存的話,就蛋疼了。

關于WKWebView的代理方法 這篇有比較完整的介紹
http://www.lxweimin.com/p/1d7a8525ad16

下面是相關的交互方法


  • app調(diào)js方法

WKWebView調(diào)用js方法和UIWebView類似,一個是evaluateJavaScript,一個是stringByEvaluatingJavaScriptFromString。獲取返回值的方式不同,WKWebView用的是回叫函數(shù)獲取返回值

 //直接調(diào)用js
    webView.evaluateJavaScript("hi()", completionHandler: nil)
//調(diào)用js帶參數(shù)
    webView.evaluateJavaScript("hello('liuyanwei')", completionHandler: nil)
// 調(diào)用js獲取返回值
   webView.evaluateJavaScript("getName()") { (any,error) -> Void in
        NSLog("%@", any as! String)
    }
  • js調(diào)app方法

UIwebView沒有js調(diào)app的方法,而在WKWebView中有了改進。具體步驟分為app注冊handler,app處理handler委托,js調(diào)用三個步驟

  1. 注冊handler需要在webView初始化之前,如示例,注冊了一個webViewApp的handler
        config = WKWebViewConfiguration()
         //注冊js方法
        config.userContentController.addScriptMessageHandler(self, name: "webViewApp")
         // 初始化
        webView = WKWebView(frame: self.webWrap.frame, configuration: config)
  1. 處理handler委托。ViewController實現(xiàn)WKScriptMessageHandler委托的func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage)代理方法。在里面處理事件。
        //實現(xiàn)WKScriptMessageHandler委托
        class ViewController:WKScriptMessageHandler
        //實現(xiàn)js調(diào)用ios的handle委托
        func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {
        //接受傳過來的消息從而決定app調(diào)用的方法
         let dict = message.body as! Dictionary<String,String>
         let method:String = dict["method"]!
         let param1:String = dict["param1"]!
         if method=="hello"{
             hello(param1)
           }
         }
  1. js調(diào)用 。通過window.webkit.messageHandlers.webViewApp找到之前注冊的handler對象,然后調(diào)用postMessage方法把數(shù)據(jù)傳到app,app通過上一步的方法解析方法名和參數(shù)。webViewApp是之前注冊的name

     var message = {
                     'method' : 'hello',
                     'param1' : 'liuyanwei',
                     };
     window.webkit.messageHandlers.webViewApp.postMessage(message);        
    

如果需要app對js的調(diào)用有所響應,可以通過回叫函數(shù)的方式回應js。可以在調(diào)用app的時候增加一個js回叫函數(shù)名 ,app在處理完之后可以呼叫回叫函數(shù)并把需要的參數(shù)通過回叫函數(shù)的方式進行傳遞

  • 使用用戶腳本來注入 JavaScript

WKUserScript 允許在正文加載之前或之后注入到頁面中。這個強大的功能允許在頁面中以安全且唯一的方式操作網(wǎng)頁內(nèi)容。

一個簡單的例子如下,用戶改變背景的用戶腳本被插入到網(wǎng)頁中:

let source = "document.body.style.background = \"#777\";"
let userScript = WKUserScript(source: source,   injectionTime: .AtDocumentEnd, forMainFrameOnly: true)

let userContentController = WKUserContentController()
userContentController.addUserScript(userScript)

let configuration = WKWebViewConfiguration()
configuration.userContentController = userContentController
self.webView = WKWebView(frame: self.view.bounds, configuration: configuration)

WKUserScript 對象可以以 JavaScript 源碼形式初始化,初始化時還可以傳入是在加載之前還是結(jié)束時注入,以及腳本影響的是這個布局還是僅主要布局。于是用戶腳本被加入到 WKUserContentController 中,并且以 WKWebViewConfiguration 屬性傳入到 WKWebView 的初始化過程中。

4. 攔截協(xié)議

最簡單也是最容易想到的一種
UIWebView的代理方法,web view發(fā)出請求后攔截,查看是否為約定的協(xié)議,采取處理。

-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSString *url = request.URL.absoluteString;
    if ([url rangeOfString:@"camera://"].location != NSNotFound) {
        // url的協(xié)議頭是camera
        NSLog(@"callCamera");
        return NO;
    }
    return YES;
}

WKWebView

-(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    NSString *url = navigationAction.request.URL.absoluteString;
    NSLog(@"%@",url);
    
    if (navigationAction.navigationType == WKNavigationTypeLinkActivated  && [url rangeOfString:@"camera://"].location != NSNotFound)
    {
        // url的協(xié)議頭是camera
        NSLog(@"callCamera");
        decisionHandler(WKNavigationActionPolicyCancel);
        
        // dosomthing。。。
    }
    else
    {
        decisionHandler(WKNavigationActionPolicyAllow);
    }
   
}

5. NSURLProtocol攔截

這種方式也是最近才看到,原本利用自定義NSURLProtocol來做緩存處理。相關的文章可以看:
NSURLProtocol和NSRunLoop的那些坑
iOS中的 NSURLProtocol
在自定義的Protocol的- (void)startLoading方法中,可以攔截到請求。一般會在這里做緩存的判斷與讀取處理。在此處,也可以判斷約定的協(xié)議,然后發(fā)送通知,客戶端就可以接收到通知,執(zhí)行相應的方法。

- (void)startLoading
{
    NSString * url = [[[self request] URL] absoluteString];
    
    if([url hasPrefix:@"LocalActions/"])
    {
        NSString * actname = [url stringByReplacingOccurrencesOfString:@"LocalActions/" withString:@"LocalAction_"];
  
        // 發(fā)送通知  客戶端就可執(zhí)行方法
        [[NSNotificationCenter defaultCenter] postNotificationName:actname object:nil];
    }
}

需要注意的是WKWebView貌似不支持NSURLProtocol和NSURLCache。不能做緩存的話,就蛋疼了。

相關參考
iOS與JS交互實戰(zhàn)篇(ObjC版)
Objective-C與JavaScript交互的那些事

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內(nèi)容

  • 前言 關于UIWebView的介紹,相信看過上文的小伙伴們,已經(jīng)大概清楚了吧,如果有問題,歡迎提問。 本文是本系列...
    Dark_Angel閱讀 28,978評論 67 291
  • 前言 關于UIWebView的介紹,相信看過上文的小伙伴們,已經(jīng)大概清楚了吧,如果有問題,歡迎提問。 本文是本系列...
    CoderLF閱讀 9,003評論 2 12
  • 通過學習,你將會學習以下幾個方面的內(nèi)容: **什么是WKWebView以及它和UIWebView的區(qū)別是什么 **...
    SOI閱讀 11,684評論 18 42
  • 隨著H5技術的興起,在iOS開發(fā)過程中,難免會遇到原生應用需要和H5頁面交互的問題。其中會涉及方法調(diào)用及參數(shù)傳值等...
    Chris_js閱讀 3,102評論 1 8
  • 隨著中秋國慶的到來,公司的運營要搞一系列活動,這就需要服務端提供數(shù)據(jù)支持,iOS、Android要提供相應的入口及...
    伍驍辛閱讀 4,330評論 1 40