swift實(shí)現(xiàn)一個(gè)與智能機(jī)器人聊天的app(四)

終于有時(shí)間繼續(xù)寫我的文章了,這段時(shí)間在趕學(xué)校的軟件課程設(shè)計(jì),可算弄完了!
下面繼續(xù)我們的創(chuàng)造之旅~

本篇文章你會學(xué)到

  • 用KVO方法優(yōu)化鍵盤彈出動畫
  • 將同步下載消息改為異步,以減輕主線程的壓力。
  • 實(shí)現(xiàn)app登錄、注冊的功能
    首先下載本章源代碼:
    百度網(wǎng)盤地址
    在上一章結(jié)尾我提到:
    我們的app在鍵盤彈出時(shí)有一些問題:
  • 在我們點(diǎn)出鍵盤時(shí)會遮擋消息:


    iOS Simulator Screen Shot 2015年9月8日 下午4.14.55.png
  • 鍵盤彈出時(shí)把tableView拉到底部會有一個(gè)很難看的空白:
    iOS Simulator Screen Shot 2015年9月8日 下午4.15.21.png

    下面我們來解決它,我們需要在鍵盤彈出時(shí)修改tableView的一些屬性和約束條件,所以我們需要在鍵盤彈出時(shí)得到通知,要做到這個(gè),我們要使用KVO(Key-Value Observing)方法。
    viewDidLoad()中的結(jié)尾添加以下代碼來添加鍵值監(jiān)控:
        let notificationCenter = NSNotificationCenter.defaultCenter()
        notificationCenter.addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
        notificationCenter.addObserver(self, selector: "keyboardDidShow:", name: UIKeyboardDidShowNotification, object: nil)

首先獲取通知中心的實(shí)例,然后添加兩個(gè)觀察者,第一個(gè)用來監(jiān)控UIKeyboardWillShowNotification鍵值的變化,這是系統(tǒng)提供的鍵值,當(dāng)鍵盤將要彈出時(shí)會改變;第二個(gè)監(jiān)控 UIKeyboardDidShowNotification,同樣地,這也是系統(tǒng)提供的,當(dāng)鍵盤完全彈出時(shí)會改變。
當(dāng)這兩個(gè)鍵值改變時(shí),會向通知中心發(fā)送通知,然后由我們自定義的兩個(gè)selector方法處理通知,下面定義這兩個(gè)方法。
首先第一個(gè)方法:

    func keyboardWillShow(notification: NSNotification) {
        
        let userInfo = notification.userInfo as NSDictionary!
        let frameNew = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
        let insetNewBottom = tableView.convertRect(frameNew, fromView: nil).height
        let insetOld = tableView.contentInset
        let insetChange = insetNewBottom - insetOld.bottom
        let overflow = tableView.contentSize.height - (tableView.frame.height-insetOld.top-insetOld.bottom)
        
        let duration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
        let animations: (() -> Void) = {
            if !(self.tableView.tracking || self.tableView.decelerating) {
                // 根據(jù)鍵盤位置調(diào)整Inset
                if overflow > 0 {
                    self.tableView.contentOffset.y += insetChange
                    if self.tableView.contentOffset.y < -insetOld.top {
                        self.tableView.contentOffset.y = -insetOld.top
                    }
                } else if insetChange > -overflow {
                    self.tableView.contentOffset.y += insetChange + overflow
                }
            }
        }
        if duration > 0 {
            let options = UIViewAnimationOptions(UInt((userInfo[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber).integerValue << 16)) // http://stackoverflow.com/a/18873820/242933
            UIView.animateWithDuration(duration, delay: 0, options: options, animations: animations, completion: nil)
        } else {
            animations()
        }
    }

很難懂?不著急,我們一步一步解釋這些代碼!
首先取出通知的userifno,鍵盤的所有屬性都在這里面,他是一個(gè)字典類型的數(shù)據(jù):

  let userInfo = notification.userInfo as NSDictionary!

然后通過UIKeyboardFrameEndUserInfoKeykey取出鍵盤的位置、大小信息,也就是frame,并將其的參考view設(shè)置為tableView,記錄下它的高度

 let frameNew = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
  let insetNewBottom = tableView.convertRect(frameNew, fromView: nil).height

然后我們需要計(jì)算一些數(shù)據(jù):

 let insetOld = tableView.contentInset
 let insetChange = insetNewBottom - insetOld.bottom
 let overflow = tableView.contentSize.height - (tableView.frame.height-insetOld.top-insetOld.bottom)

insetChange指的是那部分呢?我畫出一個(gè)圖大家就明白了:

insetChange

tableview的contentInset所指的是所圖的紅框部分。
overflow指的是所有消息的總高度和鍵盤彈出前contentInset的差值,實(shí)際上就是沒有顯示部分的高度,也就是溢出的部分。
然后通過UIKeyboardAnimationDurationUserInfoKey
key來得到鍵盤彈出動畫的持續(xù)時(shí)間,設(shè)置自定義的動畫閉包:

        let duration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
        let animations: (() -> Void) = {
            if !(self.tableView.tracking || self.tableView.decelerating) {
                // 根據(jù)鍵盤位置調(diào)整Inset
                if overflow > 0 {
                    self.tableView.contentOffset.y += insetChange
                    if self.tableView.contentOffset.y < -insetOld.top {
                        self.tableView.contentOffset.y = -insetOld.top
                    }
                } else if insetChange > -overflow {
                    self.tableView.contentOffset.y += insetChange + overflow
                }
            }
        }

我們看一下動畫閉包內(nèi)部做了些什么。
首先判斷tableView的滾動是否停止了,如果沒有停止?jié)L動就不做任何事情。
tableView的滾動有兩種情況:

  1. 手指點(diǎn)擊tableView,開始滾動,即tracking
  2. 手指抬起,tableView還會有一段減速滾動,也就是decelerating
  if !(self.tableView.tracking || self.tableView.decelerating){
.....
.....
}

