關(guān)于SwiftUI的內(nèi)部技術(shù)分享

什么是 SwiftUI?[1]

官方的定義非常明確:

SwiftUI is a user interface toolkit that lets us design apps in a declarative way.
SwiftUI 就是?種描述式的構(gòu)建 UI 的?式。

簡介[2]

蘋果在 2019 WWDC 推出新一代聲明式布局框架-SwiftUI 缤灵,該框架可用于 watchOS跨跨、tvOS址愿、macOS、iOS 等个盆,蘋果的任意平臺都可以使用矾芙,達(dá)到跨平臺的實現(xiàn)舍沙。

在 SwiftUI 出現(xiàn)之前近上,蘋果不同的設(shè)備之間的開發(fā)框架并不互通剔宪,macOS 開發(fā)用的 AppKit,iOS 開發(fā)用的 UIKit壹无,WatchOS 開發(fā)用的堆疊葱绒,每個都不一樣,不能達(dá)到互通互用斗锭,可復(fù)用性差地淀。

之前

import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }
}

現(xiàn)在

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
            .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environmentObject(ModelData())
    }
}

分成兩個部分:

  • struct ContentView 定義的是視圖結(jié)構(gòu)。
  • struct ContentView_Previews 是預(yù)覽視圖聲明岖是。
    我們主要關(guān)注第一部分:struct ContentView

關(guān)鍵字 some 帮毁,其實就是一個opaque(不透明)類型实苞,在返回類型前面添加這個關(guān)鍵字,代表你和編譯器都確定這個函數(shù)總會返回一個特定的具體類型-只是你不知道是哪一種

SwiftUI 的編輯器是雙向交互的:

  • 左邊代碼編輯器的改動會立即反應(yīng)到右邊的預(yù)覽視圖烈疚。
  • 右邊的預(yù)覽視圖的編輯也會同步到左邊的代碼視圖黔牵。

優(yōu)點

  • 使用 SwiftUI,系統(tǒng)會默認(rèn)支持白天和黑夜模式的自動切換
  • 實時刷新預(yù)覽
  • 各種尺寸的屏幕間自動適配
  • 高效:更少的代碼爷肝,更快的交付

SwiftUI 1.0 基本沒有公司敢用在正式上線的APP 上猾浦,API 在 Beta 版本之間各種廢棄,UI 樣式經(jīng)常不兼容灯抛,大列表性能差

缺點

  • iOS 14 才可放心的使用金赦,
  • 要解決的是如何部署到低版本操作系統(tǒng)上?

SwiftUI的基本組件[3]

名稱 含義
Text 用來顯示文本的組件对嚼,類似UIKit中的UILabel
Image 用來展示圖片的組件夹抗,類似UIKit中的UIImageView
Button 用來展示圖片的組件,類似UIKit中的UIButton
List 用來展示列表的組件纵竖,類似UIKit中的UITableView
ScrollView 用來支持滑動的組件兔朦,類似UIKit中的UIScrollView
Spacer 一個靈活的空間,用來填充空白的組件
Divider 一條分割線磨确,用來劃分區(qū)域的組件
VStack 將子視圖按“豎直方向”排列布局沽甥。(Vertical stack)
HStack 將子視圖按“水平方向”排列布局。(Horizontal stack)
ZStack 將子視圖按“兩軸方向均對齊”布局(居中乏奥,有重疊效果)
基本組件:
  • Text:用來顯示文本的組件
Text("Hello, we are QiShare!").foregroundColor(.blue).font(.system(size: 32.0))
  • Image:用來展示圖片的組件
Image.init(systemName: "star.fill").foregroundColor(.yellow)
  • Button:用于可點擊的按鈕組件
Button(action: { self.showingProfile.toggle() }) {
    Image(systemName: "paperplane.fill")
        .imageScale(.large)
        .accessibility(label: Text("Right"))
        .padding()
}
  • List:用來展示列表的組件
