UIKit框架(二十八) —— 一个UISplitViewController的简单实用示例 (一)

版本记录

版本号 时间
V1.0 2019.09.18 星期三

前言

iOS中有关视图控件用户能看到的都在UIKit框架里面,用户交互也是通过UIKit进行的。感兴趣的参考上面几篇文章。
1. UIKit框架(一) —— UIKit动力学和移动效果(一)
2. UIKit框架(二) —— UIKit动力学和移动效果(二)
3. UIKit框架(三) —— UICollectionViewCell的扩张效果的实现(一)
4. UIKit框架(四) —— UICollectionViewCell的扩张效果的实现(二)
5. UIKit框架(五) —— 自定义控件:可重复使用的滑块(一)
6. UIKit框架(六) —— 自定义控件:可重复使用的滑块(二)
7. UIKit框架(七) —— 动态尺寸UITableViewCell的实现(一)
8. UIKit框架(八) —— 动态尺寸UITableViewCell的实现(二)
9. UIKit框架(九) —— UICollectionView的数据异步预加载(一)
10. UIKit框架(十) —— UICollectionView的数据异步预加载(二)
11. UIKit框架(十一) —— UICollectionView的重用、选择和重排序(一)
12. UIKit框架(十二) —— UICollectionView的重用、选择和重排序(二)
13. UIKit框架(十三) —— 如何创建自己的侧滑式面板导航(一)
14. UIKit框架(十四) —— 如何创建自己的侧滑式面板导航(二)
15. UIKit框架(十五) —— 基于自定义UICollectionViewLayout布局的简单示例(一)
16. UIKit框架(十六) —— 基于自定义UICollectionViewLayout布局的简单示例(二)
17. UIKit框架(十七) —— 基于自定义UICollectionViewLayout布局的简单示例(三)
18. UIKit框架(十八) —— 基于CALayer属性的一种3D边栏动画的实现(一)
19. UIKit框架(十九) —— 基于CALayer属性的一种3D边栏动画的实现(二)
20. UIKit框架(二十) —— 基于UILabel跑马灯类似效果的实现(一)
21. UIKit框架(二十一) —— UIStackView的使用(一)
22. UIKit框架(二十二) —— 基于UIPresentationController的自定义viewController的转场和展示(一)
23. UIKit框架(二十三) —— 基于UIPresentationController的自定义viewController的转场和展示(二)
24. UIKit框架(二十四) —— 基于UICollectionViews和Drag-Drop在两个APP间的使用示例 (一)
25. UIKit框架(二十五) —— 基于UICollectionViews和Drag-Drop在两个APP间的使用示例 (二)
26. UIKit框架(二十六) —— UICollectionView的自定义布局 (一)
27. UIKit框架(二十七) —— UICollectionView的自定义布局 (二)

开始

今天是个特殊的日子,勿忘国耻,国人当自强,向抵抗侵略的将士们致敬!

首先看下主要内容

了解如何将iOS应用程序拆分为两个部分,并在此UISplitViewController教程的每一侧显示视图控制器

接着看一下写作环境

Swift 5, iOS 13, Xcode 11

应用程序通常需要提供拆分视图以提供整洁的导航模型。 这方面的一个例子是Mail.app,它在iPad上使用左侧有文件夹列表的分割视图,然后是右侧选定的邮件项目。 Apple为我们构建了一个非常方便的视图控制器,称为UISplitViewController,它可以直接回到iPad的低端。 在这个UISplitViewController教程中,您将学习如何使用它! 此外,自iOS 8起,split view controller拆分视图控制器可在iPadiPhone上运行。

在本教程中,您将从头开始创建一个通用应用程序,它使用split view controller来显示Math Ninja中的怪物列表。

您将使用拆分视图控制器来处理导航和显示。 它适用于iPhoneiPad

单击File ▸ New ▸ Project…,在Xcode中创建一个新项目。 选择 iOS ▸ Application ▸ Single View App模板。

将项目命名为MathMonsters。 将Language保持为Swift。 将User Interface设置为Storyboard。 取消选中所有复选框。 然后单击Next完成项目的创建。

虽然您可以使用Master-Detail App模板作为起点,但您将从头开始使用Single View App模板。 这将使您更好地了解UISplitViewController的工作原理。 在将来的项目中使用UISplitViewController时,这些知识将非常有用。

是时候创建UI了,所以打开Main.storyboard

删除故事板中的默认初始View Controller Scene。 同时从项目导航器中删除ViewController.swift,确保在询问时选择Move to Trash

将拆分视图控制器拖到空的故事板中:

这将为您的storyboard添加几个元素:

  • Split View Controller - 拆分视图控制器:此拆分视图将包含应用程序的其余部分,并且是应用程序的根。
  • Navigation Controller - 导航控制器:此UINavigationController将是主视图控制器的根视图。 这是拆分视图的左侧窗格,当在iPad上或在较大的iPhone(如iPhone 8 Plus)上横向显示时。在拆分视图控制器中,您将看到导航控制器具有称为master view controller的关系segue。 这允许您在主视图控制器中创建整个导航层次结构,而无需影响详细视图控制器。
  • View Controller - 视图控制器:这将最终显示所有怪物的详细信息。 如果查看拆分视图控制器,您将看到视图控制器具有称为详细视图控制器(detail view controller)的关系segue:
  • Table View Controller:这是主UINavigationController的根视图控制器。 它最终将显示怪物列表。

注意:Xcode会警告您表视图的原型单元缺少重用标识符(reuse identifier)。 暂时不要担心。 你很快就会解决它。

由于您从故事板中删除了默认的初始视图控制器,因此您需要告诉故事板您希望拆分视图控制器成为初始视图控制器。

选择Split View Controller,然后打开Attributes inspector。 选中Is Initial View Controller选项。

您将在分割视图控制器的左侧看到一个箭头。 这告诉你它是这个故事板的初始视图控制器。

在iPad模拟器上构建并运行应用程序。 将模拟器旋转到横向。

您应该看到一个空的拆分视图控制器:

现在可以在任何iPhone模拟器上运行它,除了一个加大尺寸的手机,它足够大,可以像iPad一样运行。 你会看到它开始全屏显示细节视图。 它还允许您点击导航栏上的后退按钮以弹回主视图控制器:

在除了横向大型PlusMax设备之外的iPhone上,分割视图控制器将像传统的master-detail应用程序一样,带有导航控制器来回推出和弹出。这是内置功能,开发人员只需要很少的额外配置。

您需要显示自己的视图控制器而不是这些默认控制器。是时候开始创建它们了。


Creating Custom View Controllers

故事板具有视图控制器层次结构集:拆分视图控制器,其主视图控制器和详细视图控制器作为其子视图。现在,您需要实现代码方面以获取要显示的数据。

转到File ▸ New ▸ File…,并选择iOS ▸ Source ▸ Cocoa Touch Class模板。将类命名为MasterViewController,并使其成为UITableViewController的子类。确保未选中Also create XIB file复选框,并将Language设置为Swift。单击Next,然后单击Create

打开MasterViewController.swift

向下滚动到numberOfSections(in:)中。删除此方法。只返回一个部分时不需要它。

接下来,找到tableView(_:numberOfRowsInSection :)并用以下内容替换实现:

override func tableView(
  _ tableView: UITableView, 
  numberOfRowsInSection section: Int) 
    -> Int {
  return 10
}

最后,取消注释tableView(_:cellForRowAt :)并将其实现替换为以下内容:

override func tableView(
  _ tableView: UITableView, 
  cellForRowAt indexPath: IndexPath) 
    -> UITableViewCell {
  let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
  return cell
}

这样,当你稍后测试这个东西时,你将看到十个空行。

打开Main.storyboard。 选择Root View Controller并切换Identity检查器。 将类更改为MasterViewController

此外,您需要确保在表视图中为原型单元格提供重用标识符。 如果没有,它会在故事板试图加载时导致崩溃。

Master View Controller中,选择Prototype Cell。 在Attributes inspector中,将Identifier更改为Cell。 同时将单元格Style更改为Basic

iPadiPhone模拟器中构建和运行。 你会注意到虽然有十行,都标有标题,点击一行不会做任何事情。 这是因为您尚未指定详细视图控制器。

现在,您将为细节方创建视图控制器。

转到File ▸ New ▸ File…,并选择iOS ▸ Source ▸ Cocoa Touch Class模板。 将类命名为DetailViewController,并使其成为UIViewController的子类。 确保未选中Also create XIB file复选框,并将Language设置为Swift

单击Next,然后单击Create

打开Main.storyboard并在View Controller Scene中选择视图控制器。 在Identity inspector中,将Class更改为DetailViewController

然后将label拖到详细视图控制器的中间。 使用“自动布局”将label固定到容器的水平和垂直中心。

双击label将其文本更改为Hello,World!,所以当你稍后测试它时你会知道它正在工作。

构建并运行。 此时,您应该看到自定义视图控制器。

iPad上:

iPhone上:

您现在已经获得了拆分视图的基础,每个位都有自定义视图控制器。 接下来你需要添加那些讨厌的怪物。