如果溢出大于0,則將tableView當(dāng)前位置contentOffset向下移動,也就對應(yīng)著手指向上拖動insetChange的高度,這樣可以保證消息和鍵盤同時(shí)向上移動,但是如果滾動之后仍然是負(fù)值,且超出insetOld.top的距離,也就是導(dǎo)航欄的高度,就把tableView的當(dāng)前位置設(shè)置成屏幕之上一個(gè)導(dǎo)航欄的高度。
如果溢出是負(fù)值,但是絕對值小于insetChange,則contenOffset.y增加兩者的差值。
當(dāng)時(shí)長大于0時(shí)真正執(zhí)行我們的動畫閉包,否則就即時(shí)執(zhí)行閉包:

        if duration > 0 {
            let options = UIViewAnimationOptions(UInt((userInfo[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber).integerValue << 16)) // http://stackoverflow.com/a/18873820/242933
            UIView.animateWithDuration(duration, delay: 0, options: options, animations: animations, completion: nil)
        } else {
            animations()
        }

其中要注意的是,我們的動畫曲線要和鍵盤彈出動畫的曲線相同,所以要用 UIKeyboardAnimationCurveUserInfoKeykey得到曲線信息,這里的類型轉(zhuǎn)換比較麻煩,要進(jìn)行左移16的位運(yùn)算,因?yàn)闆]有對應(yīng)的 as類型轉(zhuǎn)換可用,只能用最底層的方式。
為什么要這樣呢,其實(shí)我也不知道。。我也是查來的= =
stackoverflow
第二個(gè)方法,是用來防止出現(xiàn)底下的白邊,原理就是限制顯示出的高度,將底部切掉一部分,也就是將contenInset.bottom值變大一些,變大為鍵盤的高度:

   func keyboardDidShow(notification: NSNotification) {
        let userInfo = notification.userInfo as NSDictionary!
        let frameNew = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
        let insetNewBottom = tableView.convertRect(frameNew, fromView: nil).height
        
        //根據(jù)鍵盤高度設(shè)置Inset
        let contentOffsetY = tableView.contentOffset.y
        tableView.contentInset.bottom = insetNewBottom
        tableView.scrollIndicatorInsets.bottom = insetNewBottom
        // 優(yōu)化,防止鍵盤消失后tableview有跳躍
        if self.tableView.tracking || self.tableView.decelerating {
            tableView.contentOffset.y = contentOffsetY
        }
    }

這樣就好了,運(yùn)行一下,是不是感覺舒服多了?
好的,下面我們解決下一個(gè)問題,在我們打開app的時(shí)候,會看到控制臺顯示如下內(nèi)容:

2015-09-14 21:16:24.951 TuringChatMachine[820:36384] Warning: A long-running operation is being executed on the main thread. 
 Break on warnBlockingOperationOnMainThread() to debug.

