UITableView 教程:动态 Table View Cell 高

原文地址: RAYWENDERLICH

说明:英文水平有限,主要是为了巩固学到的知识,也能帮别人快速上手,节约时间,有任何破绽,尤其是技术上的,请您一定要告诉我。


学习怎样在iOS8上使用Swift创建动态Cell高

旁白:其实这张图几乎说明了所有问题,设置好constraints,其它都不是问题。补充一下:这篇教程实际上就是 auto layout教程。)

你应该在使用 table view cells 的时候经常会写很多代码,来手动计算label,image view,text field 和 cell 相关的每一个控件的高。
坦白讲,这种方法很容易出错且容易让人迷糊看不懂。
在这篇教程里,你将学会怎样创建自定义 cell 并且根据内容动态调整 cell 的高度,你可能会想,"这得需要多少工作量...!"
Nope! * (旁白:you are right ~*)
你很幸运,Apple 可以让你在 iOS8 里很容易的做到这些。你将从写适配代码中解脱出来。但是还是要实现 table view 的数据源和代理方法。

让我们开始

iOS6出来几天之后,Apple 介绍了一个超赞的技术:auto layout。程序猿们开始庆祝,街头聚会,写赞歌...(旁白:要不要这么夸张啊

好吧,也许有些问题,但它毕竟是一次大飞跃(旁白:是这个意思吧,哈哈

在给了很多开发者希望的同时,auto layout 还是很难用的。尤其是手写 auto layout 代码。Interface Builder 在设置 constraints 时也并不理想。

很快到现在,伴随着对 Interface Builder 的所有改进和 iOS8 的到来,我们现在可以很容易的动态设置 table view cells 的高度了。

你需要不得不做的事情有:
1,在创建 table view cells 时使用 auto layout。
2,设置 table view 的** rowHeight 等于 UITableViewAutomaticDimension
3,设置
estimatedRowHeight ** 的值或者实现预估高度的代理方法。

这是你需要知道的几个要点,现在开始下载代码,搞起项目了。
旁白:确实很重要,虽然说了不少废话,但是并不是浪费时间的。

教程 App 概览

设想一下你的老大来到你面前,对你说:“我们的用户在为看** Deviant Artists 的方法而大声抗议”。
我会问:"什么是
Deviant Artists "。
你的头解释说:“那是一个艺术家们用来分享自己作品的社交平台。你可以通过 Deviant Art websiteMedia RSS endpoint 来了解艺术家的公告和动态。”
老大:“我们开始做这个 App 吧,但是要怎么样把内容显示到表格上呢?你能做到吧?”
你突然受到了感召,走进最近的电话亭,换上了披风成了super Dev..


但你不需要弄骗人的把戏做你老大的英雄,用你的编程技术就可以做到了。
旁白:你怎么不去做导演啊...

首先,现在客户端代码(项目的起始程序)这里
旁白:语法有一些过时,打开项目会自动让你转换到最新的语法,转换完之后会报一个错,将 ** FeedViewController.swift 里的 deselectAllRows **方法替换成如下代码:

func deselectAllRows() {
    if let selectedRows = tableView.indexPathsForSelectedRows{
      for indexPath in selectedRows {
        tableView.deselectRowAtIndexPath(indexPath, animated: false)
      }
    }
  }


这个项目使用的** CocoaPods ,因此打开 DeviantArtBrowser.xcworkspace ** (不是** xcodeproj 这个文件),pods 已经包含到 zip 包里了,所以不用在重新 pod install **。
注意:如果你不清楚什么是 CocoaPods ,可以看下这个教程

打开** Main.storyboard (在 DeviantArtBrowser ** project 下的** DeviantArtBrowser 文件夹 Views **分组里),你将会看到下面四个场景:


从左到右,它们是:

  • 顶级的导航控制器。
  • **FeedViewController **,标题是 ** Deviant Browser **。
  • 还有两个都是** DetailViewController 的场景,标题分别是Deviant Article** 和 Deviant Media ,一个只用来显示文本,另一个文本和图片一起显示。
    编译并运行,你将看到一些控制台上的输出日志和一个短暂出现的活动指示器,但是在 app 里并没有什么内容显示出来。
    日志输出像下面一样:

2014-11-08 14:30:02.746 DeviantArtBrowser[70847:829282] GET 'http://backend.deviantart.com/rss.xml?q=boost%3Apopular'

2014-11-08 14:30:03.297 DeviantArtBrowser[70847:829282] 200 'http://backend.deviantart.com/rss.xml?q=boost%3Apopular' [0.5506 s]

app 发起一个网络请求并获得返回,但是并没有做任何事。
旁白:如果请求失败,很可能是iOS9系统下不可用http协议,打开info.plist的源码,粘贴如下内容。

<key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>

现在,打开** FeedViewController.swift(在 Controllers **文件夹的下面)。
看下 ** parseForQuery **这段代码:

func parseForQuery(query: String?) {
  showProgressHUD()
 
  parser.parseRSSFeed(deviantArtBaseUrlString,
    parameters: parametersForQuery(query),
    success: {(let channel: RSSChannel!) -> Void  in
 
      self.convertItemPropertiesToPlainText(channel.items as! [RSSItem])
      self.items = (channel.items as! [RSSItem])
 
      self.hideProgressHUD()
      self.reloadTableViewContent()
 
    }, failure: {(let error:NSError!) -> Void in
 
      self.hideProgressHUD()
      println("Error: \(error)")
  })
}

** parser 是一个 RSSParser 的实例,属于 MediaRSSParser 的一部分。
这是一个得到
Deviant Art ** RSS feed 的网络请求,它会在成功的 block 里返回一个** RSSChannel 实例。然后解析数据将 HTML 转成普通文本, channel.items 和控制器里的 items **属性都是数组。

** channel.items 数组包含 RSSItem **对象,每一个对象元素都是一个 RSS feed。(现在你该知道要将什么显示到表格里了吧,正是 ** items **数组!)

最后,项目里会有一些** //TODO:Write this... **的注释,是为了告诉我们需要实现些什么。

开始创建自定义 Cell

查看源代码之后,你现在知道这个 app 有了不错的数据,但是什么都没有显示出来,要显示它们,你需要创建一个自定义的 table view cell。

  • 添加一个新类到** DeviantArtBrowser**项目里。
  • 名字是 ** BasicCell 并且继承自 UITableViewCell **。
  • 确信** Also create xib file **没有被勾选。
  • 语言选择** Swift
    打开
    BasicCell.swift ** 并添加如下属性:
@IBOutlet var titleLabel: UILabel!
@IBOutlet var subtitleLabel: UILabel!

接下来,打开** Main.storyboard ,拖拽一个UITableViewCell到** FeedViewController 的 table view上。
设置
BasicCell Custom Class


设置
BasicCell Identifier (Reuse Identifier)

设置 cell 的
Row Height 83

拖拽一个 UILabel 到 cell 上,设置 text为
Title

设置 label 的
Lines 为 0,就是没有上限。(行数可以设置很多)

接下来像下面截图一样设置 label 的尺寸和位置。

连接 cell 的 label 到
BasicCell titleLabel ** outlet 上。

接下来,拖拽第二个 UILabel 到 cell 上,像第一个 label 一样,并且设置 text 为** Subtitle

像第一个 label 一样,按照下面的截图来设置 Subtitle 的尺寸和位置。

设置 subtitle label 的
Color ** 为 * Light Gray Color ;字体大小为 * 15.0 ;并且 Lines **为 0 ;

将 cell 的 subtitle label 连接到 BasicCell 上的 subtitleLabel outlet上。

接下来,你将给** BasicCell **添加 auto layout ** constraints ** ,来布局 cell 。
注意:如果你对 auto layout 还不太熟悉,不清楚怎么设置 auto layout constraints ,可以看下 这个教程

选择 title label 且设置它的 toptrailingleading 距离父视图(也就是content view)20个点。确信你没有勾选Constrain to margins。(旁白:这个属性就是系统会为你默认两边留白)

图片显示的很清楚

确信 cell 的 title label 一直是:

  • 向上距离20个点。
  • 相对于 content view 的整体宽度,左右两边空出20个点。

现在,选择 subtitle label 设置它的 leadingtrailing,和** bottom 距离父视图20个点。再次确认,没有勾选Constrain to margins


像 title label 一样,确定在 subtitle label 上的
constraints ,是按照底部距离 content view 20个点,左右距离 content view 也是20个点。
旁白
:还是看图更直观一些)

技巧: 使用 auto layout 布局 UITableViewCell 时,要确定这些约束都布局到了每一个 subview 的四边,也就是说,每一个 subview 都应该有 leading,top,trailing 和 bottom 约束。
除此之外,** contentView 的顶部到底部都要有清晰的约束条件, 你要能确定,这些子视图的约束可以正确的指定出 contentView **的高度。
另一部分技巧是,interface Builder 经常在你缺失一些约束的情况下,没有警告提示。在运行项目时,auto layout 没有返回正确的高度,比如会返回 0 的高度,遇到这些问题需要你重新调整约束条件 直到满足条件为止。

现在, 选择 subtitle label, 按住 Control 并拖拽到 title label。选择 Vertical Spacing 连接** subtitle label 的顶部和 title label **的底部。

在 title label 上,设置 ** Horizontal** 和 ** Vertical** 的 Content Hugging PriorityContent Compression Resistance Priority751


在 subtitle label 上,设置 ** Horizontal** 和 ** Vertical** 的 Content Hugging PriorityContent Compression Resistance Priority750

旁白:解释一下,这两个优先级的意思,**Content Hugging Priority **就是级别越高,越不会被拉开,抻开。 **Content Compression Resistance Priority **就是级别越高,越不会被压缩,挤掉。这个还是要看具体的例子来理解的,有一点绕。)
这就是告诉 auto layout 怎样去适配 labels 的文本-区分 title label 和 subtitle label 之间约束的优先级。在这个例子里,这些约束基本满足了条件。