List(0..<5){_ in
        NavigationLink.init(destination: VStack(alignment:.center){
            Image.init(systemName: "\(item+1).square.fill").foregroundColor(.green)
            Text("詳情界面\(item + 1)").font(.system(size: 16))
    }) {
          //ListRow
       }
布局組件:

VStack摆舟、HStack、ZStack

功能組件:
  • NavigationView:負(fù)責(zé)App中導(dǎo)航功能的組件邓了,類似UIKit中的UINavigationView
  • NavigationLink:負(fù)責(zé)App頁面 跳轉(zhuǎn) 的組件恨诱,類似于UINavigationView中的 push與pop 功能
NavigationView {
    List(0..<5){_ in
        NavigationLink.init(destination: VStack(alignment:.center){
            Image.init(systemName: "\(item+1).square.fill").foregroundColor(.green)
            Text("詳情界面\(item + 1)").font(.system(size: 16))
    }) {
          //ListRow
       }
}
.navigationBarTitle("導(dǎo)航\(item)",displayMode: .inline)
  • TabView:負(fù)責(zé)App中的標(biāo)簽頁功能的組件,類似UIKit中的UITabBarController
TabView {
    Text("The First Tab")
        .tabItem {
            Image(systemName: "1.square.fill")
            Text("First")
        }
    Text("Another Tab")
        .tabItem {
            Image(systemName: "2.square.fill")
            Text("Second")
        }
    Text("The Last Tab")
        .tabItem {
            Image(systemName: "3.square.fill")
            Text("Third")
        }
}
.font(.headline)

UI布局的基本法則

在SwiftUI中骗炉,不能給子視圖強制規(guī)定一個尺寸

  1. 父view為子view提供一個建議的size
  2. 子view根據(jù)自身的特性照宝,返回一個size
  3. 父view根據(jù)子view返回的size為其進(jìn)行布局

舉個例子:
UI布局的基本法則
struct ContentView: View {
    var body: some View {
        Text("Hello, world")
            .border(Color.green)
    }
}
  1. ContentView是Text的父view,為Text提供一個建議的size(全屏尺寸)
  2. 然后Text根據(jù)自身的特性句葵,返回了它實際需要的size
    (注意:Text的特性是盡可能的只使用必要的空間厕鹃,也就是說能夠剛好展示完整文本的空間)
  3. 然后ContentView根據(jù)Text返回的size,在其內(nèi)部對Text進(jìn)行布局乍丈,在SwiftUI中剂碴,容器默認(rèn)的布局方式為居中對齊。

Frame[4]

frame 在UIKit中是一種絕對布局轻专,它的位置是相對于父view左上角的絕對坐標(biāo)忆矛。但SwiftUI中frame的概念卻完全不同

在SwiftUI中,frame是一個modifier(修飾符的意思)请垛,并不是真的修改了view催训。實際上會創(chuàng)建一個新的view

舉個例子

struct ContentView: View {
    var body: some View {
        Text("Hello, world")
            .background(Color.green)
            .frame(width: 200, height: 50)
    }
}
想要的

實際的

在上邊的代碼中洽议,.background并不會直接去修改原來的Text,而是在Text圖層的下方新建了一個新的view

為什么會這樣呢漫拭?

根據(jù)布局的3法則考慮這個問題

在考慮布局的時候绞铃,是自下而上的!!!

  1. 我們先考慮ContentVIew,它的父view給他的建議尺寸為整個屏幕的大小
  2. ContentVIew去詢問它的child嫂侍,它的child為下邊的那個frame儿捧,返回了width200, height50挑宠, 因此frame告訴ContentView它需要的size為width200菲盾, height50,因此最終ContentView的size為width200各淀, height50
  3. background是個一個透明的view懒鉴,它的父控件frame,給的建議尺寸是width200碎浇, height50临谱。它又去詢問其child,text返回的是只需要容納文本的size奴璃,因此text的size并不會是width: 200, height: 50

所以要想達(dá)到理想效果悉默,需要修改一下上邊的代碼,調(diào)整frame和background的順序就能實現(xiàn)

struct ContentView: View {
    var body: some View {
        Text("Hello, world")
            .frame(width: 200, height: 50)
            .background(Color.green)
    }
}

數(shù)據(jù)處理的基本原則

  • Data Access as a Dependency 數(shù)據(jù)訪問依賴

SwiftUI中的界面是嚴(yán)格數(shù)據(jù)驅(qū)動的:運行時界面的修改苟穆,只能通過修改數(shù)據(jù)來間接完成抄课,而不是直接對界面進(jìn)行修改操作。不回再像 傳統(tǒng)命令式編程 MVC 模式下那樣雳旅,ViewController 承載各種 UIVew控件跟磨,開發(fā)者需要手動處理 UIView 和 數(shù)據(jù)之間的依賴關(guān)系。當(dāng)數(shù)據(jù)產(chǎn)生變化時攒盈,要不停的同步數(shù)據(jù)和視圖之間的狀態(tài)變化抵拘。

SwiftUI是一切皆 View,所以可以把 View 切分成各種細(xì)粒度的組件型豁,然后通過組合的方式拼裝成最終的界面僵蛛,這種視圖的拼裝方式大大提高了界面開發(fā)的靈活性和復(fù)用性,視圖組件化并任意組合的方式是 SwiftUI 官方非常鼓勵的做法

  • A Single Source Of Truth 單一數(shù)據(jù)源

在 SwiftUI 中偷遗,不同視圖間如果要訪問同樣的數(shù)據(jù)墩瞳,不需要各自持有數(shù)據(jù)驼壶,直接共用一個數(shù)據(jù)源即可氏豌。這樣的好處是無需手動處理視圖和數(shù)據(jù)的同步,當(dāng)數(shù)據(jù)源發(fā)生變化時會自動更新與該數(shù)據(jù)有依賴關(guān)系的視圖

swiftUI數(shù)據(jù)流轉(zhuǎn)規(guī)范 - 數(shù)據(jù)流圖

從上圖可以看出SwiftUI 的數(shù)據(jù)流轉(zhuǎn)過程:

  • 用戶對界面進(jìn)行操作热凹,產(chǎn)生一個操作行為 action
  • 該行為觸發(fā)數(shù)據(jù)狀態(tài)的改變
  • 數(shù)據(jù)狀態(tài)的變化會觸發(fā)視圖重繪
  • SwiftUI 內(nèi)部按需更新視圖,最終再次呈現(xiàn)給用戶,等待下次界面操作

數(shù)據(jù)流工具[5]

通過它們建立數(shù)據(jù)和視圖的依賴關(guān)系

  • Property
  • @State
  • @Binding
  • ObservableObject
  • @EnvironmentObject
  1. Property:
    開發(fā)中最常見的空盼,它就是一個簡單的屬性爹梁,沒什么特別。ChildView 需要 Parent View 給它傳一個字符串坦胶,并且 ChildView 不對這個字符串進(jìn)行修改,所以直接定義一個 Property,在使用的時候突诬,直接讓 Parent View 告訴它就好了。
struct ContentView : View {
    var body: some View {
        ChildView(text: "Demo")
    }
}

struct ChildView: View {
    let text: String
    var body: some View {
        Text(text)
    }
}
  1. @State:
  • 基于值類型的狀態(tài)管理芜繁,這些值通常是字符串旺隙、數(shù)字、布爾等常量值
  • 只能在當(dāng)前 View 的 body 內(nèi)修改骏令,所以它的使用場景是只影響當(dāng)前 View 內(nèi)部的變化
  • 當(dāng)被@State包裝的屬性改變蔬捷,SwiftUI 內(nèi)部會自動重新計算和繪制 View的body部分
  • 被@State包裝的變量一定要用private修飾,并且這個變量只能在當(dāng)前view以及其子View的body中使用榔袋,不讓外部使用周拐。如果想讓外部使用,則應(yīng)該使用@ObservedObject和@EnvironmentObject
struct PlayerView : View {
    @State private var isPlaying: Bool = false
    
    var body: some View {
        VStack {
            
            Button(action: {
                self.isPlaying.toggle()
            }) {
                Image(systemName: isPlaying ? "pause.circle" : "play.circle")
            }
        }
    }
}
  1. @Binding

傳統(tǒng)的命令式編程中最復(fù)雜的部分莫過于狀態(tài)管理凰兑,尤其是多數(shù)據(jù)同步妥粟。
一個數(shù)據(jù)存在于不同的 UI 中,某個數(shù)據(jù)改變就要同步到不同的UI 中吏够。當(dāng)這樣需要同步的數(shù)據(jù)變的很多罕容,再加上一些其他的異步的操作和邏輯處理,會使代碼變得臃腫稿饰、可讀性下降锦秒,并且伴隨著而來的就是各種 Bug,SwiftUI 的解決辦法就是使用 @Binding

使用@state包裝的屬性只在它所屬view的內(nèi)部使用喉镰,那么當(dāng)它的子視圖要訪問這個屬性的時候就要用到@binding了

@Binding主要有下面幾個作用

  • 在不持有數(shù)據(jù)源的情況下旅择,任意讀取
  • 從 @State 中獲取數(shù)據(jù),并保持同步
  • 對包裝的值采用傳址而不是傳值
struct ContentView: View {
    // 用@State修飾需要改變的變量
    @State private var count: Int = 0
    
    var body: some View {
        VStack {
            Text("\(count)").foregroundColor(.orange).font(.largeTitle).padding()
            // $訪問傳遞給另外一個UI
            CountButton(count: $count)
        }
    }
}

struct CountButton : View {
    // 用@State修飾侣姆,綁定count的值
    @Binding var count: Int
    
    var body: some View {
        Button(action: {
            // 此處修改數(shù)據(jù)會同步到上面的UI
            self.count = self.count + 1
            
        }) { Text("改變Count")
        }
    }
}
  1. ObservableObject

它的原理和RxSwift發(fā)布者和訂閱者的模式類似

  • ObservableObject 是個協(xié)議生真,必須要類去實現(xiàn)該協(xié)議,適用于多個 UI 之間的同步數(shù)據(jù)

  • 在應(yīng)用開發(fā)過程中捺宗,很多數(shù)據(jù)其實并不是在 View 內(nèi)部產(chǎn)生的柱蟀,這些數(shù)據(jù)可能是一些本地存儲的數(shù)據(jù),也可能是網(wǎng)絡(luò)請求的模型數(shù)據(jù)蚜厉,這些數(shù)據(jù)默認(rèn)是與 SwiftUI 沒有依賴關(guān)系的长已,要想建立依賴關(guān)系就要用 ObservableObject,與之配合的是還有@ObservedObject和@Published兩個修飾符

  • @Published 修飾的屬性一旦發(fā)生了變化,會自動觸發(fā) ObservableObject 的objectWillChange 的 send方法术瓮,刷新頁面康聂。這一步是系統(tǒng)幫我們默認(rèn)實現(xiàn)的

  • ObservedObject:被觀察的對象 ,告訴SwiftUI胞四,這個對象是可以被觀察的恬汁,里面含有被@Published包裝了的屬性

  • @ObservedObject包裝的對象,必須遵循ObservableObject協(xié)議辜伟。也就是說必須是class對象氓侧,不能是struct。

  • @ObservedObject允許外部進(jìn)行訪問和修改

class UserSettings: ObservableObject {
    // 有可能會有多個視圖使用导狡,所以屬性未聲明為私有
    @Published var score = 123
}

struct ContentView: View {
    @ObservedObject var settings = UserSettings()

    var body: some View {
        VStack {
            Text("人氣值: \(settings.score)").font(.title).padding()
            Button(action: {
                self.settings.score += 1
            }) {
                Text("增加人氣")
            }
        }
    }
}

有這樣一個場景甘苍,A->B->C->D->E->F,A界面的數(shù)據(jù)要傳遞給F界面烘豌,假如使用@ObservedObject包裝载庭,需要一層一層傳遞。再有反向傳值的話就更復(fù)雜廊佩,且容易出錯囚聚。而使用@EnvironmentObject則不需要,直接在F界面标锄,通過SwiftUI環(huán)境直接取出來就行顽铸。

  1. @EnvironmentObject 包裝的屬性是全局的,整個app都可以訪問
  • 主要是為了解決跨組件數(shù)據(jù)傳遞的問題料皇。
  • 組件層級嵌套太深谓松,就會出現(xiàn)數(shù)據(jù)逐層傳遞的問題,@EnvironmentObject可以幫助組件快速訪問全局?jǐn)?shù)據(jù)践剂,避免不必要的組件數(shù)據(jù)傳遞問題鬼譬。
  • 使用基本與@ObservedObject一樣,但@EnvironmentObject突出強調(diào)此數(shù)據(jù)將由某個外部實體提供逊脯,所以不需要在具體使用的地方初始化优质,而是由外部統(tǒng)一提供。
  • 使用@EnvironmentObject军洼,SwiftUI 將立即在環(huán)境中搜索正確類型的對象巩螃。如果找不到這樣的對象,則應(yīng)用程序?qū)⒘⒓幢罎⒇罢砸?慎用
