質問: count + 0.01 ![イメージ説明](https://teratail-v2.storage.googleapis.com/uploads/contributed_images/1852836c50970bd7862e25944f4716b8.png) } } ### 試したこと Githubやcocoa podsでソースコードを探し見比べたりしたのですが、原因を見つけられませんでした。
回答: オリジナルのコードに基づく修正結果は後半に追記しました。 BPM = Beats per Minute なので、一分間の拍数(タップ回数)を計測すれば良いことになります。 作られたプログラムだと、割り込みを 0.01秒ごとにかけてタップ回数を計測するような流れになっていますが(それ以上深く読んでいませんので、根本的な原因まではみていませんが)、上記の理屈を素直に解釈すれば、**1分後に起きる割り込みをかけて、その間に実行されたタップ回数を計測すれば良い**ということになります。 もっとも、1分間ずっとタップするのもしんどいので、たとえば10秒間タップし、タップされた回数を6倍することで、BPM を推測することも可能かと思います。 整理すれば * 割り込み周期が10秒のタイマを作る * 割り込み周期内に計測用のボタンが押されたら、その回数を記録する * 10秒後、割り込みが発生した段階で計測用のボタンが押された回数を6倍し、表示する という流れにすれば、とりあえず BPM を計算するようなアプリは作れます。 ただし、このアプリには次のような問題も含まれています。 * 現状では、スタートボタンを押した瞬間から計測を開始している。したがって、スタートボタンを押した後、計測用のタップボタンを押すまでの空白時間も計測に含まれるため、より正確に計測するのであればタイマ割り込みを開始するタイミングを考慮する必要がある。 * 10秒間に押されたボタンの回数から BPM を推測する方法を採用しているが、実は厳密に BPM を計算することはできない。この方法だと拍数が遅くなればなるほど誤差が含まれてしまう。より正確に計測するのであれば、拍ごとの経過時間を計測し、そこから BPM を計算する必要がある(この場合は 0.1 秒周期などの割り込みを掛けて計算する必要もあるでしょう)。 * あと何秒で計測が終了するのか分からないので、使い慣れていないと不安になる。 * リアルタイムで BPM を表示するのであれば、やはり割り込み方法を変更する必要がある。 上記の内容をクリアすることこそ、独自の BPM カウントアプリを作る醍醐味だとおもいますので、ぜひ挑戦していただければと思います。 ``` import UIKit class ViewController: UIViewController { // MARK: BPM 表示用のボタン @IBOutlet weak var bpmLabel: UILabel! // MARK: 計測開始用のボタン @IBOutlet weak var startButton: UIButton! // MARK: 計測用(タップ開始)のボタン @IBOutlet weak var tapButton: UIButton! // MARK: タイマのインスタンスを代入するための変数 // オプショナル型にする案もあるが、とりあえずはインスタンスを代入しておく。 var timer: Timer = Timer() // MARK: タップ回数 var count = 0 override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. // タップ用のボタンは隠しておく tapButton.isHidden = true bpmLabel.text = "0" } // MARK: スタートボタンを押したときの処理 @IBAction func startButtonPressed(_ sender: Any) { // スタートボタンを押したら、スタートボタンは隠し、タップボタンを表示する tapButton.isHidden = false startButton.isHidden = true // ラベルに「計測中」と表示する bpmLabel.text = "計測中" // カウント回数を初期化 count = 0 // タイマのインスタンスを作成。 // 今回は10秒間計測し、計測終了したら calcBPM() を呼び出す。呼び出しは一回のみ(リピートしない) // TODO: いまの作りだと、スタートボタンを押したらすぐに計測を開始してしまう。タップボタンを押した瞬間から計測したいのであれば、適切な場所に移動させる。 timer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(calcBPM), userInfo: nil, repeats: false) } // MARK: タップボタンを押すたびに count をカウントアップする @IBAction func tapButton(_ sender: Any) { count += 1 } // MARK: BPM を計算する処理 @objc func calcBPM() { // TODO: リピートしないタイマであれば、自分で invalidate() を呼ぶ必要はない // timer.invalidate() // 隠していたボタンを表示し、表示していたボタンを隠す startButton.isHidden = false tapButton.isHidden = true // 10秒間のタップ回数を6倍することで BPM を見積もる let estimatedBPM = count * 6 bpmLabel.text = "BPMは \(estimatedBPM) です" } } ``` 追記 == 思ったように動かない、というコードをきちんとみてみました。 タップ間の経過時間を計算した値で 60 を割ることによってリアルタイムで BPM を表示したいのだと理解しました(上で指摘した項目のうち、リアルタイムで計測することを実現しようとしている)。 思うように動かない理由は * 初回だけで良い Timer のインスタンスを毎回作っている(作っても多分動くが意味がない) * `BPM` に入れるべき計算結果を `count` に入れて上書きしている * タップごとに `count` をリセットしなければいけないのにリセットしていない などがあります。 これらを修正すれば、概ね元の構造を残したまま動くようにすることは可能です。 ``` import UIKit class ViewController: UIViewController { var count: Double = 0.00 var timer: Timer! var BPM: Double! @IBOutlet var timerLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } @IBAction func start() { // MARK: タイマは初回だけ起動 if timer == nil { timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(ViewController.update), userInfo: nil, repeats: true) } else { // 秒数を count で割った値を BMP に代入 BPM = 60 / count timerLabel.text = String(format: "%.2f", BPM) // count はゼロにする count = 0 } } @IBAction func stop() { timer.invalidate() } @objc func update() { count = count + 0.01 } } ```