检查: 上面的约束条件满足情况了吗?
1,每一个子视图的所有侧边都有约束吗?Yes。
2,**contentView 从上到下都有约束吗?Yes(旁白:只有这样 contentView 才能确定出自己的高度,像 UIScrollView 一样,才能知道自己的 contentSize,否则运行起来都不能滑动。)
**titleLabel **距顶部有20个点,它和 **subtitleLabel **之间的距离是4个点,并且 ** subtitleLabel **和底部有19.5个点。
所以,现在 auto layout 已经可以动态设置 cell 的高度了。

接下来,你要创建一个 ** BasiceCell ** 跳转到 **Deviant Article **场景的链接(segue)。

选择你的 BasiceCell,按住 control 拖拽到 **Deviant Article 场景,从 Selection Segue **选项中选择 **Push **。

Interface Builder 将自动更改 Accessory 属性为 Disclosure Indicator,这是为了指示你从 cell 导航到详情里去。然而,这并不符合程序的设计,选择 **BasiceCell ** ,更改 ** Accessory **为 None


现在,用户在任何时候点击 BasicCell 都会跳转到 **DetailViewController **里了。

哇塞,你的** BasicCell 已经设置完了!如果你编译运行 app 的话,还是毛都看不到,为啥呢...(旁白:自言自语..)


