55944


現在、カウントダウンアプリを作っております。

カウントダウン中にストップボタンを押して動作を止め、もう一度スタートボタンを押したら動作を再開したいのですが、私の書いたコードですと下の画像の様に見た目はストップしているのですが、スタートをボタンを押すとストップしたはずがしていないことになってしまいます。

どの様なコードに修正したら良いでしょうか。

* * *

[![２０ボタンを押す](https://i.stack.imgur.com/KvG1v.png)](https://i.stack.imgur.com/KvG1v.png)

[![startButtonを押す](https://i.stack.imgur.com/cXV4c.png)](https://i.stack.imgur.com/cXV4c.png)

[![画像の説明をここに入力](https://i.stack.imgur.com/igHao.png)](https://i.stack.imgur.com/igHao.png)

**19 : 45** のときにstopボタンを押します。

その **10秒後に** startボタンを押すします。

[![画像の説明をここに入力](https://i.stack.imgur.com/Y8dWh.png)](https://i.stack.imgur.com/Y8dWh.png)

私の望む処理ではありません **19 : 44** から再スタートして欲しいのですがこのコードはstopした過去をなくしています。

**コード↓↓↓**

**画像を司るコード**

[code]

    import UIKit
    
    func secondsToTimerLabel(_ seconds : Int) -> String {
        let min = seconds / 60
        let sec = seconds % 60
        return String(format: "%02d : %02d", min, sec)
    }
    
    class NextViewController: UIViewController {
    
        @IBOutlet weak var countdwonTimer: UILabel!
        @IBOutlet weak var executionTimer: UILabel!
        @IBOutlet weak var stopButton: UIButton!
        @IBOutlet weak var kyannseruButton: UIButton!
        @IBOutlet weak var twentyButton: UIButton!
        @IBOutlet weak var thirtyButton: UIButton!
        @IBOutlet weak var sixtyButton: UIButton!
        @IBOutlet weak var ninetyButton: UIButton!
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            stopButton.layer.cornerRadius = 30.0
            kyannseruButton.layer.cornerRadius = 30.0
            twentyButton.layer.cornerRadius = 22.5
            thirtyButton.layer.cornerRadius = 22.5
            sixtyButton.layer.cornerRadius = 22.5
            ninetyButton.layer.cornerRadius = 22.5
    
            self.executionTimer.text = secondsToTimerLabel(TimerManager.shared.count)
        }
    
        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
    
            TimerManager.shared.onCountDidSet = { count in
                self.executionTimer.text = secondsToTimerLabel(Int(count))
            }
        }
    
        override func viewDidDisappear(_ animated: Bool) {
            super.viewDidDisappear(animated)
    
            TimerManager.shared.onCountDidSet = nil
        }
    
        @IBAction func stopButtonAction(_ sender: UIButton) {
    
            if TimerManager.shared.isRunning {
                if TimerManager.shared.timer?.isValid == true{
                    TimerManager.shared.stop()
                    stopButton.setTitle("start", for: .normal)
                    twentyButton.isUserInteractionEnabled = true
                    thirtyButton.isUserInteractionEnabled = true
                    sixtyButton.isUserInteractionEnabled = true
                    ninetyButton.isUserInteractionEnabled = true
                } else {
                    TimerManager.shared.restartCount()
                    stopButton.setTitle("stop", for: .normal)
                    twentyButton.isUserInteractionEnabled = false
                    thirtyButton.isUserInteractionEnabled = false
                    sixtyButton.isUserInteractionEnabled = false
                    ninetyButton.isUserInteractionEnabled = false
                }
            } else {
                TimerManager.shared.startCount()
                stopButton.setTitle("stop", for: .normal)
                twentyButton.isUserInteractionEnabled = false
                thirtyButton.isUserInteractionEnabled = false
                sixtyButton.isUserInteractionEnabled = false
                ninetyButton.isUserInteractionEnabled = false
            }
        }
    
        @IBAction func cancelButtonAction(_ sender: UIButton) {
            TimerManager.shared.cancel()
    
            twentyButton.isUserInteractionEnabled = true
            thirtyButton.isUserInteractionEnabled = true
            sixtyButton.isUserInteractionEnabled = true
            ninetyButton.isUserInteractionEnabled = true
    
            TimerManager.shared.count = 0
            self.executionTimer.text = secondsToTimerLabel(0)
    
            stopButton.setTitle("start", for: .normal)
        }
    
        @IBAction func twentyButtonAction(_ sender: UIButton) {
            self.executionTimer.text = secondsToTimerLabel(1200)
            TimerManager.shared.nowNumber = 20
        }
    
        @IBAction func thertyButtonActiojn(_ sender: UIButton) {
            self.executionTimer.text = secondsToTimerLabel(1800)
            TimerManager.shared.nowNumber = 30
        }
    
        @IBAction func sixtyButtonAction(_ sender: UIButton) {
            self.executionTimer.text = secondsToTimerLabel(3600)
            TimerManager.shared.nowNumber = 60
        }
    
        @IBAction func ninetyButtonAction(_ sender: UIButton) {
            self.executionTimer.text = secondsToTimerLabel(5400)
            TimerManager.shared.nowNumber = 90
        }
    
    }
    
[/code]

**タイマーを司るコード**

[code]

    import UIKit
    
    class TimerManager {
        static let shared = TimerManager()
    
        let settingKey = "timer_value"
        let settingKeytwenty = "timer_twenty"
        let settingKeythirty = "timer_thirty"
        let settingKeysixty  = "timer_sixty"
        let settingKeyninety = "timer_ninety"
    
        var timer : Timer?
        var startDate : Date?
        var restartDate : Date?
    
        var count = 0 {
            didSet {
            onCountDidSet?(Double(count))
            }
        }
    
        var onCountDidSet : ((_ count : Double) -> Void)? = nil
    
        var nowNumber : Int = 0
    
        var restartBool : Bool = false
    
        init () {
            let settings = UserDefaults.standard
            settings.register(defaults: [settingKey : 216000])
            let settings20 = UserDefaults.standard
            settings20.register(defaults: [settingKeytwenty : 1200])
            let settings30 = UserDefaults.standard
            settings30.register(defaults: [settingKeythirty : 1800])
            let settings60 = UserDefaults.standard
            settings60.register(defaults: [settingKeysixty : 3600])
            let settings90 = UserDefaults.standard
            settings90.register(defaults: [settingKeyninety : 5400])
        }
    
        func restartCount() {
            restartDate = Date()
            switch nowNumber {
    
            case 20 :
                timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in self.twntytimer(timer)
                })
            case 30 :
                timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: {timer in self.thritytimer(timer)})
            case 60 :
                timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in
                    self.sixtytimer(timer)})
            case 90 :
                timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in
                    self.ninetytimer(timer)})
            default :
                print("Invalid value \(nowNumber)")
            }
        }
    
        func startCount() {
            startDate = Date()
    
            if timer == nil {
    
                switch nowNumber {
                case 20 :
                    timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in self.twntytimer(timer)
                    })
                case 30 :
                    timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: {timer in self.thritytimer(timer)})
                case 60 :
                    timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in
                        self.sixtytimer(timer)})
                case 90 :
                    timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in
                        self.ninetytimer(timer)})
                default :
                    print("Invalid value \(nowNumber)")
                }
    
            }
        }
    
        func stop() {
            timer?.invalidate()
        }
    
        func cancel() {
            timer?.invalidate()
            timer = nil
        }
    
        var isRunning : Bool {
            return timer != nil
        }
    
        func twntytimer(_ timer : Timer) {
    
            let now = Date()
    
            let sitting = UserDefaults.standard
            let timeValue = sitting.integer(forKey: settingKeytwenty)
    
            if restartBool != true {
    
                guard let startDate = startDate else {
                    return
                }
    
                let dateDifference = now.timeIntervalSince(startDate)
                count = timeValue - Int(dateDifference)
    
            } else {
    
                guard let restartDate = restartDate else {
                    return
                }
                let dateDifference = now.timeIntervalSince(restartDate)
                count = timeValue - Int(dateDifference)
    
            }
    
            if count <= 0 {
                self.timer?.invalidate()
                count = 0
                self.timer = nil
            }
    
        }
    
        func thritytimer (_ timer : Timer) {  //途中ですtwntytimer(_:)が成功したら直します
    
            guard let startDate = startDate else {
                return
            }
            let now = Date()
            let dateDifference = now.timeIntervalSince(startDate)
    
            let sitting = UserDefaults.standard
            let timeValue = sitting.integer(forKey: settingKeythirty)
    
            count = timeValue - Int(dateDifference)
    
            if count <= 0 {
                self.timer?.invalidate()
                count = 0
                self.timer = nil
            }
    
        }
    
        func sixtytimer(_ timer : Timer) {  //途中ですtwntytimer(_:)が成功したら直します
    
            guard let startDate = startDate else {
                return
            }
            let now = Date()
            let dateDifference = now.timeIntervalSince(startDate)
    
            let sitting = UserDefaults.standard
            let timeValue = sitting.integer(forKey: settingKeysixty)
    
            count = timeValue - Int(dateDifference)
    
            if count <= 0 {
                self.timer?.invalidate()
                count = 0
                self.timer = nil
            }
    
        }
    
        func ninetytimer(_ timer : Timer) {  //途中ですtwntytimer(_:)が成功したら直します
    
            guard let startDate = startDate else {
                return
            }
            let now = Date()
            let dateDifference = now.timeIntervalSince(startDate)
    
            let sitting = UserDefaults.standard
            let timeValue = sitting.integer(forKey: settingKeyninety)
    
            count = timeValue - Int(dateDifference)
    
            if count <= 0 {
                self.timer?.invalidate()
                count = 0
                self.timer = nil
            }
    
        }
    
    }
    
