[Swift] UIGestureRecognizerでストップウォッチを作ってみた。

Xcodeの勉強の手始めに、ストップウォッチを作ってみるところから始め、おおよそ完成しましたので共有します。

##いきさつ
基本的なストップウォッチの作り方は[ニコニコ動画](http://www.nicovideo.jp/watch/sm24733337)で学習しました。で、大体のストップウォッチにはスタートボタンとストップボタンとリセットボタンとが別々にあり、なんか**もっと直感的に操作できてもいい**んじゃないかな？（例えば**表示をタップすればスタート／ストップ**）と思いましたのでこの改良に昼夜をそそいで参りました。

画面全体のどこか(ViewController.view)をタップすればスタート／ストップ、というのはその日のうちにすぐできました。
で、それでは不満でしたのでストップウォッチ機能を持つ```UILbel```を継承して作り、[このあたり](http://qiita.com/osamu1203/items/9c5680d78d77146a9cfc)と[このあたり](http://qiita.com/edo_m18/items/5dd48b7ee1e6899b8ed5)を参考に、触れる```UILabel```の継承クラスに改良し、[このあたり](http://qiita.com/mochizukikotaro/items/a5bc60d92aa2d6fe52ca)と[このあたり](http://qiita.com/tkuma/items/e50831b7c3aafbdd4bab)を参考にデリゲートを実装してラップ機能を持たせることに成功しました。

![2015-02-03 15.56.20.png](https://qiita-image-store.s3.amazonaws.com/0/10766/061e62d0-ce54-d461-2b01-5449e259b37b.png)

##コード
共有用にStoryBoardを使わないコーディングにしました。

```ViewController.swift
import UIKit

protocol CountNumDelegate: class {
    func rapDelegate(lastRap: Int) -> ViewController
}

class TimerView :UILabel {
    var timerOn = false
    var nsTimer = NSTimer()
    var countNum :Int
    var lastRap = 0
    weak var delegate :CountNumDelegate!
    
    func update() {
        countNum++
        self.text = timeFormat(countNum)
    }
    
    func timeFormat(var num :Int)-> String {
//        if num < 0 { num = 0 }
        let ms = num % 100
        let s = (num - ms) / 100 % 60
        let m = (num - s - ms) / 6000 % 3600
        return String(format: "%02d:%02d.%02d", arguments: [m,s,ms])
    }
    
    override init(frame :CGRect) {
        countNum = lastRap
        super.init(frame: frame)
        self.userInteractionEnabled = true  // 地味に必須

        let tap = UITapGestureRecognizer()
        let swipeRight = UISwipeGestureRecognizer()
        swipeRight.direction = UISwipeGestureRecognizerDirection.Right
        let swipeDown = UISwipeGestureRecognizer()
        swipeDown.direction = UISwipeGestureRecognizerDirection.Down
        
        tap.addTarget(self, action: "startAndStop")
        swipeRight.addTarget(self, action: "reset")
        swipeDown.addTarget(self, action: "rap")

        self.addGestureRecognizer(tap)
        self.addGestureRecognizer(swipeRight)
        self.addGestureRecognizer(swipeDown)

        self.text = timeFormat(countNum)
        self.backgroundColor = UIColor.redColor()
        self.font = UIFont(name: "Symbol", size: 60.0)
        self.textAlignment = NSTextAlignment.Center
        self.baselineAdjustment = UIBaselineAdjustment.AlignCenters
//        self.layer.cornerRadius = 5 //      ?
    }

    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func startAndStop() {
        if timerOn == false {
            nsTimer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: Selector("update"), userInfo: nil, repeats: true)
            timerOn = true
            self.backgroundColor = UIColor.greenColor()
            NSLog("Tap to start")
            
        } else {
            nsTimer.invalidate()
            timerOn = false
            self.backgroundColor = UIColor.redColor()
            NSLog("Tap to stop")
        }
    }

    func reset() {
        if timerOn == false {
            countNum = 0
            lastRap = 0
            self.text = timeFormat(countNum)
            NSLog("Swiped Right to reset")
        }
    }
    
    func rap() {
        lastRap = countNum - lastRap
        NSLog("Swiped Down at count \(timeFormat(lastRap))")
        if timerOn == true {
            delegate.rapDelegate(countNum).draw(6)   // ViewControllerのメソッドを呼ぶ
        }
    }
}

class ViewController: UIViewController, CountNumDelegate {

    var timerLabel = [
        TimerView(frame: CGRectMake(0, 0, 250, 80)),
        TimerView(frame: CGRectMake(0, 0, 250, 80)),
//        TimerView(frame: CGRectMake(0, 0, 250, 80)),
    ]

    func rapDelegate(countNum :Int) -> ViewController {
        let rapped = TimerView(frame: CGRectMake(0, 0, 250, 80))
        rapped.userInteractionEnabled = false  // 再稼働禁止
        rapped.lastRap = countNum
        timerLabel.insert(rapped, atIndex: 1)
        rapped.countNum = countNum - timerLabel[2].lastRap
        let t = timerLabel[1]
        t.lastRap = countNum
        t.text = rapped.timeFormat(rapped.countNum)
        NSLog("\(t.timeFormat(t.lastRap))")
        return self
    }

    func draw(max: Int) {
        // 既存のビューを一度すべて消す
        let subviews = self.view.subviews as [UIView]
        for v in subviews {
            if let timerView = v as? TimerView {
                    timerView.removeFromSuperview()
            }
        }
        // 並べ直し
        var num = 0
        for t in timerLabel {
            t.center = CGPointMake(self.view.bounds.width / 2, self.view.bounds.height * CGFloat(++num) / 7)
            if num <= max {
                self.view.addSubview(t)
            }
        }
    }
    
    override func viewDidLayoutSubviews() {
        draw(6)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.timerLabel[0].delegate = self
        self.draw(1)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}
```

##特徴
すべてのアクションはジェスチャーで解決します。**スタート／ストップはタップ**で、**ストップ中に右スワイプでリセット**、**起動中に下スワイプでラップを表示**します。

##苦労した点
ストップウォッチを作ってることを周囲に見せると必ずと言っていいほどラップに言及されるのですが、**下スワイプを認識してラップを表示する機能をつける**というのが大変でした。デリゲーションに対する理解がイマイチで```override func viewDidLoad() {}``` に```self.timerLabel[0].delegate = self```とするところに気がつかず、```TimerView.delegate```に```nil```が入っててデリゲーション部分の実装を呼び出せないバグに苦心しました。

##既知のバグ
*　一度リセットした後のラップにマイナスの値が入る

##参考文献
*　[UIButtonを動的に作るサンプルコード](http://qiita.com/osamu1203/items/9c5680d78d77146a9cfc)
*　[[Objective-C] UIViewをいい感じに上下左右センタリングする](http://qiita.com/edo_m18/items/5dd48b7ee1e6899b8ed5)
*　[プロトコルとデリゲートのとても簡単なサンプルについて](http://qiita.com/mochizukikotaro/items/a5bc60d92aa2d6fe52ca)
*　[[Swift] 自作UILabelにDelegateでタッチイベントをつける](http://qiita.com/tkuma/items/e50831b7c3aafbdd4bab)

##github
[worthmine/Swift-StopWatch-Simple](https://github.com/worthmine/Swift-StopWatch-Simple)