55647


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

UserDefaultを使ってみるも良く分からず。。。

[code]

    import UIKit
    
    class NextViewController: UIViewController {
    
        var timer : Timer?
        var date1 : Date?
        var date2 : Date?
    
        var count = 15.0
    
        @IBOutlet weak var countDownLabel: UILabel!
    
        override func viewDidLoad() {
            super.viewDidLoad()
        }
    
        @IBAction func startButton(_ sender: UIButton) {
            date1 = Date()
    
            if timer?.isValid != true {
                timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(fifteenCount(_:)), userInfo: nil, repeats: true)
            }
        }
    
        @objc func fifteenCount (_ timer : Timer) {
    
            date2 = Date()
            let dateDifference = date2!.timeIntervalSince(date1!)
    
            count = 15 - dateDifference
    
            countDownLabel.text = String(Int(count))
    
            if count <= 0 {
                 timer.invalidate()
            }
    
        }
    }
    
[/code]

* * *


CC BY-SA 4.0
2019-06-09T05:15:27.940
55641
2019-06-10T03:15:26.777
2019-06-10T03:15:26.777
34541
34541
question
swift xcode
Swift 画面遷移しても継続してカウントダウンして欲しい。


最初にちょっとお断りしておくと、画面遷移関連で動作がうまくいかない、と言った事象についてご質問を書かれる場合、

  * 遷移元のview controllerのコード (最低でも画面遷移に関わるコード全部)
  * 遷移先のview controllerのコード (最低でも画面遷移に関わるコード全部)
  * 両画面間の関係(navigation controllerに埋め込まれているかどうかとか)
  * 画面遷移にセグエを使用している場合には、どのように設定したかの詳細な情報

辺りをご質問中に含めるようにして下さい。以下の回答本体は、よほど変なコード・設定になっていない限り解決策になるはずですが、逆に言えば変なことをやっていれば、役に立たない無駄なものになってしまいます。

* * *

**Swift 画面遷移しても継続してカウントダウンして欲しい。** と言う事なのでしたら、`Timer`関連の処理をどこか特定のview
controllerに置かないようにします。

幸いあなたの現在のコードでは、`Timer`関連のコードと特定のview
controllerに依拠するコードが比較的くっきりと分かれていますので、独立したクラスを作りやすくなっています。

[code]

    import Foundation
    
    class TimerManager: NSObject {
    
        var timer : Timer?
        var date1 : Date?
        var date2 : Date?
    
        var count = 15.0
    
        func startCount() {
            date1 = Date()
    
            if timer?.isValid != true {
                timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(fifteenCount(_:)), userInfo: nil, repeats: true)
            }
        }
    
        @objc func fifteenCount(_ timer : Timer) {
            date2 = Date()
            let dateDifference = date2!.timeIntervalSince(date1!)
    
            count = 15 - dateDifference
    
            //### `count`の更新をview controllerに伝える方法は、後で考える
            //countDownLabel.text = String(Int(count))
    
            if count <= 0 {
                timer.invalidate()
            }
        }
    }
    
[/code]

このまま使っても動くようにはできるでしょうが、色々気になる点があるので、書き換えておきます。また、`TimerManager`のインスタンスをview
controller毎に作ってしまうと、結局同じ問題が起きるので、シングルトンにしておきましょう。

そこら辺を書き換えると、こんな感じ。

[code]

    import Foundation
    
    class TimerManager { //### `NSObject`を継承する必要はない
        static let shared = TimerManager() //### `TimerManager`のシングルトン
    
        var timer : Timer?
        var startDate : Date? //### たとえお試し用のコードでも`〜1`, `〜2`なんて意味の無いネーミングは避ける
        //### `date2`をインスタンス変数に持つ必要性は見つからない
    
        var count = 15.0
    
        func startCount() {
            startDate = Date() //### ↓`startDate`を設定するのも「timerが非稼働中」だけの方が良いのでは?
    
            if timer == nil  { //### timerが停止したらnilにすることにしておけば、Optionalの比較は不要
                //### startDate = Date()
    
                //### Swift用のコードには、セレクタは出来るだけ避ける
                timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) {timer in
                    self.fifteenCount(timer)
                }
            }
        }
    
        func fifteenCount(_ timer : Timer) { //### `@objc`も不要になる
            //### ちょとしたタイミングの問題で`startDate`がnilの時に呼ばれる可能性が捨てきれないので、
            //### guardで安全に行く
            guard let startDate = startDate else {return}
    
            let now = Date() //### できるだけ適切な名前を使う
            let dateDifference = now.timeIntervalSince(startDate)
    
            count = 15 - dateDifference
    
            //### `count`の更新をview controllerに伝える方法は、後で考える
            //countDownLabel.text = String(Int(count))
    
            if count <= 0 {
                timer.invalidate()
                self.timer = nil //### `invvalidate()`されたtimerは再利用できないので、nilを設定
                count = 15.0 //### `count`をリセットしている場所がない。どこかで必要では?
            }
        }
    }
    
[/code]

このクラス定義はあなたの`NextViewController`と同じファイルに置いても良いのですが、ここでは独立した`TimerManager.swift`辺りのファイルに置くことを想定しています。

`NextViewController`の方はこんな感じになります。

[code]

    class NextViewController: UIViewController {
    
        @IBOutlet weak var countDownLabel: UILabel!
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            //### view controllerがloadされた時の初期表示
            countDownLabel.text = String(Int(TimerManager.shared.count))
        }
    
        @IBAction func startButton(_ sender: UIButton) {
            TimerManager.shared.startCount()
        }
    }
    
[/code]

とここまで修正しても、`countDownLabel`の表示内容は変化しません。「`count`の更新をview
controllerに伝える方法」を先送りにしたので当然なのですが。

* * *

ここも色々なやり方が考えられるのですが、せっかくSwiftではクロージャーが使いやすくなっているので、`count`の値が変化したら適当なクロージャーを呼んでやる事にしましょう。

上記の`TimerManager`の中の`var count = 15.0`の行を、以下のように書き換えてやります。

[code]

        var count = 15.0 {
            didSet {
                onCountDidSet?(count)
            }
        }
    
        var onCountDidSet: ((_ count: Double)->Void)? = nil
    
[/code]

`NextViewController`の方では「表示される時」「表示されなくなる時」に`onCountDidSet`の値を更新してやるように次のメソッドのオーバライドを追加してやります。

[code]

        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
    
            TimerManager.shared.onCountDidSet = {count in
                self.countDownLabel.text = String(Int(count))
            }
        }
    
        override func viewWillDisappear(_ animated: Bool) {
            super.viewWillDisappear(animated)
    
            TimerManager.shared.onCountDidSet = nil
        }
    
[/code]

こちらで作成した確認用のプロジェクトでは期待通りの動作をするのですが、最初に書いたようにあなたが提示されていない部分の状態によっては、あなたのプロジェクトではうまく動かないかもしれません。その際には、ご質問内容を編集して足りない情報を付け足すようにして下さい。

* * *

ちなみに「時々表示が一挙に2減る時がある」と言う挙動はそのままにしてあります。また上記の修正を入れると、カウント終了時に、表示が`0`ではなく`15`になってしまうのが、あなたのコードの挙動とは変わってしまうのですが、その辺りはご自身で調整して見て下さい…。まずはお試しを。


CC BY-SA 4.0
2019-06-09T09:05:02.017
55647
2019-06-09T09:05:02.017
13972
55641
answer