[/code]

また、このコードのどこがどうダメなのか教えていただけましたら幸いです。

* * *

**補足↓↓**

上記の処理を行いたいがために  
`NextController`のstopButton部に下記の二つのコードを追加してみたのですがstop時からカウントダウンがうごかなくなってしまいました。なぜでしょうか

[code]

    @IBAction func stopButtonAction(_ sender: UIButton) {
    
        if TimerManager.shared.isRunning {
    
            if TimerManager.shared.timer?.isValid == true{
    
                TimerManager.shared.stop()
                stopButton.setTitle("start", for: .normal)
                twentyButton.isUserInteractionEnabled = true
                thirtyButton.isUserInteractionEnabled = true
                sixtyButton.isUserInteractionEnabled = true
                ninetyButton.isUserInteractionEnabled = true
    
            } else {
    
                TimerManager.shared.restartCount()
                TimerManager.shared.startDate = nil  //ここです！
                TimerManager.shared.restartDate = nil //ここです！
                stopButton.setTitle("stop", for: .normal)
                twentyButton.isUserInteractionEnabled = false
                thirtyButton.isUserInteractionEnabled = false
                sixtyButton.isUserInteractionEnabled = false
                ninetyButton.isUserInteractionEnabled = false
            }
    
        } else {
    
            TimerManager.shared.startCount()
            stopButton.setTitle("stop", for: .normal)
            twentyButton.isUserInteractionEnabled = false
            thirtyButton.isUserInteractionEnabled = false
            sixtyButton.isUserInteractionEnabled = false
            ninetyButton.isUserInteractionEnabled = false
    
        }
    
    }
    
