iOS開(kāi)發(fā)------Widget(Today Extension)插件化開(kāi)發(fā)

iOS10.0發(fā)布啦(貌似過(guò)去有點(diǎn)時(shí)間了吧 - -),在宏觀帶給我們使用體驗(yàn)的提升之外,更多的是帶給iOS開(kāi)發(fā)者一定的欣喜。

因?yàn)槲覀冇忠獙W(xué)習(xí)新東西來(lái)適配10啦。



博文所說(shuō)的Widget(以下稱之為拓展應(yīng)用)并不是iOS10系統(tǒng)新推出的插件化應(yīng)用(其實(shí)早在iOS8上就已經(jīng)出現(xiàn)啦,只不過(guò)樓主是在iOS10發(fā)布之后才算真正的關(guān)注它,實(shí)在是慚愧呀)。iOS10之前它僅僅是存在于通知那一欄中,至于多隱蔽我就不說(shuō)了吧。但在iOS10之后獲得重生,地位獲得了巨大的提升,從這點(diǎn)也不難看出蘋果增加了對(duì)它的重視。盡管公司的App沒(méi)有適配Widget,但作為一個(gè)“后知后覺(jué)”的iOS開(kāi)發(fā)者,注意到了但不研究一下就說(shuō)不過(guò)去了吧?

為了避免真實(shí)情況與博文的圖不太符合,這里聲明一下:樓主用的IDE是最新版的Xcode8.0(沒(méi)辦法,還是迫不及待的進(jìn)行了升級(jí)0.0),可能會(huì)與其他版本的Xcode界面不太一樣

博文中的所有代碼:https://github.com/RITL/WidgetDemo(如果有用請(qǐng)star支持一下,感謝)

預(yù)覽圖

這里附上Widget Demo中完成后的預(yù)覽圖: 這里會(huì)稍有不同,如果使用Xcode7及之前版本IDE編譯的應(yīng)用(后面稱作宿主應(yīng)用),那么找到Widget的方法如圖1;如果是Xcode8編譯的宿主應(yīng)用,那么可以直接通過(guò)3D Touch喚起Widget,當(dāng)然通過(guò)第一種也是可以的。不過(guò)兩者本質(zhì)是一樣的。

iOS10以下可以通過(guò)右滑以及下滑找到Widget

</img>


iOS10以及6s以上機(jī)型可通過(guò)3D Touch直接喚出

</img>


創(chuàng)建Widget Extension

1.首先創(chuàng)建一個(gè)新的Target: New->Target,Xcode8 會(huì)出現(xiàn)如下界面,選擇Today Extension,命名為WidgetExtension:

</img>

2、創(chuàng)建完畢,則會(huì)出現(xiàn)如下文件夾,名字什么的不是問(wèn)題,一般創(chuàng)建好的名字都為TodayViewController,我只不過(guò)是改了改名字而已O(∩_∩)O

</img>

3、這里啰嗦一句,雖然作為應(yīng)用的拓展,但這兩個(gè)應(yīng)用是“獨(dú)立”存在的,你也可以認(rèn)為這拓展應(yīng)用與宿主應(yīng)用是兩個(gè)完全獨(dú)立的應(yīng)用,這也就是說(shuō)明在開(kāi)發(fā)過(guò)程中會(huì)出現(xiàn)一些共享的問(wèn)題,不過(guò)共享問(wèn)題下面博文會(huì)有介紹。在此之前,對(duì)于拓展應(yīng)用,我們也是要去開(kāi)發(fā)者申請(qǐng)APP ID以及開(kāi)發(fā),發(fā)布證書(shū)的。

由于樓主只是為了學(xué)習(xí),用了Xcode8的Automatically manager signing,它的作用是自動(dòng)生成id以及證書(shū)。

作用細(xì)說(shuō)一點(diǎn)就是:如果開(kāi)發(fā)Team沒(méi)有相應(yīng)的APP ID,那么Xcode會(huì)自動(dòng)生成APP ID; 如果沒(méi)有創(chuàng)建相應(yīng)的證書(shū),那么它會(huì)自動(dòng)創(chuàng)建證書(shū) (當(dāng)然,正常開(kāi)發(fā)過(guò)程中,還是建議手動(dòng)去創(chuàng)建ID以及配置證書(shū)吧)

</img>

