使用RxSwift和RxCocoa拆分计时器(一)

> 本文翻译自:http://rx-marin.com/post/rxswift-rxcocoa-sample-split-laps-timer/

作者:[Marin Todorov ](http://www.underplot.com/)

----

我正在浏览[RxMarbles](http://rxmarbles.com/),完全被[`sample`](http://rxmarbles.com/#sample)函数所困扰。流程图看起来很随机:

![marble diagram](http://rx-marin.com/images/marbles-sample.png)

起初我想 - “嘿,第二个序列正在被完全忽略!”,但是在我阅读之后,我发现:

`第一个序列的元素是由样品发出的,而第二个序列的元素决定样品发出的时间,所以在某种程度上是-实际上A,B,C,D确实被完全忽略。`

当我明白了`sample`做了些什么,我开始想知道这个功能是否有实际的用处:]

这让我想创建一个拆分计时器(注: `split lap timer`)应用程序来测试`sample`可以为我们做什么。在完成的项目中,我有一个定时器发出时间值(也就是一个序列),同时我也想要抓住(注: grap)或者`sample`每一次用户点击(是另一个序列)按钮的反应值。

下面是应用程序设置的流程图:

![marble diagram when app setup](http://rx-marin.com/images/sample-dia.png)

这是完成后的应用程序的样子:

![finished](http://rx-marin.com/images/laptimer-finished.png)

让我们开始构建这个应用吧:]

下面是我想要的拆分计时器应用的运行与功能:

1. 在程序启动时就启动计时器

2. 以 `MM:SS:MS` 的形式显示运行时间

3. 当用户点击了按钮`Split Lap`的时候添加一条拆分时间值

4. 以一个列表的形式展示拆分时间

5. 以列表头的形式展示拆分时间数目总和

### 1. 启动计时器

像我在[前一篇文章](http://rx-marin.com/post/rxswift-timer-sequence-manual-dispose-bag/)中关于手动处理包内容那样,我添加一个`timer`。

```

var timer: Observable!

```

同时在`viewDidLoad`中让它每1/10秒运行一次。(我选择只显示一位数毫秒,所以不需要频率更高:])

```

//create the timer

timer = Observable.interval(0.1, scheduler: MainScheduler.instance)

timer.subscribeNext({ msecs -> Void in

 print("\(msecs)00ms")

}).addDisposableTo(bag)

```

这会使计时器运行,并填满控制台:

```

000ms

100ms

200ms

300ms

400ms

500ms

600ms

700ms

800ms

900ms

1000ms

1100ms

```

酷,这很好:](你一定会想,“好吧,我已经知道如何做这个了”)

### 2. 显示当前已过去时间

这也是我已经知道如何去做的部分。首先我写了一个小的函数,该函数会将已过去的时间格式化成想要的字符串。

```

func stringFromTimeInterval(ms: NSInteger) -> String {

 return String(format: "%0.2d:%0.2d.%0.1d",

   arguments: [(ms / 600) % 600, (ms % 600 ) / 10, ms % 10])

}

```

然后回到`viewDidLoad`,我使用该函数绑定计时器到通过`Interface Builder`创建的Label中。

```

//wire the chrono

timer.map(stringFromTimeInterval)

 .bindTo(lblChrono.rx.text)

 .addDisposableTo(bag)

```

我真的很喜欢代码运行的步骤,下面是代码中一步步发生的事情:

```

timer -> 1,2,3 -> stringFromTimeInterval -> "string", "string" -> lblChrono

```

函数式的代码非常棒,因为我已经取得了两个巨大胜利,我可以很容易重用`stringFromTimeInterval`,然后我也可以写一些非常简单的测试代码。

与此同时,定时器`label`已经成功显示了当前运行的时间:

![当前运行时间](http://rx-marin.com/images/laptimer-label.png)

### 3. 当用户点击了`Split Lap`按钮的时候,抓取当前拆分的时间

在这里我应该可以达到我的终极胜利:使用`sample`。前几次尝试并没有让我迈出大步,直到我意识到`rx.tap`也是一个`Observable`。

> Duh,一切都是`Observable`:

现在只剩下`timer`调用`sample`这一个问题了,并提供作为控制序列的按钮的属性`rx.tap`,就像这样:`timer.sample(btnLap.rx.tap)`,怎么去做?(Whaaat?)

现在我每次点击按钮`sample`都会发出`timer`产生的最新值。由于我对数字不感兴趣,但是在格式化字符串的时候我再次将结果使用`stringFromTimeInterval`转换。

因为我需要创建一个分离的时间列表,我使用`scan`。实际上我第一次是想用`reduce`,因为我想在列表中累加值,但是后来意识到,我需要产生一个序列,同时这个序列发出每个新值得列表。因此我意识到了我得使用`scan`。

```

let lapsSequence = timer.sample(btnLap.rx_tap)

.map(stringFromTimeInterval)

.scan([String](), accumulator: {lapTimes, newTime in

return lapTimes + [newTime]

})

.shareReplay(1)

```

所以,每次`sample`发出新的拆分时间时,`scan`会扫描到目前为止所有拆分时间的数组。

不知道如何解释`scan`更简单,但是我会尝试:在`RxSwift`中的任何时候,如果你正想使用`reduce`,那么你也可以使用`scan`代替:]

### 4. 显示到目前为止所有的拆分时间表

OK, 所以我得到了`lapsSequence`发出的拆分时间数组。在参考了`RxExample`之后,我们就可以操作`UITableView`了。

```

//show laps in table

lapsSequence

.bindTo(tableView.rx.items(cellIdentifier: "Cell", cellType: UITableViewCell.self)) { (row, element, cell) in

cell.textLabel?.text = "\(row + 1)) \(element)"

}

.disposed(by: bag)

.addDisposableTo(bag)

```

同时我的APP已经正常工作了。

![Working](http://rx-marin.com/images/laptimer-table.png)

我每一次点击`Split Lap`按钮,都会添加一条拆分时间到TableView上。Sweet~

### 5. 在TableView标题上显示拆分时间总数

这部分是让我最失望的。因为没有一个绑定我可以用在TableView的Header上,同时我又不想添加数据源,这样会使代码复杂化。

于是我突然想到在我的控制器中添加一个Label,并将它作为TableView的header,然后将拆分次数总数绑定到这个label的`rx.text`上。

所以我添加一个label到视图控制器上:

```

let tableHeaderView = UILabel()

```

然后是一个扩展,并将该label设置为TableView的header:

```

extension ViewController: UITableViewDelegate {

func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

return tableHeaderView

}

}

```

我知道如何设置TableView的代理到ViewController这个类中(让我们回到ViewDidload中):

```

//set table delegate

tableView

.rx.setDelegate(self)

.addDisposableTo(bag)

```

现在又出现了变况,我不得不将`lapsSequence`从数组映射到单个字符上(例如:5次),并将该字符串绑定到TableView header上。

我过度兴奋地想使用`scan`,但是代码确实有些不优雅。后来在RxSwift slak问了[KrunoslavZaher](), 他启发了我,因为我有一个数组,这样我就可以使用`map`将其转换成字符串。

下面是最终在`viewDidLoad`中的代码:

```

//update the table header

lapsSequence.map({ laps -> String in

return "\t\(laps.count) laps"

})

.startWith("\tno laps")

.bindTo(tableHeaderView.rx_text)

.addDisposableTo(bag)

```

因为`lapsSequence`发出的是所有拆分时间的数组,每次发出一个新的拆分时间,我只取该数组,并返回一个包含元素数量的字符串。

此外,我将初始值设置为`no laps`, 这样很好。我将所有内容直接绑定到`tableHeaderView.rx_text`上。

这是完整的应用程序!

![](http://rx-marin.com/images/laptimer-finished.png)

您可以下载完成的项目,并在这里尝试:[rx_laptimer.zip](https://github.com/qiuncheng/posted-articles-in-blog/tree/master/Demos).

希望这篇文章是有帮助的,如果你想联系你,你可以找到我在这里[Twitter](https://twitter.com/intent/follow?original_referer=http%3A%2F%2Frx-marin.com%2Fpost%2Frxswift-rxcocoa-sample-split-laps-timer%2F&ref_src=twsrc%5Etfw®ion=follow_link&screen_name=icanzilb&tw_p=followbutton)

> 注: 原文提供的代码下载地址还不是Swift 3的,所以我在这里已经更改了原作者提供的地址。代码经过`Xcode8.2.1`测试,完全可以测试通过。

对于英语好的同学,推荐阅读作者的英文原文。  

 

 

 

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

推荐阅读更多精彩内容