[/code]


CC BY-SA 4.0
2019-06-20T07:24:26.550
55938
2019-06-21T02:07:58.873
2019-06-21T02:07:58.873
34541
34541
question
swift xcode
カウントダウンアプリで動作の中断と再開を正しく動作させたい


**_私の書いたコードですと見た目はストップしているのですが、スタートをボタンを押すとストップしたはずがしていないことになってしまいます。_**

> **20 : 45** のときにstopボタンを押します。

(実際は **19 : 45** ですね。)

> その **10秒後に** startボタンを押すします。

(カウントダウンが始まると **19 : 35** あたりになっている。)

> 私の望む処理ではありません **10 : 44** から再スタートして欲しい

(これも「 **19 : 44** から再スタート」ですよね。)

と言うわけで、うまくいかない例とはいえ、説明に誤りがあると、正解にたどり着くまでとんでもない遠回りになることもあるため、気をつけて下さい。

結論だけ言うと

**せっかく用意した`restartBool`を利用していない**

からだと言えます。

`restartBool`は`false`のままなので、`twntytimer(_:)`の中では常に「1200秒 -
(`startTime`からの経過時間)」を`count`に入れています。リスタートの場合でも、リスタートした時(`restartTime`)からではなく、最初にスタートした時からの経過時間を使っているのですから、記載されたような動作になっちゃいますよね?