class UserSettings: ObservableObject {
    @Published var score = 123
}

struct ContentView: View {
    
    @EnvironmentObject var settings: UserSettings
    
    var body: some View {        
        NavigationView{            
            VStack {
                // 顯示score
                Text("人氣值: \(settings.score)").font(.title).padding()
                // 改變score
                Button(action: {
                    self.settings.score += 1
                }) {
                    Text("增加人氣")
                }
                // 跳轉(zhuǎn)下一個界面
                NavigationLink(destination: DetailView()) {
                    Text("下一個界面")
                }
            }
        }
    }
}

struct DetailView: View {
    
    @EnvironmentObject var settings: UserSettings
    
    var body: some View {
        VStack {
            Text("人氣值: \(settings.score)").font(.title).padding()
            Button(action: {
                self.settings.score += 1
            }) {
                Text("增加人氣")
            }
        }
    }
}

// 需要注意此時需要修改SceneDelegate避乏,傳入environmentObject
window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(UserSettings()))
  • Property、 @State甘桑、 @Binding 一般修飾的都是 View 內(nèi)部的數(shù)據(jù)拍皮。
  • @ObservedObject歹叮、 @EnvironmentObject 一般修飾的都是 View 外部的數(shù)據(jù):
  • 網(wǎng)絡(luò)或本地存儲的數(shù)據(jù)
  • 界面之間互相傳遞的數(shù)據(jù)

