LoginSignup
17
15

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-01-30

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

いきさつ

基本的なストップウォッチの作り方はニコニコ動画で学習しました。で、大体のストップウォッチにはスタートボタンとストップボタンとリセットボタンとが別々にあり、なんかもっと直感的に操作できてもいいんじゃないかな?(例えば表示をタップすればスタート/ストップ)と思いましたのでこの改良に昼夜をそそいで参りました。

画面全体のどこか(ViewController.view)をタップすればスタート/ストップ、というのはその日のうちにすぐできました。
で、それでは不満でしたのでストップウォッチ機能を持つUILbelを継承して作り、このあたりこのあたりを参考に、触れるUILabelの継承クラスに改良し、このあたりこのあたりを参考にデリゲートを実装してラップ機能を持たせることに成功しました。

2015-02-03 15.56.20.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.delegatenilが入っててデリゲーション部分の実装を呼び出せないバグに苦心しました。

既知のバグ

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

参考文献

* UIButtonを動的に作るサンプルコード
* [Objective-C] UIViewをいい感じに上下左右センタリングする
* プロトコルとデリゲートのとても簡単なサンプルについて
* [Swift] 自作UILabelにDelegateでタッチイベントをつける

github

worthmine/Swift-StopWatch-Simple

17
15
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
15