多分試行錯誤しながら現在のコードになったのでしょうが、`restartCount()`の中に`restartBool =
true`のようなコードを入れても、所望の動作にはなりません。今のコードだと`restartBool`が`true`の場合には、

### 1200秒 - (`restartTime`からの経過時間)

を`count`に入れちゃいますから、もう一度 **`20 : 00`** からカウントし直すような動作になっちゃうはずです。

* * *

こんな動作になってしまう原因としては、せっかくカウントダウンタイマーの「残り時間」を表す変数があるのに、それをうまく使えていない点にあります。

ついでに`twntytimer(_:)`から`ninetytimer(_:)`まで、中身がほとんど同じなのに少し動作を変えるだけで全部修正するなんて面倒だと思いませんか?

また、`UserDefaults`を`TimerManager`の中で参照しているのもいただけません。せっかく`NextViewController`からカウントダウンする「分」数を`nowNumber`(もうちょっといい名前をつけましょう)にセットしてもらうんだから、もう一度`UserDefaults`を参照する意味はあまりないですね。(`UserDefaults`を使うなら、`NextViewController`の中で使いましょう。)

と言うわけで、`TimerManager`に大ナタを振るってみると、こんな感じになります。

[code]

    import UIKit
    
    class TimerManager {
        static let shared = TimerManager() //<-有益なアドバイスはどんどん取り入れましょう
    
        var timer : Timer?
        var startDate : Date?
    
        var count = 0 {
            didSet {
                onCountDidSet?(Double(count))
            }
        }
        var onCountDidSet : ((_ count : Double) -> Void)? = nil
    
        var nowNumber : Int = 0
    
        var timeRemaining: Int = 0 //<- start/restartした時の残り時間
    
        init () {
            //`UserDefaults`は要らないんで`init()`も空っぽ
        }
    
        func restartCount() {
            startDate = Date()
    
            if let pausingTimer = timer, !pausingTimer.isValid { //`timer`がnilじゃないが、validでもない時だけrestartできる
                //現在の`count`を残り時間として使う
                timeRemaining = count
    
                timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
                    self.timerFired(timer)
                }
            } else {
                print("Timer cannot restart") //黙って無視するとなぜrestart出来ないかがわかりにくいので、こんなのを出す
            }
        }
    
        func startCount() {
            startDate = Date()
    
            if timer == nil { //`timer`がnilの時だけstartできる
                //`nowNumber` * 60を残り時間として使う
                timeRemaining = nowNumber * 60
    
                timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
                    self.timerFired(timer)
                }
            } else {
                print("Timer cannot start") //黙って無視するとなぜstart出来ないかがわかりにくいので、こんなのを出す
            }
        }
    
        func stop() {
            timer?.invalidate()
            //`timer`がnilじゃないけど、validでもない時は「一時停止中」なんで気をつける
        }
    
        func cancel() {
            timer?.invalidate()
            timer = nil
        }
    
    
        var isRunning : Bool {
            return timer != nil
        }
    
    
        func timerFired(_ timer : Timer) {
    
            let now = Date()
    
            let timeValue = timeRemaining //<- 1200秒とかの規定の数ではなく、start/restart時にセットした残り時間を基準にする
    
            guard let startDate = startDate else { //<- このguardのせいで`self.startDate`が`nil`の時はタイマー動作は行われない
                return
            }
            let dateDifference = now.timeIntervalSince(startDate)
            count = timeValue - Int(dateDifference)
            if count <= 0 {
                self.timer?.invalidate()
                count = 0
                self.timer = nil
            }
        }
    }
    
[/code]

メソッド名など、ちょっとわかりにくいのもそのままにして、今のあなたの`TimerManager`と差し替えられるようにしたつもりです。ちょっと多めにコメントを入れましたので、動作を確認しながらお試しください。

(ちなみに **補足** に対する回答もコメント中にありますので、見てみて下さい。)


CC BY-SA 4.0
2019-06-20T12:21:14.580
55944
2019-06-20T12:21:14.580
13972
55938
answer