4、證書(shū)都配置完畢,運(yùn)行,添加Widget,就可以看到咱們的項(xiàng)目已經(jīng)具備了Widget的拓展功能,默認(rèn)的是MainInterface.storyboard上的內(nèi)容啦:(我改了改Label上的字,O(∩_∩)O)

</img>


布局方式interface builder or coding

如果牽扯到UI繪制的方式,這里只需要調(diào)整一點(diǎn)東西即可。Demo中樓主選用的是使用storyboard完成快速布局,當(dāng)然,如果開(kāi)發(fā)者習(xí)慣使用代碼來(lái)完成布局,依舊是可以的。需要對(duì)拓展應(yīng)用的info.plist文件做如下操作:

使用interface builder

這個(gè)是默認(rèn)的,如果修改了默認(rèn)的storyboard,只需要將NSExtensionMainStoryboard的value修改成相應(yīng)的storyboard名字即可

</img>

使用coding

首先將NSExtensionMainStoryboard字段刪除,添加NSExtensionPrincipalClass字典,value為主控制器的類名即可。

使用這個(gè)方法不要忘記在todayViewController的ViewDidLoad中設(shè)置preferredContentSize屬性調(diào)整大小。

</img>


數(shù)據(jù)共享

很多的時(shí)候我們需要Widget與宿主應(yīng)用共享一些數(shù)據(jù),想到數(shù)據(jù)共享,如果是單一的APP,我們的方法是很多的,比如單例,文件等形式,但由于拓展與宿主應(yīng)用是兩個(gè)完全獨(dú)立的App,并且iOS應(yīng)用基于沙盒的形式,所以一般的共享數(shù)據(jù)方法都是實(shí)現(xiàn)不了數(shù)據(jù)共享,這里就需要使用App Groups。

App Groups

1、首先需要在開(kāi)發(fā)者網(wǎng)站注冊(cè)一個(gè)App Groups


</img>

2、在 宿主應(yīng)用 以及 拓展應(yīng)用 中將App Groups打開(kāi),選中需要共享數(shù)據(jù)的group

</img>

兩種共享數(shù)據(jù)的方式


使用UserDefaults共享數(shù)據(jù)

NSUserDefaults大家應(yīng)該都是非常熟悉的了,通常用法就是

//獲取UserDefaults的單例對(duì)象,完成對(duì)應(yīng)用內(nèi)相關(guān)數(shù)據(jù)的持久化儲(chǔ)存
[NSUserDefaults standardUserDefaults];

正像之前所說(shuō),由于沙盒機(jī)制,拓展應(yīng)用是不允許訪問(wèn)宿主應(yīng)用的沙盒路徑的,因此上述用法是不對(duì)的,需要搭配app group完成實(shí)例化UserDefaults,使用UserDefaults類進(jìn)行數(shù)據(jù)共享樓主封裝為RITL_ShareDataDefaultsManager

通過(guò)groups實(shí)例化UserDefaults對(duì)象的代碼如下:

//組名
private static let groupIdentifier : String = "group.com.yue.WidgetTest"

/// 獲得userDefualt對(duì)象
private class func __userDefault() -> UserDefaults
{
    return UserDefaults(suiteName: RITL_ShareDataDefaultsManager.groupIdentifier)!
}

存儲(chǔ)數(shù)據(jù)方法如下,至于為什么會(huì)有open關(guān)鍵詞(與public作用是一樣的,只不過(guò)開(kāi)發(fā)文檔中新的API貌似都改為open了),因?yàn)闃侵髟贒emo中將該文件分離出來(lái)了,需要實(shí)現(xiàn)"不同命名空間"代碼共用,所以Swift默認(rèn)的Internal作用域就顯得權(quán)限不足了,至于如何分離下面會(huì)提及:

//存放數(shù)據(jù)的鍵值
private static let defaultKey : String = "com.yue.WidgetTest.value"

/// 保存數(shù)據(jù)
open class func saveData(_ value : String)
{
    //保存數(shù)據(jù)
    __userDefault().set(value, forKey: RITL_ShareDataDefaultsManager.defaultKey)
    __userDefault().synchronize()
}

獲取數(shù)據(jù)的方法與保存數(shù)據(jù)很像:

/// 獲取數(shù)據(jù)
open class func getData() -> String!
{
    //如果值為nil,表示沒(méi)有存過(guò)值,返回默認(rèn)的值
    let value = (__userDefault().value(forKey: RITL_ShareDataDefaultsManager.defaultKey))
    
    __userDefault().synchronize()
    
    guard value == nil else {
        
        return value as! String
    }

    return ""
}

因?yàn)槭峭ㄟ^(guò)文件來(lái)生成,所以必須要在必要的時(shí)候?qū)Υ鎯?chǔ)的數(shù)據(jù)進(jìn)行刪除,如下:

/// 清除數(shù)據(jù)
open class func clearData()
{
    __userDefault().removeSuite(named: RITL_ShareDataDefaultsManager.groupIdentifier)
    __userDefault().synchronize()
}


使用FileManager共享數(shù)據(jù)

第二種方法說(shuō)本質(zhì)的與第一種是一樣的,因?yàn)樗麄兌际峭ㄟ^(guò)在本地創(chuàng)建文件完成數(shù)據(jù)的共享,該功能的Demo中封裝成了RITL_ShareDataFileManager

與第一種不同的就是,它不但要實(shí)例化對(duì)象,還需要獲得保存數(shù)據(jù)的路徑,如下:

//組名
private static let groupIdentifier : String = "group.com.yue.WidgetTest"

//存儲(chǔ)的路徑
private static let dataSavePathFile : String = "Library/Caches/widgetTest"

/// 獲得存儲(chǔ)的路徑
private class func __fileManagerSavePath() -> URL
{
    //獲得當(dāng)前的組的路徑
    var url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: RITL_ShareDataFileManager.groupIdentifier)
    
    //返回拼接完畢的路徑
    url?.appendPathComponent(RITL_ShareDataFileManager.dataSavePathFile)
    
    return url!
}

保存數(shù)據(jù)的方法,因?yàn)樵赟wift中有的方法是throw異常的,所以寫(xiě)法稍有不同,如下:

/// 保存數(shù)據(jù)
open class func saveData(_ value:String) -> Bool
{
    //進(jìn)行存儲(chǔ)
    do {
        try value.write(to: __fileManagerSavePath(), atomically: true, encoding: String.Encoding.utf8)
        
    } catch _ as NSError {//出錯(cuò)
        
        return false
    }
    return true
}

獲取數(shù)據(jù)的方法只是讀取存放的數(shù)據(jù)即可,當(dāng)然Demo中存的是字符串,方法實(shí)現(xiàn)如下:

/// 獲取數(shù)據(jù)
open class func getData() -> String
{
    //用于接收數(shù)據(jù)
    var value : String
    
    do {//讀取數(shù)據(jù)
        try value = String(contentsOf: __fileManagerSavePath())
        
    } catch _ as NSError {
        
        return ""http://有誤輸出空字符串
    }
    return value
}

必要時(shí)候不要忘記清除數(shù)據(jù):

/// 清除數(shù)據(jù)
open class func clearData() -> Bool
{
//其實(shí)不太規(guī)范,應(yīng)該先判斷是否存在該文件,再進(jìn)行刪除
    do {//開(kāi)始刪除
        try FileManager.default.removeItem(at: __fileManagerSavePath())
        
    } catch _ as NSError{
        
        return false
    }
    return true
}


代碼共享

這里為什么會(huì)有代碼共享呢,如果上面兩個(gè)存儲(chǔ)的類寫(xiě)在了宿主應(yīng)用目錄下,那么宿主應(yīng)用使用是沒(méi)有問(wèn)題的,but,這個(gè)時(shí)候拓展應(yīng)用是獲取不到這兩個(gè)類的,當(dāng)然,如果每個(gè)應(yīng)用里都寫(xiě)一套不就可以了,雖然這樣也能解決問(wèn)題,但我很難用完美解決問(wèn)題來(lái)形容他,因?yàn)檫@樣不僅會(huì)出現(xiàn)命名,不好維護(hù)等眾多問(wèn)題,嚴(yán)重的時(shí)候還會(huì)帶來(lái)很多問(wèn)題,話不多說(shuō),如何共享代碼呢?

使用Framework

這個(gè)問(wèn)題在iOS8之后能夠完美的用framework來(lái)解決,(如果有人問(wèn)iOS7怎么辦?請(qǐng)面壁3秒鐘,Widget不是iOS8才對(duì)我們開(kāi)放的么0.0)