意思是有一個(gè)長運(yùn)行時(shí)間的操作在主線程執(zhí)行,由于主線程主要用于UI顯示,所以如果有其他占用cpu的線程也在其中運(yùn)行的話會使得UI顯示變得很卡。
雖然沒有什么感覺,但是如果我們?nèi)タ碿pu的負(fù)荷圖的話,如下圖所示:

cpu負(fù)荷圖.png

會看到一個(gè)瞬間cpu負(fù)荷暴漲到了32%!這樣很不酷對不對?
我們的解決辦法就是,將這個(gè)占用cpu很多使用量的操作放在另一個(gè)線程中,但首先我們要找到這是哪個(gè)操作,細(xì)心的你一定注意到,當(dāng)加載聊天界面的時(shí)候會比較慢,沒錯(cuò)就是那個(gè)操作在作怪!
所以呢,我們對initData()方法進(jìn)行一些優(yōu)化。
首先改變我們從Parse服務(wù)器下載數(shù)據(jù)的方法query.findObjects(),這是同步下載數(shù)據(jù),會占據(jù)我們很大一部分cpu負(fù)載,所以我們要改為異步下載,也就是放到其他線程執(zhí)行,將以下代碼修改一下:

 for object in query.findObjects() as! [PFObject]{
                
                let message = Message(incoming: object[incomingKey] as! Bool, text: object[textKey] as! String, sentDate: object[sentDateKey] as! NSDate)
                if let url = object[urlKey] as? String{
                    message.url = url
                    
                }
                if index == 0{
                    currentDate = message.sentDate
                }
                let timeInterval = message.sentDate.timeIntervalSinceDate(currentDate!)
                
                
                if timeInterval < 120{
                    messages[section].append(message)
                }else{
                    section++
                    messages.append([message])
                    
                    
                }
                currentDate = message.sentDate
                index++
            }

修改為以下使用findObjectsInBackgroundWithBlock的版本:

        query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
            if error == nil {
                
                if objects!.count > 0 {
                    
                    for object in objects as! [PFObject] {
                        
                        if index == objects!.count - 1{
                            
                            dispatch_async(dispatch_get_main_queue(), { () -> Void in
                                
                                self.tableView.reloadData()
                                
                            })
                            
                        }
                       
                        let message = Message(incoming: object["incoming"] as! Bool, text: object["text"] as! String, sentDate: object["sentDate"] as! NSDate)
                        
                        if let url = object["url"] as? String{
                            
                            message.url = url
                            
                        }
                        if index == 0{
                            
                            currentDate = message.sentDate
                            
                        }
                        let timeInterval = message.sentDate.timeIntervalSinceDate(currentDate!)
                        
                        
                        if timeInterval < 120{
                            
                            self.messages[section].append(message)
                            
                            
                        }else{
                            
                            section++
                            
                            self.messages.append([message])
                            
                        }
                        currentDate = message.sentDate
                        
                        index++
                        
                    }
                }
                
            }else{
                println("error \(error?.userInfo)")
            }
        }

由于這是異步下載,所以tableView仍然會繼續(xù)加載cell而不會去管messages里有沒有值,這時(shí)一定會崩潰,所以為了防止這種情況發(fā)生,我們要首先給messages賦一個(gè)歡迎消息,在方法開頭加上這一行代碼:

  messages = [[Message(incoming: true, text: "你好,請叫我靈靈,我是主人的貼身小助手!", sentDate: NSDate())]]

然后運(yùn)行一下,同時(shí)看一下cpu的負(fù)荷率表:

屏幕快照 2015-09-14 下午10.25.58.png

僅有7%了!干的漂亮!
下面我們來為我們的app增加一個(gè)登錄的功能,因?yàn)闆]有辦法去區(qū)分聊天信息,所有人的聊天信息都是共享的,真正的聊天app可不會是這樣的。
要做到這個(gè),我們要為我們數(shù)據(jù)庫上的聊天消息類增加一個(gè)新屬性:

添加新屬性