總結(jié):

  1. View與View間的公用數(shù)據(jù)使用@State + @Binding。
  2. 多個View與Class間的公用數(shù)據(jù):對View用@ObservedObject春缕,讓Class滿足ObservableObject協(xié)議盗胀。
  3. 父View與子View對Class間的公用數(shù)據(jù):父View用@ObservedObject艘蹋,子View用@EnvironmentObject锄贼,Class滿足ObservableObject協(xié)議

與UIKit彼此相容

由于SwiftUI 是一個新發(fā)布的框架,UI 組件并不齊全女阀,當(dāng) SwiftUI 中并沒有提供類似的功能時宅荤,可以把 UIKit 中已有的部分進(jìn)行封裝后,提供給 SwiftUI 使用浸策。不過需要遵循UIViewRepresentable協(xié)議冯键。UIViewRepresentable協(xié)議是SwiftUI框架中提供的用于將UIView轉(zhuǎn)換成SwiftUI中View的協(xié)議
當(dāng)然,也可以在已有的項目中庸汗,僅用 SwiftUI 制作一部分的 UI 界面惫确。
UIViewRepresentable協(xié)議

protocol UIViewRepresentable : View
    associatedtype UIViewType : UIView

   /// 返回想要封裝的 UIView 類型 和 實例
    func makeUIView(context: Self.Context) !" Self.UIViewType

    /// UIViewRepresentable 中的某個屬性發(fā)生變化,SwiftUI 要求更新該 UIKit 部件時被調(diào)用
    func updateUIView(
        _ uiView: Self.UIViewType,
        context: Self.Context
    )
}