1、與創(chuàng)建拓展一樣,New->Target,選擇Cocoa Touch Framework來(lái)創(chuàng)建framework,Demo中命名隨便了一點(diǎn),起名為RITLKit

</img>

2、將需要共享的代碼從源項(xiàng)目的編譯源中刪除,添加到RITLKit中

</img>

3、將創(chuàng)建的framework都要鏈接到 宿主項(xiàng)目 以及 拓展應(yīng)用 的Linked Frameworks and Libraries中,不要忘了,都要添加,不然可能會(huì)出現(xiàn)找不到文件的問(wèn)題

</img>

4、這里提示一下,如果上面的步驟完成,但是在拓展中還是提示找不到文件,那么還需要做一個(gè)步驟,就是將我們的framework添加到拓展應(yīng)用中Allow app extension API only選中即可,將如下:

</img>

5、以上步驟完畢之后,應(yīng)該就可以在拓展以及宿主應(yīng)用中實(shí)現(xiàn)代碼共用了,但還有一點(diǎn):

如果是ObjC項(xiàng)目,導(dǎo)入Objc的文件,只需使用#import"XX.h"導(dǎo)入即可,但是如果framework中含有Swift文件,使用#import "Project-Swift.h"是導(dǎo)入不進(jìn)去項(xiàng)目的,可以使用@import RILTKit; 對(duì)創(chuàng)建的framework編譯的文件進(jìn)行導(dǎo)入,就可以使用Swift文件了,這件事在Demo中也已經(jīng)實(shí)現(xiàn)。


Extension與宿主應(yīng)用交互

通過(guò)點(diǎn)擊Widget上的按鈕來(lái)打開(kāi)宿主應(yīng)用并實(shí)現(xiàn)響應(yīng)操作也是一種重要的交互手段,如何實(shí)現(xiàn)呢?

1、首先我們需要在宿主應(yīng)用的Target->Info->URL Types中添加url Schemes

</img>

2、通過(guò)Widget來(lái)打開(kāi)宿主應(yīng)用,Demo中點(diǎn)擊Widget中的按鈕跳轉(zhuǎn)至不同的界面,通過(guò)Widget打開(kāi)宿主應(yīng)用的操作如下:

/// 打開(kāi)我的App
- (void)openMyApplication:(NSString *)title
{
    NSURL * url = [NSURL URLWithString:[NSString stringWithFormat:@"WidgetDemoOpenViewController://%@",title]];
    
   [self.extensionContext openURL:url completionHandler:^(BOOL success) {}];
}

3、宿主App通過(guò)AppDelegate中的響應(yīng)openUrl的代理方法,接收信息并發(fā)出通知來(lái)響應(yīng)全局:

/// 
-(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
    if ([url.scheme isEqualToString:@"WidgetDemoOpenViewController"])
    {
        NSLog(@"host = %@",url.host);
        
        //發(fā)送通知
        [[NSNotificationCenter defaultCenter] postNotificationName:@"ExtenicationNotification" object:url.host];
    }
    return false;
}

4、宿主應(yīng)用中響應(yīng)通知的控制器接收通知即可,比如Demo中是主頁(yè)進(jìn)行跳轉(zhuǎn):

//添加獲得拓展打開(kāi)基礎(chǔ)應(yīng)用的通知
[[NSNotificationCenter defaultCenter] addObserverForName:@"ExtenicationNotification" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
   
    //獲得類型
    NSString * type = note.object;
    
    [weakSelf presentTextController:type];
    
}];


NCWidgetProviding協(xié)議

如何仔細(xì)看,其實(shí)Widget的控制器與其他的控制器是沒(méi)有區(qū)別的,只不過(guò)它履行了一個(gè)叫做"NCWidgetProviding"的協(xié)議。協(xié)議方法不多,在iOS10中新增了一個(gè),廢棄了一個(gè),如下:

// 這個(gè)就不用多說(shuō)了吧,沒(méi)有很難得單詞哦0.0
typedef NS_ENUM(NSUInteger, NCUpdateResult) {
    NCUpdateResultNewData,
    NCUpdateResultNoData,
    NCUpdateResultFailed
} NS_ENUM_AVAILABLE_IOS(8_0);