選擇屬性類型
)
User類是Parse默認(rèn)的用戶類,我們的類型用指針,指向用戶類,將信息與用戶進(jìn)行綁定,這樣就能知道該條信息屬于哪個(gè)用戶了。
幸運(yùn)的是Parse已經(jīng)提供了登錄的視圖控制器,同樣還有注冊的視圖控制器:
PFLogInViewControllerPFSignUpViewController
雖然它本身的語言是英文,但是我在初始項(xiàng)目里對他們進(jìn)行了一下漢化修改,其實(shí)有更好的辦法進(jìn)行國際化,但這個(gè)只是為了演示。
首先我們創(chuàng)建一個(gè)歡迎頁面:

屏幕快照 2015-09-15 上午8.55.18.png

還有登錄頁面,注冊頁面:

登錄頁面
注冊頁面

都加上

import ParseUI

LogInViewController.swift中的viewDidLoad()方法里添加以下代碼來自定義logo:

self.logInView?.logo = UIImageView(image: UIImage(named: "logo"))

同樣地,在SignUpViewController.swift中的viewDidLoad()方法里添加以下代碼:

self.signInView?.logo = UIImageView(image: UIImage(named: "logo"))

WelcomeViewController.swift增加import模塊:

import Parse
import ParseUI

使WelcomeViewController遵循PFSignUpViewControllerDelegate
PFLogInViewControllerDelegate代理:

class WelcomeViewController: UIViewController,PFSignUpViewControllerDelegate,PFLogInViewControllerDelegate{


}

增加屬性,登錄視圖控制器和注冊視圖控制器,還有歡迎界面的logowelcomeLabel用來顯示logo和歡迎語:

    var loginVC:LogInViewController!
    var signUpVC:SignUpViewController!
    var logo:UIImageView!
    var welcomeLabel:UILabel!

我們來實(shí)現(xiàn)一些代理方法,首先是登錄的代理方法:

    func logInViewController(logInController: PFLogInViewController, shouldBeginLogInWithUsername username: String, password: String) -> Bool {
        if (!username.isEmpty && !password.isEmpty )
        {
            return true
        }
        UIAlertView(title: "缺少信息", message: "請補(bǔ)全缺少的信息", delegate: self, cancelButtonTitle:"確定").show()
        
        
        return false
    }
    func logInViewController(logInController: PFLogInViewController, didLogInUser user: PFUser) {
       self.dismissViewControllerAnimated(true, completion: nil) 
    }
    func logInViewController(logInController: PFLogInViewController, didFailToLogInWithError error: NSError?) {

        println("登錄錯(cuò)誤")
        
        
    }

第一個(gè)方法是執(zhí)行我們自定義的用戶名密碼的合法性檢查方法;第二個(gè)是在登錄之后執(zhí)行,可以通過user參數(shù)知道登錄的是哪個(gè)用戶;第三個(gè)是如果登錄出現(xiàn)錯(cuò)誤,錯(cuò)誤信息可以在這里找到。
同樣地,實(shí)現(xiàn)注冊相應(yīng)的三個(gè)方法:

    func signUpViewController(signUpController: PFSignUpViewController, shouldBeginSignUp info: [NSObject : AnyObject]) -> Bool {
        
        var infomationComplete = true
        for key in info.values {
            var field = key as! String
            if (field.isEmpty){
                infomationComplete = false
                break
            }
        }
        
        if (!infomationComplete){
            
            
            UIAlertView(title: "缺少信息", message: "請補(bǔ)全缺少的信息", delegate: self, cancelButtonTitle:"確定").show()
            
            return false
        }
        return true
    }
    func signUpViewController(signUpController: PFSignUpViewController, didSignUpUser user: PFUser) {

        self.dismissViewControllerAnimated(true, completion: nil)
        
    }
    func signUpViewController(signUpController: PFSignUpViewController, didFailToSignUpWithError error: NSError?) {
        println("注冊失敗")
    }

下面我們在viewDidLoad()中配置一下歡迎界面:

        view.backgroundColor = UIColor.whiteColor()
        self.navigationController?.navigationBarHidden = true
        logo = UIImageView(image: UIImage(named: "logo"))
        logo.center = CGPoint(x: view.center.x, y: view.center.y - 50)
        welcomeLabel = UILabel(frame: CGRect(x: view.center.x - 150/2, y: view.center.y + 20, width: 150, height: 50))
        welcomeLabel.font = UIFont.systemFontOfSize(22)
        welcomeLabel.textColor = UIColor(red:0.11, green:0.55, blue:0.86, alpha:1)
        welcomeLabel.textAlignment = .Center
        view.addSubview(welcomeLabel)
        
        view.addSubview(logo)