舉個栗子

struct SearchBar : UIViewRepresentable {
    
    @Binding var text : String
    
    class Cordinator : NSObject, UISearchBarDelegate {
        
        @Binding var text : String
        
        init(text : Binding<String>) {
            _text = text
        }
        
        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
            text = searchText
        }
    }
    
    func makeCoordinator() -> SearchBar.Cordinator {
        return Cordinator(text: $text)
    }
    
    func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
        let searchBar = UISearchBar(frame: .zero)
        searchBar.delegate = context.coordinator
        return searchBar
    }
    
    func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) {
        uiView.text = text
    }
}

學(xué)習(xí)資料

  1. 斯坦福公開課 CS193P·2020 年春:該課程強推蚯舱,我當(dāng)年學(xué)習(xí) OC 看的就是它改化,現(xiàn)在到SwiftUI了還是先看這個,系統(tǒng)且細(xì)致枉昏,結(jié)合案例和編程過程中的小技巧介紹陈肛,是很好的入門課程。
  2. 蘋果官方 SwiftUI 課程:打開Xcode兄裂,照著官方的教學(xué)句旱,從頭到尾學(xué)著做一遍應(yīng)用。
  3. Hacking with swift:這是國外一個程序員用業(yè)余時間搭建的分享網(wǎng)站晰奖,有大量的文章可以閱讀谈撒,還有推薦初學(xué)者跟著做的「100 Days of SwiftUI」課程。
  4. 蘋果官方文檔:雖然很多文檔缺乏工程細(xì)節(jié)匾南,但是文檔涉及很多概念性的內(nèi)容港华,你可以知道官方是怎么思考的,并且有很多具體的機制參數(shù)
  5. SwiftUI的 View 如何布局午衰?

  1. https://www.zhihu.com/question/327763737 ?

  2. https://sspai.com/post/65567 ?

  3. https://juejin.cn/post/6844903999762595854 ?

  4. http://www.reibang.com/p/8b05d84fe411 ?

  5. http://www.reibang.com/p/72fc8a2f530f ?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末立宜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子臊岸,更是在濱河造成了極大的恐慌橙数,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帅戒,死亡現(xiàn)場離奇詭異灯帮,居然都是意外死亡崖技,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門钟哥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來迎献,“玉大人,你說我怎么就攤上這事腻贰∮趸校” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵播演,是天一觀的道長冀瓦。 經(jīng)常有香客問我,道長写烤,這世上最難降的妖魔是什么翼闽? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮洲炊,結(jié)果婚禮上感局,老公的妹妹穿的比我還像新娘。我一直安慰自己暂衡,他們只是感情好询微,可當(dāng)我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著古徒,像睡著了一般拓提。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上隧膘,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天代态,我揣著相機與錄音,去河邊找鬼疹吃。 笑死蹦疑,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的萨驶。 我是一名探鬼主播歉摧,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼腔呜!你這毒婦竟也來了叁温?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤核畴,失蹤者是張志新(化名)和其女友劉穎膝但,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谤草,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡跟束,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年莺奸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冀宴。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡灭贷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出略贮,到底是詐尸還是另有隱情甚疟,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布刨肃,位于F島的核電站古拴,受9級特大地震影響箩帚,放射性物質(zhì)發(fā)生泄漏真友。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一紧帕、第九天 我趴在偏房一處隱蔽的房頂上張望盔然。 院中可真熱鬧,春花似錦是嗜、人聲如沸愈案。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽站绪。三九已至,卻和暖如春丽柿,著一層夾襖步出監(jiān)牢的瞬間恢准,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工甫题, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留馁筐,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓坠非,卻偏偏與公主長得像敏沉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子炎码,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353

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