/* 該方法是用來(lái)告知Widget控制器是否需要更新的一個(gè)協(xié)議方法 */
- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult result))completionHandler;

比如Demo中為了避免重復(fù)刷新做了如下操作:

- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler {
    // Perform any setup necessary in order to update the view.
    
    // If an error is encountered, use NCUpdateResultFailed
    // If there's no update required, use NCUpdateResultNoData
    // If there's an update, use NCUpdateResultNewData
    //獲得數(shù)據(jù)
    NSString * newValue = [RITL_ShareDataDefaultsManager getData];
    
    if ([newValue isEqualToString:self.textLabel.text])//表明沒(méi)有更新
    {
        completionHandler(NCUpdateResultNoData);
    }
    
    else//需要刷新
    {
        completionHandler(NCUpdateResultNewData);
    }
}
// iOS10 版本之后將不會(huì)再被喚起
// 用來(lái)設(shè)置Widget控制器邊框間距的方法,如果出現(xiàn)偏差,可以調(diào)整此方法的返回值進(jìn)行操作
- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets NS_DEPRECATED_IOS(8_0, 10_0, "This method will not be called on widgets linked against iOS versions 10.0 and later.");


// iOS10 新增的方法
// 用來(lái)設(shè)置Widget是展開(kāi)還是折疊狀態(tài)的方法,可以設(shè)置相關(guān)的preferredContentSizes屬性修改大小
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize NS_AVAILABLE_IOS(10_0);


保存數(shù)據(jù)的時(shí)機(jī)

這個(gè)看具體的需求,比如Demo中就是選擇在宿主應(yīng)用將要失去Active狀態(tài)的時(shí)候進(jìn)行數(shù)據(jù)的保存,實(shí)現(xiàn)如下:

    //獲得失去前臺(tái)的監(jiān)聽(tīng)
    [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {//進(jìn)行數(shù)據(jù)的保存
        
        //保存當(dāng)前的數(shù)據(jù)
#ifdef RITL_ShareDataType_UserDefaults
        //第一種保存數(shù)據(jù)
        [RITL_ShareDataDefaultsManager saveData:weakSelf.mainTextField.text];
        
#else
        //第二種保存數(shù)據(jù)
        [RITL_ShareDataFileManager saveData:weakSelf.mainTextField.text];
#endif
        
    }];


Widget無(wú)法展開(kāi)折疊問(wèn)題

2016-09-24補(bǔ)充

之前丟了一點(diǎn),也有小伙伴們問(wèn),就是說(shuō)按照上面的方式來(lái)開(kāi)發(fā)插件,不能折疊的問(wèn)題.

解決方案:

//在TodayViewController的ViewDidLoad里面需要設(shè)置最大展示的類型
#ifdef __IPHONE_10_0 //因?yàn)槭莍OS10才有的,還請(qǐng)記得適配
    //如果需要折疊
    self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;
#endif


Widget高度范圍

2016-09-28補(bǔ)充

首先感謝一下CSDN用戶qq_29012653的提示,之前沒(méi)有太注意,在協(xié)議方法widgetActiveDisplayModeDidChange:withMaximumSize:打印了一下各種狀態(tài)下的MaxSize:

// 5s模擬器下:
// NCWidgetDisplayModeCompact模式下:{304, 110}
// NCWidgetDisplayModeExpanded模式下:{304, 528}

// 6s模擬器下:
// NCWidgetDisplayModeCompact模式下:{359, 110}
// NCWidgetDisplayModeExpanded模式下:{359, 616}

//從上面來(lái)看,雖說(shuō)是最大的Size,但不得不說(shuō),蘋果還是把Widget的高度范圍限制在了110 ~ 最大值之間
//如果設(shè)置高度小于110,那么default = 110;
//如果設(shè)置高度大于最大的616,那么default = 最大值;

但折疊狀態(tài)下,可能是出于規(guī)范的考慮,蘋果是果斷的將高度固定在110上,所以這個(gè)時(shí)候我們?cè)O(shè)置preferredContentSize屬性是沒(méi)有任何效果的。

更多的歡迎下載Github代碼一起鉆研~3Q

感謝一下博文對(duì)我的幫助,感謝
iOS開(kāi)發(fā)之widget實(shí)現(xiàn)
WWDC 2014 Session筆記 - iOS 通知中心擴(kuò)展制作入門

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

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