我們在viewWillAppear()方法中實(shí)現(xiàn)歡迎頁面邏輯,當(dāng)已經(jīng)登錄時(shí),顯示歡迎語歡迎某某某,然后2s后進(jìn)入聊天界面,否則顯示未登錄,進(jìn)入登錄界面:

    override func viewWillAppear(animated: Bool) {
        if (PFUser.currentUser() != nil){
            self.welcomeLabel.text = "歡迎 \(PFUser.currentUser()!.username!)!"
            delay(seconds: 2.0, { () -> () in
                var  chatVC = ChatViewController()
                chatVC.title = "靈靈"
                var naviVC  =  UINavigationController(rootViewController: chatVC)
                self.presentViewController(naviVC, animated: true, completion: nil)
            })
        }else{
            self.welcomeLabel.text = "未登錄"
            delay(seconds: 2.0) { () -> () in
                self.loginVC = LogInViewController()
                self.loginVC.delegate = self
                self.signUpVC = SignUpViewController()
                self.signUpVC.delegate = self
                self.loginVC.signUpController = self.signUpVC
                self.presentViewController(self.loginVC, animated: true, completion: nil)
            }
            
            
        }

    }

定義這個(gè)延時(shí)方法,在import下面:

func delay(#seconds: Double, completion:()->()) {
    let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64( Double(NSEC_PER_SEC) * seconds ))
    
    dispatch_after(popTime, dispatch_get_main_queue()) {
        completion()
    }
}

運(yùn)行之前還有一步,就是在AppDelegate.swiftapplication()方法里修改我們的初始視圖控制器:

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        Parse.setApplicationId("CYdFL9mvG8jHqc4ZA5PJsWMInBbMMun0XCoqnHgf", clientKey: "6tGOC1uIKeYp5glvJE6MXZOWG9pmLtMuIUdh2Yzo")

        var welcomeVC:WelcomeViewController = WelcomeViewController()

        UINavigationBar.appearance().tintColor = UIColor.whiteColor()
        UINavigationBar.appearance().barTintColor = UIColor(red: 0.05, green: 0.47, blue: 0.91, alpha: 1.0)
        UINavigationBar.appearance().titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()]
        UIApplication.sharedApplication().statusBarStyle = UIStatusBarStyle.LightContent
        
        
        let frame = UIScreen.mainScreen().bounds
        window = UIWindow(frame: frame)
        
        window!.rootViewController = welcomeVC
        window!.makeKeyAndVisible()
        return true
    }

還有一件事,我們要在讀取數(shù)據(jù)的時(shí)候只讀取當(dāng)前登錄用戶的信息,而不是全部,所以我們要加上一個(gè)限制,在query.findObjectsInBackgroundWithBlock執(zhí)行前加上以下代碼:

        if let user = PFUser.currentUser(){
            query.whereKey("createdBy", equalTo: user)
            messages = [[Message(incoming: true, text: "\(user.username!)你好,請叫我靈靈,我是主人的貼身小助手!", sentDate: NSDate())]]
        }

同樣地,我們保存消息的時(shí)候,將當(dāng)前用戶賦值給createdBy屬性,修改一下saveMessage()方法:

    func saveMessage(message:Message){
        var saveObject = PFObject(className: "Messages")
        saveObject["incoming"] = message.incoming
        saveObject["text"] = message.text
        saveObject["sentDate"] = message.sentDate
        saveObject["url"] = message.url
        var user = PFUser.currentUser()
        saveObject["createdBy"] = user
        saveObject.saveEventually { (success, error) -> Void in
            
            if success{
                println("消息保存成功!")
            }else{
                
                println("消息保存失敗! \(error)")
                
            }
        }
        
    }

至此我們的登錄注冊功能就集成進(jìn)我們的app了,當(dāng)然這只是一個(gè)演示,為了演示如何用ParseUI庫實(shí)現(xiàn)登錄功能,并沒有太多的自定義,更復(fù)雜的應(yīng)用這里先不進(jìn)行擴(kuò)展了。

登錄.gif

到此我們的app已經(jīng)有一些正式的樣子了,下一章還會對其進(jìn)行功能的擴(kuò)充和優(yōu)化!請持續(xù)關(guān)注!
本章完成源代碼下載
如果我的文章對你有幫助,請點(diǎn)一下喜歡,大家的支持是我繼續(xù)寫作的動力!

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

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