Making Your Model

接下来,您需要为要显示的数据定义模型。 在学习拆分视图控制器的基础知识时,您不希望复杂化,因此您将使用没有数据持久性的简单模型。

首先,创建一个表示要显示的怪物的类。 转到File ▸ New ▸ File…,选择iOS ▸ Source ▸ Swift File模板,然后单击Next。 将文件命名为Monster,然后单击Create

您将创建一个简单的类,其中包含有关要显示的每个怪物的属性属性。 您还将实现一些方法来创建新的怪物并访问每个怪物武器的图像。

用以下内容替换Monster.swift的内容:

import UIKit

enum Weapon {
  case blowgun, ninjaStar, fire, sword, smoke

  var image: UIImage {
    switch self {
    case .blowgun:
      return UIImage(named: "blowgun.png")!
    case .fire:
      return UIImage(named: "fire.png")!
    case .ninjaStar:
      return UIImage(named: "ninjastar.png")!
    case .smoke:
      return UIImage(named: "smoke.png")!
    case .sword:
      return UIImage(named: "sword.png")!
    }
  }
}

class Monster {
  let name: String
  let description: String
  let iconName: String
  let weapon: Weapon

  init(name: String, description: String, iconName: String, weapon: Weapon) {
    self.name = name
    self.description = description
    self.iconName = iconName
    self.weapon = weapon
  }

  var icon: UIImage? {
    return UIImage(named: iconName)
  }
}

这定义了枚举和类。 枚举是为了跟踪不同种类的武器,包括每种武器的图像。 该类将使用简单的初始化程序保存怪物信息以创建Monster实例。

这是用于定义模型的。 接下来,您将它连接到您的主视图!


Displaying the Monster List

打开MasterViewController.swift并向该类添加一个新属性:

let monsters = [
    Monster(name: "Cat-Bot", description: "MEE-OW",
            iconName: "meetcatbot", weapon: .sword),
    Monster(name: "Dog-Bot", description: "BOW-WOW",
            iconName: "meetdogbot", weapon: .blowgun),
    Monster(name: "Explode-Bot", description: "BOOM!",
            iconName: "meetexplodebot", weapon: .smoke),
    Monster(name: "Fire-Bot", description: "Will Make You Steamed",
            iconName: "meetfirebot", weapon: .ninjaStar),
    Monster(name: "Ice-Bot", description: "Has A Chilling Effect",
            iconName: "meeticebot", weapon: .fire),
    Monster(name: "Mini-Tomato-Bot", description: "Extremely Handsome",
            iconName: "meetminitomatobot", weapon: .ninjaStar)
  ]

这可以保存用于填充表视图的怪物数组。

找到tableView(_:numberOfRowsInSection :)并将return语句替换为以下内容:

return monsters.count

这将根据数组的大小返回怪物数量。

接下来,找到tableView(_:cellForRowAtIndexPath :)并在最终的return语句之前添加以下代码:

let monster = monsters[indexPath.row]
cell.textLabel?.text = monster.name

这将根据正确的怪物配置单元格。 这就是table view,它只是显示每个怪物的名字。

构建并运行应用程序。

你应该在横屏iPad的左侧看到怪物机器人列表:

iPhone上:

请记住,在compact-widthiPhone上,您可以在详细信息屏幕上的导航堆栈中开始一层深度。 您可以点击后退按钮查看table view


Updating the Master View Controller’s Title

导航栏自动设置初始视图控制器的标题,即RootViewController

打开Main.storyboard,选择Root View Controller并双击NavigationBar

将其更改为Monster List。 这比Root View Controller好得多。


Displaying Bot Details

现在table view显示了怪物列表,现在是时候按顺序获取详细视图了。

打开Main.storyboard,选择Detail View Controller并删除之前放下的label

使用下面的屏幕截图作为指导,将以下控件拖到DetailViewController的视图中(有关要添加的内容的详细列表,请参阅下面的内容):

以下是您需要添加的内容:

  • 1) 其余视图将进入的容器视图。这应该与屏幕顶部对齐并在屏幕中水平居中。
  • 2) 一个95×95的图像视图,距离容器视图顶部8个像素,距离左侧20个像素。这是为了显示怪物的图像。
  • 3) 与图像视图顶部对齐的label,字体System Bold,大小为30,文本为Monster Name。将其顶部与图像的顶部对齐,并将其设置为图像右侧的8个像素。同时使其尾部边距容器视图的右侧8像素。
  • 4) 下面有两个带有System字体的labels,尺寸为24。一个label应与图像视图底部对齐。另一个label应位于第一个标签下方。它们的左边缘应该对齐,它们应该垂直间隔8个像素。同时将这些标签的尾部设置为距离容器视图右侧8个像素。他们应该有标题DescriptionPreferred way to kill
  • 5) 一个70×70的图像视图,与Preferred way to kill标签左对齐,8像素垂直间距。同时将其底部设置为距离容器视图底部8个像素。