还记得哪些** TODO **的注释吗?,对了,这就是问题所在了。你需要到哪些 TODOs 里写一些代码。

配置 Table View

首先,你需要配置下这个 table view。
打开** FeedViewController.swift ** ,用下面的代码替换**configureTableView() **这个方法。

func configureTableView() {
  tableView.rowHeight = UITableViewAutomaticDimension
  tableView.estimatedRowHeight = 160.0
}

确信 table view 是用 auto layout 来动态设置高的时候,将 **rowHeight 设置为 UITableViewAutomaticDimension
确定这些之后,你还要提供一个 ** estimatedRowHeight 的值。小意思,160.0
是一个随意的值也可以使用。在你的项目里,你可能也想根据数据类型设置一个更好的值。

实现 UITableView 的 Data Source

接下来,你需要实现** UITableViewDataSource 的协议方法。
首先,在
FeedViewController **里加上这个常量:

let basicCellIdentifier = "BasicCell"

这可以让你用这个标识在 storyboard 里取得 ** BasicCell
接下来,用
tableView(_:numberOfRowsInSection:) **返回从 Deviant Art 获取到数据个数:

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  return items.count
}

然后用,下面的代码替换** tableView(_:cellForRowAtIndexPath:) **这个方法。

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  return basicCellAtIndexPath(indexPath)
}
 
