35645


画面遷移先(ここではEditPopUpViewController)でユーザーが時間を設定し、遷移元のタイマーにそれを反映させ、タイマーを機能させようとしています。  
具体的な流れは、  
①`editTask`ボタン(`identifier: "goEditPopUp"`)を押して、画面遷移  
②遷移先にある`wheelTimerOutlet`(`UIDatePicker`の`countDownTimer`)を使って時間を設定し、`doneOutlet`ボタンを押して遷移元に戻る  
③その設定された時間を遷移元に反映させて、タイマースタート。`taskContent`ラベルに残り時間を毎秒表示  
(④`stop`ボタンでタイマー停止)  
です。  
しかし、これをランすると、`taskContent`ラベルにあるタイマーの残り時間が２秒ずつ減ってしまいます。そして、`stop`ボタンを押すと１秒ごとに減るようになります。原因は最初の段階でタイマーが二度呼ばれてしまっていることにあるようです。  
どうしたら解決できるでしょうか。  
以下コードです。簡単のため本件に関係ないと判断した機能は省略させていだだきました。分かりにくいところがあればコメントにてお知らせ下さい。なるべくすぐに対応します。

[code]

    import UIKit
    class FirstViewController: UIViewController{
        override func viewDidLoad() {
            super.viewDidLoad()
            stop.addTarget(self, action: #selector(stopTimer), for: .touchUpInside)
            //・・・
        }
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
        //・・・
        @IBOutlet weak var taskContent: UILabel!
        @IBOutlet weak var editTask: UIButton!
        @IBOutlet weak var stop: UIButton!
    
        var count = Date(timeIntervalSinceReferenceDate: 0.0)
        var timer = Timer()
        //画面遷移から戻ってくる時
        @IBAction func unwindToFirstView(segue: UIStoryboardSegue)
        {
            if segue.identifier == "backToFVCdone"
            {
                //遷移先の取得に失敗したら何もしない
                guard let EditPopUpVC = segue.source as? EditPopUpViewController else
                {
                    return
                }
                //EditPopUpVCのプロパティcountAtEditをcountに受け渡す
                count = Date(timeIntervalSinceReferenceDate: EditPopUpVC.wheelTimerOutlet.countDownDuration)
                let dateFormatter = DateFormatter()
                dateFormatter.dateFormat = "HH時間mm分ss秒"
                dateFormatter.timeZone = TimeZone(abbreviation: "GMT")
                taskContent.text = dateFormatter.string(from: count)
                //タイマー開始
                timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(FirstViewController.countDown), userInfo: nil, repeats: true)
            }
        }
        func countDown()//この関数が二回呼ばれている
        {
            count -= 1
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "HH時間mm分ss秒"
            dateFormatter.timeZone = TimeZone(abbreviation: "GMT")
            taskContent.text = dateFormatter.string(from: count)
    
            //カウントダウンが終了したらタイマーを止める
            if count.timeIntervalSinceReferenceDate <= 0.0
            {
                timer.invalidate()
                taskContent.text = "終了！"
            }
        }
        func stopTimer()
        {
            timer.invalidate()
        }
        //画面遷移のとき
        override func prepare(for segue: UIStoryboardSegue, sender: Any?)
        {
            if segue.identifier == "backToFVCdone"
            {
                //略
                //実際はもう数種類のUILabel等があり、ここではそのラベルを表示させる・させないを切り替えています
            }
            else if segue.identifier == "goEditPopUp"
            {
                //・・・
                let EditPopUpVC = segue.destination as! EditPopUpViewController
                EditPopUpVC.taskContentAtEditVC = taskContent
                //・・・
            }
        }
    }
    
    //画面遷移先のViewController
    class EditPopUpViewController: UIViewController{
        override func viewDidLoad() {
            super.viewDidLoad()
        }
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
        var taskContentAtEditVC: UILabel!
        @IBOutlet weak var wheelTimerOutlet: UIDatePicker!
        @IBOutlet weak var doneOutlet: UIButton!
        @IBAction func doneActive(_ sender: Any)
        {
            performSegue(withIdentifier: "backToFVCdone", sender: self)
            dismiss(animated: true, completion: nil)
        }
    }
    
[/code]


CC BY-SA 3.0
2017-06-15T12:15:50.500
35611
2017-06-16T11:39:59.123
2017-06-15T12:20:59.140
76
20671
question
swift swift3
タイマーが１秒ごとに動かない（一度に２回呼ばれてしまう）


ご提示のコードからは、タイマーが2度呼ばれる原因を見つけることができませんでした。提示されていない箇所に、おそらく原因があるのではないかと思われます。  
本題と関係ありませんが、気になった点がいくつかあるので、指摘させていただきます。

_class EditPopUpViewController_

[code]

    @IBAction func doneActive(_ sender: Any)
    {
        performSegue(withIdentifier: "backToFVCdone", sender: self)
        dismiss(animated: true, completion: nil)
    }
    
[/code]

`performSegue`も`dismiss`も、モーダル（あるいはポップアップ）を終了させる命令であって、処理の重複です。そして、おそらく`dismiss`の方は機能していません。

_class FirstViewController_

[code]

    guard let EditPopUpVC = segue.source as? EditPopUpViewController else
        {
            return
        }
    
[/code]

Swiftの命名規則は、型名（整数値型、実数型、クラス名、構造体名、プロトコル名など）は大文字で始め、それ以外（変数名など）は小文字で始めるとなっています。直感的にコードを読み解くための、工夫からくる規則で、素直に従う方が、相応のメリットを享受できると思います。それによれば、上のコードの`EditPopUpVC`は、`editPopUpVC`とすべきで、明らかにクラス名とインスタンス名の混乱を避けることができます。

* * *

タイマーを2度呼んでしまう箇所を見つけるのではなく、タイマーを2度（以上）呼ばない処理を施すことを考えてみます。  
そのために、Optional型の有効活用をしてみましょう。

[code]

    var timer: Timer?
    
[/code]

`timer`が`nil`なら新たに`Timer`のインスタンスを作る。`timer`が`nil`でなければ、`Timer`のインスタンスを作らない。そういう処理をしていれば、タイマーの個数は、0か1に限定できます。

以下のサンプルコードは、ラベル一つと、スタートボタン、ストップボタンがあって、スタートボタンを叩くと、カウントダウンが始まり、0になったらカウントダウンが終わります。ストップボタンは、カウントダウンを中断します。カウントダウン中、スタートボタンを押しても、なにも反応しません。

[code]

    import UIKit
    
    class ViewController: UIViewController {
    
        var count = 10
        var timer: Timer?
    
        @IBOutlet weak var label: UILabel!
    
        @IBAction func startTimer(_ sender: Any) {
            if timer == nil {
                label.text = "\(count)"
                timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(ViewController.countDown), userInfo: nil, repeats: true)
            }
        }
    
        @IBAction func stopTimer(_ sender: Any) {
            if timer != nil {
                timer?.invalidate()
                timer = nil
                count = 10
            }
        }
    
        func countDown() {
            count -= 1
            label.text = "\(count)"
    
            if count < 1 {
                timer?.invalidate()
                timer = nil
                count = 10
            }
        }
    }
    
[/code]


CC BY-SA 3.0
2017-06-16T11:39:59.123
35645
2017-06-16T11:39:59.123
18540
35611
answer