让自动布局使用适当的约束尤其重要,因为这个应用程序是通用的,自动布局确保布局适应iPad和iPhone。

这就是现在的自动布局。 接下来,您需要将这些视图挂钩到某些outlets

打开DetailViewController.swift并将以下属性添加到类的顶部:

@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var descriptionLabel: UILabel!
@IBOutlet weak var iconImageView: UIImageView!
@IBOutlet weak var weaponImageView: UIImageView!

var monster: Monster? {
  didSet {
    refreshUI()
  }
}

在这里,您为刚刚创建的需要动态更改的各种UI元素添加了属性。 您还为此视图控制器应显示的Monster对象添加了一个属性。

接下来,将以下帮助器方法添加到类中:

private func refreshUI() {
  loadViewIfNeeded()
  nameLabel.text = monster?.name
  descriptionLabel.text = monster?.description
  iconImageView.image = monster?.icon
  weaponImageView.image = monster?.weapon.image
}

无论何时切换怪物,您都希望UI自行刷新并更新outlets中显示的详细信息。 你甚至可以在视图加载之前更改monster并触发方法。 因此,您调用loadViewIfNeeded()以保证视图已加载且其outlets已连接。

现在,打开Main.storyboard。 在Document Outline中右键单击Detail View Controller对象以显示插座列表。 从每个项目右侧的圆圈拖动到视图以连接outlets

请记住,图标图像视图是左上角的大图像视图。 武器图像视图是Preferred way to kill标签的方式下面较小的一个。

转到SceneDelegate.swift并使用以下内容替换scene(_:willConnectTo:options :)的实现:

guard 
  let splitViewController = window?.rootViewController as? UISplitViewController,
  let leftNavController = splitViewController.viewControllers.first 
    as? UINavigationController,
  let masterViewController = leftNavController.viewControllers.first 
    as? MasterViewController,
  let detailViewController = splitViewController.viewControllers.last 
    as? DetailViewController
  else { fatalError() }

let firstMonster = masterViewController.monsters.first
detailViewController.monster = firstMonster

拆分视图控制器具有一个数组属性viewControllers,其中包含主控制器和详细视图控制器。 在您的情况下,主视图控制器实际上是导航控制器。 因此,要获取实际的MasterViewController实例,请使用导航控制器的第一个视图控制器。

要获取详细视图控制器,请查看拆分视图控制器的viewControllers数组中的第二个视图控制器。

构建并运行应用程序,您应该在右侧看到一些怪物细节。

iPad上横屏:

iPhone

请注意,在MasterViewController上选择一个monster什么也没做,你就永远陷入了Cat-Bot。 这就是你接下来要做的事情!


Hooking Up the Master With the Detail

关于如何在这两个视图控制器之间进行最佳通信的策略有很多。 在Master-Detail App模板中,主视图控制器具有对详细视图控制器的引用。 这意味着主视图控制器可以在选择行时在详细视图控制器上设置属性。

这适用于在详细信息窗格中只有一个视图控制器的简单应用程序。 但是,您将遵循UISplitViewController类引用中建议的方法来处理更复杂的应用程序并使用委托delegate

打开MasterViewController.swift并在MasterViewController类定义上面添加以下协议定义:

protocol MonsterSelectionDelegate: class {
  func monsterSelected(_ newMonster: Monster)
}

这定义了一个带有单个方法的协议,monsterSelected(_ :)。 详细视图控制器将实现此方法,并且主视图控制器将在用户选择怪物时向其发送消息。

接下来,更新MasterViewController以添加符合委托协议的对象的属性:

weak var delegate: MonsterSelectionDelegate?

基本上,这意味着委托属性需要是一个实现了monsterSelected(_ :)的对象。 在用户选择怪物后,该对象将负责处理其视图中需要发生的事情。

由于您希望DetailViewController在用户选择怪物时更新,因此您需要实现委托。

打开DetailViewController.swift并在文件的最后添加一个类扩展:

extension DetailViewController: MonsterSelectionDelegate {
  func monsterSelected(_ newMonster: Monster) {
    monster = newMonster
  }
}