func basicCellAtIndexPath(indexPath:NSIndexPath) -> BasicCell {
  let cell = tableView.dequeueReusableCellWithIdentifier(basicCellIdentifier) as! BasicCell
  setTitleForCell(cell, indexPath: indexPath)
  setSubtitleForCell(cell, indexPath: indexPath)
  return cell
}
 
func setTitleForCell(cell:BasicCell, indexPath:NSIndexPath) {
  let item = items[indexPath.row] as RSSItem
  cell.titleLabel.text = item.title ?? "[No Title]"
}
 
func setSubtitleForCell(cell:BasicCell, indexPath:NSIndexPath) {
  let item = items[indexPath.row] as RSSItem
  var subtitle: NSString? = item.mediaText ?? item.mediaDescription
 
  if let subtitle = subtitle {
 
    // Some subtitles are really long, so only display the first 200 characters
    if subtitle.length > 200 {
      cell.subtitleLabel.text = "\(subtitle.substringToIndex(200))..."
 
    } else {
      cell.subtitleLabel.text = subtitle as String
    }
 
  } else {
    cell.subtitleLabel.text = ""
  }
}

这边发生什么:

  • 在 tableView(:cellForRowAtIndexPath:) 里,调用basicCellAtIndexPath(:) 方法获取一个** BasicCell **。
  • 在basicCellAtIndexPath(:)里获取一个** BasicCell **,使用setTitleForCell(:indexPath:) 方法设置 title label 的text,使用** setSubtitleForCell(_:indexPath:) **方法设置 subtitle label 的text,然后return 这个 cell。

现在,你需要实现... 等等,搞定了!就这么简单?
编译运行,你将看到这个表格:


图片在哪呢?

这个app看起来还不错,但是好像感觉缺了点什么?
噢,艺术在哪呢?

Deviant Art 上都是图片,但这个 app 没有显示它们,你需要去修复下这个问题,不然,你的老大会让你失去理智!

有个方法是在你的** BasicCell **上加一个 image view。

但是在 Deviant Art 上带图片的信息和纯文字的信息都有,所以更好的做法是新建一个自定义 cell。

增加一个继承自** BasicCell **的新类到项目里,名字叫 ImageCell,原因是你的新 cell 也需要 titleLabelsubtitleLabel。因此,有必要在基类里已经存在一些方法的时候再做所有的事吗?

打开** ImageCell.swift ** 并且增加下面的属性:

@IBOutlet var customImageView: UIImageView!

这个属性的名字是** customImageView,因为在 UITableViewCell 里已经有了一个叫 ImageView **的属性了。

打开** Main.storyboard ,选择 basic cell 使用 ⌘C ,或者从菜单里选择 Edit > Copy **。

选择这个 table view 并且 按下 **⌘V ,或者 Edit > Paste **,去创建一个 cell 的新 copy 。

注意:如果你操作有误,没有得到想要的结果,记得使用** ⌘Z 或者 Edit > Undo **来撤销操作。

选择新 cell ,更改它的Custom Class为** ImageCell **。同样的,更改它的 Reuse Identifier ** 为 ImageCell **。

在** ImageCell 上选择 title label,更改它的位置 x 为128**,且宽度为 172。subtitle label 也是一样。

Interface Builder 将会有一些警告,因为这些 labels 摆放的位置和约束设置的不一样。

正确的做法是,选择** ImageCell 上的 title label 删掉 leading 这条约束,subtitle label 也是一样删掉 leading **约束。

现在选择** ImageCell 的 title label,按照下面的截图改变它的 Intrinsic Size Placeholder 的值。同样的,改变 subtitle label 的 Intrinsic Size
(这是用来告诉 Interface Builder 去更新当前 view 的 frame 的占位符,Interface Builder 将不会显示警告了。)


你需要在 cell 上增加一个 image view。但是现在的高度有一点小,所以选择
ImageCell 修改它的 Row Height 141**。

现在,拖拽一个 image view 到** ImageCell **上,按照下面的截图设置这个新 view 的位置和尺寸。


接下来,选择这个 image view 做如下布局:

  • 设置 leadingtopbottom20
  • 设置** width height 100**。
  • 确认 **Constrain to margins **没有被勾选
  • 最后点击** Add 5 constrain **的按钮。


选择** image view ** 显示它的所有约束,然后选择它的 bottom 约束来编辑它。在属性编辑器里,改变** Relation Greater Than or Equal 它的 Priority 999


同样的,选择 subtitle label 去显示它的所有约束条件,然后选择它的
bottom ,在属性编辑器里,改变 Relation Greater Than or Equal 它的 Priority**为 1000

这是告诉 auto layout 在** imageView subtitleLabel 向下的约束都为20个点的时候,打破 imageView 的,遵循 subtitleLabel 的。(旁白:因为它的优先级高,这样避免约束冲突。)

然后将 image view 的** height width 约束的优先级 Priority 设置成 999
这是因为自定义的约束有时候会和系统定义的约束之间会产生冲突,这时就是告诉 auto layout ,“如果一定要这么做,就打破这些自定义的约束吧”
旁白
:系统定义的优先级高,1000。)

在大部分情况下,auto layout 都会满足这些约束。在极少数的情况下它会打破这些约束,比如在改变设备方向时,但通常都是1-2的像素偏差,不明显。

提示: 尤其在 table view cell上, auto layout 不总是很明显的提示这些约束。
如果你在控制台上看到这些 auto layout 不得不打破一个约束的警告信息,你就要试着去调整下你的约束条件的** priorities **了。

最后,选择 ImageCell上的 title label 使用 Pin Button 来设置下 leading 约束为 8。subtitle label 也是一样的。

现在你的** ImageCell **上的约束看起来是这个样子的:


你需要选择** ImageCell 和 image view 的 customImageView ** outlet 进行链接。

你需要一个** ImageCell 跳转到 Deviant Media 场景的链接(segue),
这样用户点击
ImageCell **就可以看到详情了。

像之前设置 basic cell 一样, 选择** ImageCell ,按住 control 并拖拽到 Deviant Media 场景,然后在 Selection Segue 选项中选择 Push **。

确认你的** Accessory 改回为 None **。

非常好,你的** ImageCell **已经设置完成了!现在你可以加一些代码让它显示出来了。

显示这些图片!

打开** FeedViewController.swift ** 在上面增加一个常量:

let imageCellIdentifier = "ImageCell"

接下来替换** tableView(_:cellForRowAtIndexPath:) **的代码:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  if hasImageAtIndexPath(indexPath) {
    return imageCellAtIndexPath(indexPath)
 
  } else {
    return basicCellAtIndexPath(indexPath)
  }
}
 
func hasImageAtIndexPath(indexPath:NSIndexPath) -> Bool {
  let item = items[indexPath.row]
  let mediaThumbnailArray = item.mediaThumbnails as! [RSSMediaThumbnail]
 
  for mediaThumbnail in mediaThumbnailArray {
    if mediaThumbnail.url != nil {
      return true
    }
  }
 
  return false
}
 
func imageCellAtIndexPath(indexPath:NSIndexPath) -> ImageCell {
  let cell = self.tableView.dequeueReusableCellWithIdentifier(imageCellIdentifier) as! ImageCell
  setImageForCell(cell, indexPath: indexPath)
  setTitleForCell(cell, indexPath: indexPath)
  setSubtitleForCell(cell, indexPath: indexPath)
  return cell
}
 
func setImageForCell(cell:ImageCell, indexPath:NSIndexPath) {
  let item: RSSItem = items[indexPath.row]
 
  // mediaThumbnails are generally ordered by size,
  // so get the second mediaThumbnail, which is a
  // "medium" sized image
 
  var mediaThumbnail: RSSMediaThumbnail?
 
  if item.mediaThumbnails.count >= 2 {
    mediaThumbnail = item.mediaThumbnails[1] as? RSSMediaThumbnail
 
  } else {
    mediaThumbnail = (item.mediaThumbnails as NSArray).firstObject as? RSSMediaThumbnail
  }
 
  cell.customImageView.image = nil
 
  if let url = mediaThumbnail?.url {
    cell.customImageView.setImageWithURL(url)
  }
}

像上面创建** BasicCell **一样,但是有一点不同,有一些新的代码:

  • hasImageAtIndexPath(_:) 检查IndexPath下的item的** mediaThumbnail的 url 不为空。如果不为空的要使用 ImageCell **来展示数据。
  • **imageCellAtIndexPath(:) basicCellAtIndexPath(:) 一样,但是它要用 setImageForCell(_:indexPath:) **来设置下图片。
  • ** setImageForCell(:indexPath:) ** 尝试获取第二个 media thumbnail。使用 AFNetworking提供的** setImageWithURL(:) **方法来获取图片。