类扩展非常适合分离委托协议并将方法组合在一起。 在此扩展中,您说DetailViewController符合MonsterSelectionDelegate。 然后,您实现一个必需的方法。

现在委托方法已准备就绪,您需要从master方面调用它。

打开MasterViewController.swift并添加以下方法:

override func tableView(
    _ tableView: UITableView, 
    didSelectRowAt indexPath: IndexPath) {
  let selectedMonster = monsters[indexPath.row]
  delegate?.monsterSelected(selectedMonster)
}

实现tableView(_:didSelectRowAt :)意味着只要用户在表视图中选择一行,您就会收到通知。 您需要做的就是通知新怪物的怪物选择代理。

最后,返回SceneDelegate.swift。 在scene(_:willConnectTo:options:)中,在方法的最后添加以下代码:

masterViewController.delegate = detailViewController

这是两个视图控制器之间的最终连接。

iPad上构建并运行应用程序。 你现在应该可以在monsters之间进行选择,如下所示:

到目前为止,拆分视图非常好! 但是还有一个问题:如果你在iPhone上运行它,从主表视图中选择怪物不会显示详细视图控制器。 您现在需要进行一些小修改,以确保拆分视图也适用于iPhone。

打开MasterViewController.swift。 找到tableView(_:didSelectRowAt :)并将以下内容添加到方法的末尾:

if let detailViewController = delegate as? DetailViewController {
  splitViewController?.showDetailViewController(detailViewController, sender: nil)
}

首先,您需要确保代理已设置,并且它是一个DetailViewController实例,正如您所期望的那样。 然后在拆分视图控制器上调用showDetailViewController(_:sender :)并传入详细视图控制器。 UIViewController的每个子类都有一个继承属性splitViewController,它将引用它容器视图控制器(如果存在)。

此新代码仅更改iPhone上应用程序的行为,导致导航控制器在您选择新怪物时将细节控制器推入堆栈。 它不会改变iPad实现的行为,因为在iPad上,细节视图控制器始终可见。

进行此更改后,在iPhone上运行它现在应该正常运行。 只需添加几行代码,您就可以在iPad和iPhone上使用功能齐全的分割视图控制器。 不错!


Split View Controller in iPad Portrait

以纵向模式在iPad中运行应用程序。 起初,似乎没有办法进入左侧菜单。

但请尝试从屏幕左侧滑动。 很酷吧? 点按菜单外的任意位置即可隐藏它。

内置的滑动功能非常酷,但是如果你想在导航栏上方放置一个显示菜单的按钮,类似于它在iPhone上的表现怎么办? 要做到这一点,您需要对应用程序进行一些小的修改。

首先,打开Main.storyboard并将Detail View Controller嵌入到导航控制器中。 您可以通过选择详细视图控制器,然后选择Editor ▸ Embed In ▸ Navigation Controller来完成此操作。

您的故事板现在看起来像这样:

现在打开MasterViewController.swift并找到tableView(_:didSelectRowAt :)。 通过调用showDetailViewController(_:sender :)if块更改为以下内容:

if 
  let detailViewController = delegate as? DetailViewController,
  let detailNavigationController = detailViewController.navigationController {
    splitViewController?
      .showDetailViewController(detailNavigationController, sender: nil)
}

现在,您将显示详细视图控制器的导航控制器,而不是显示详细视图控制器。 无论如何,导航控制器的根目录是详细视图控制器,因此您仍然可以看到与之前相同的内容,只需将其包含在导航控制器中。

在运行应用程序之前,最后要进行两项更改。

首先,在SceneDelegate.swift更新scene(_willConnectTo:options:)中,通过替换初始化detailViewController的行来解释DetailViewController现在包含在导航控制器中的事实:

let detailViewController = 
  (splitViewController.viewControllers.last as? UINavigationController)?
    .topViewController as? DetailViewController

由于详细视图控制器包含在导航控制器中,因此现在有两个步骤来访问它。

最后,在方法结束之前添加以下行。

detailViewController.navigationItem.leftItemsSupplementBackButton = true
detailViewController.navigationItem.leftBarButtonItem = 
  splitViewController.displayModeButtonItem

这告诉详细视图控制器用一个按钮替换其左侧导航项,该按钮将切换拆分视图控制器的显示模式。 在iPhone上运行时不会改变任何东西,但在iPad上,你会在左上角看到一个按钮来切换table view显示。

iPad竖屏上运行应用程序并检查:

现在,您可以在纵向和横向上在iPadiPhone上运行良好的效果!

后记

本篇主要讲述了一个UISplitViewController的简单实用示例,感兴趣的给个赞或者关注~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容