编译运行,你将看到漂亮的艺术图片!默认,app 检索 “popular“ 分类的数据,但你也能按照艺术家搜索。

试着输入** by:CheshireCatAr t** 并搜索。这是我的一个朋友,Devin Kraft,他是一个杰出的画家。(查看他的website)。

这是我很喜欢的一位,他在 Deviant Art 上很活跃,发过作品和博客。因此,他的账号是个很好的测试账号,可以测试带图片的 cell 和不带图片的 cell。


这个 app 已经看起来很漂亮了,但是你还可以让你的老大对你的技能信心提升一到两个级别。

优化表格

还记得很早之前设置** estimatedRowHeight 160.0 的时候吗?这个属性是在 BasicCell **的竖屏方向上工作的。但是这个值非常的不准。

你可以使用** UITableViewDelegate **提供的,在运行时给 cell 估算高度的方法替换这个值。

改之前,你需要删除** configureTableView(): **里的一行代码:

tableView.estimatedRowHeight = 160.0

现在,在** // MARK: UITextFieldDelegate **组下增加下面的方法。(实际上,你可以在这个类的任何地方增加它们,但这样结构比较清楚易读。)

// MARK: UITableViewDelegate
 
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
  if isLandscapeOrientation() {
    return hasImageAtIndexPath(indexPath) ? 140.0 : 120.0
  } else {
    return hasImageAtIndexPath(indexPath) ? 235.0 : 155.0
  }
}
 
func isLandscapeOrientation() -> Bool {
  return UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication().statusBarOrientation)
}

这个预估 cell 高的方法很简单。检查当前方向是否是横屏并且如果当前 index path 有图片的话,返回一个预先裁定的值。

提示:不论你是实现的代理方法,还是简单的设置了一个** estimatedRowHeight 固定的值。
这个 table view 会在代理方法和
estimatedRowHeight 的值之中选择一个。它会影响到滑动的指示条和滑动的性能。(是否卡顿)
如果你的 cell 预估的高度不太正确,那么在滑动的时候就会卡顿,滑动指示条不太准,内容也会混乱。
如果你的预估是准确的,计算就会慢,table view 滚动也会变慢。
这个成功的关键就在于在准确和不准确之间找一个平衡,减少不必要的计算成本。
旁白
:难道自己慢慢调试这个数值吗?)

你还是使用固定的值来估算的高度,但是现在可以根据 cell 的类型和设备的方向来设置更合理的高度了。你可以设置自己感兴趣的值,但是记住诀窍就是能让它计算的更快就行。

编译并运行,你应该能看到 table view 滑动的很流畅,看起来很棒。

这就是方法的最后实现,这个 app 现在完成了!


从这去哪呢

你可以去下载完成项目,在这
旁白:上面的路径下载下来的项目编译会报错,因为它是用Xcode6.3和Swift1.2做的,转换到最新语法之后,在像上面已经提到过的修改一个方法的代码,添加http白名单。你也可以下载这里已经修改过的代码

Table views 可能是iOS里面组织数据视图中最常用的了。你的 apps 会很复杂,你可能要使用各种类型的自定义 cell 来布局。幸运的是 iOS8 和 auto layout 能很容易的做到这些。
旁白:不幸的是,有多少 app 是只支持iOS8以上的呢。)

如果你有一些问题或建议,请在下面留言。

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

推荐阅读更多精彩内容

  • 自适应Table View Cells 注意:这篇教程支持最新的Xcode 7.3,iOS 9和Swift 2.2...
    张嘉夫阅读 3,012评论 9 50
  • 我认为这是最好的建议:不断的思考你怎样才能把事情做得更好并且不断的质疑自己。-Elon Musk,Tesla Mo...
    运营老周阅读 5,251评论 1 9
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 没有人会因为你的一点小破事而改变什么,你的悲伤太渺小,无论放在哪里就像石沉大海。只有自己去让自己快速成长起来,否则...
    晗凌阅读 110评论 0 0
  • 年近四十,却越发的觉得自己年轻了起来,对比以前的照片,我不禁发出感慨,谁说女人四十豆腐渣,只要你自己用心浇灌,一样...
    子林林阅读 471评论 2 2