35459


画面遷移先でユーザーがタイマーを設定し、そのタイマーを画面遷移する前の元の`ViewController`で作動させるにはどうしたらいいでしょうか？`func
prepare`で値を受け渡してタイマーを作動させようとしてみたのですが、エラーはないもののうまくいかず、`protocol`を使うのかなと思っているのですが、これも実装の順番や方法がイマイチわかりません。  
以下、私が試してみたコードを抜粋して載せます。解決策を教えてくだされば幸いです。

[code]

    //class FirstViewController
    //whatToDoLbl: UILabel!,taskUnSetted: UILabel!,taskContent: UILabel!
    //var timer = Timer()
    //var count: Int = 0
        @IBAction func unwindToFirstView(segue: UIStoryboardSegue){
        }
        override func prepare(for segue: UIStoryboardSegue, sender: Any?)
        {
            if segue.identifier == "backToFVCSet"
            {
                let EditPopUpVC = segue.destination as! EditPopUpViewController
                if EditPopUpVC.doneOutlet.isHidden == false
                {
                    whatToDoLbl = EditPopUpVC.whatToDoLblAtEditVC
                    taskUnSetted = EditPopUpVC.taskUnSettedAtEditVC
    
                    whatToDoLbl.isHidden = false
                    taskUnSetted.isHidden = true
                    taskContent.isHidden = false
    
                    count = EditPopUpVC.countAtEditVC
                    timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(FirstViewController.counter), userInfo: nil, repeats: true)     
                }
            }
        }
    
        func counter()
        {
            let leftHours = Int(count/3600)
            let leftMinutes = Int((count - leftHours * 3600)/60)
            let leftSeconds = count % 60
    
            let hh = NSString(format: "%02d", leftHours)
            let mm = NSString(format: "%02d", leftMinutes)
            let ss = NSString(format: "%02d", leftSeconds)
            if count > 0
            {
                taskContent.text = "\(hh)時間 \(mm)分 \(ss)秒"
            }
            else
            {
                taskContent.text = "終了!"
                timer.invalidate()
            }
        }
    
[/code]

以下サブクラスです。（画面遷移先）

[code]

    class EditPopUpViewController: ・・・{
        override func viewDidLoad() {
            //略
        }
        override func didReceiveMemoryWarning() {
            //略
        }
        @IBAction func closePopUp(_ sender: Any)//完了ボタン
        {
            //var countAtEditVC = 0
            //@IBOutlet weak var wheelTimerOutlet: UIDatePicker!
            countAtEditVC = Int(wheelTimerOutlet.countDownDuration)
            dismiss(animated: true, completion: nil)
        }
    
[/code]

コードに関してご不明な点・不明瞭な点があればお伝えいただけると嬉しいです。よろしくお願いします。


CC BY-SA 3.0
2017-06-09T15:05:14.630
35446
2017-06-10T09:33:16.780
2017-06-10T08:32:45.277
18540
20671
question
swift xcode swift3 xcode8
Delegateをtimer処理で使うには？


質問の内容に即していうなら、画面遷移時のデータの受け渡しを、Delegateを用いて行う処理に関することになります。Delegateの実装にプロトコルを用いますが、プロトコルが主題にはならないので、勝手ながら、質問のタイトルを編集させていただきました。  
画面遷移時にDelegateによって、データを受け渡すのは、定石の処理ですが、Storyboard登場以降は、レガシーな定石となってしまいました。Unwind
Segueを用いて、データを受け渡すのが、今流のやり方なので、まずそちらの解説をし、次にレガシーなDelegateによるデータの受け渡しを説明します。

* * *

まず、Unwind
SegueをStoryboardで設定する手順を説明します。モーダル表示の`ModalViewController`から`ViewController`に戻るものとします。  
`ViewController`にアクションメソッド`unwindAction(_:)`を定義します。引数の型を`UIStoryboardSegue`にします。  
Storyboardで、`ModalViewController`から、Exitにコネクトします。  
[![画像の説明をここに入力](https://i.stack.imgur.com/ffnmv.png)](https://i.stack.imgur.com/ffnmv.png)  
Document Outlineに、Unwind Segueができているので、それを選択し、Attributes Inspectorを編集します。  
[![画像の説明をここに入力](https://i.stack.imgur.com/KVRQm.png)](https://i.stack.imgur.com/KVRQm.png)  
[![画像の説明をここに入力](https://i.stack.imgur.com/9lAsY.png)](https://i.stack.imgur.com/9lAsY.png)  
Unwind SegueのIdentifierを`Unwind`とします。  
`ModalViewController`を実装します。

[code]

    import UIKit
    
    class ModalViewController: UIViewController {
    
        // カウントダウンの時間
        var timerInterval: TimeInterval = 0.0
    
        // カウントダウンタイム設定のUIDatePicker
        @IBOutlet weak var countDownPicker: UIDatePicker!
    
        // モーダル終了をするボタンのアクションメソッド
        @IBAction func startTimer(_ sender: Any) {
            timerInterval = countDownPicker.countDownDuration
            // Unwind Segueを実行する。
            performSegue(withIdentifier: "Unwind", sender: self)
        }
    
    }
    
[/code]

StoryboardSegueをコードで実行するために、メソッド`performSegue(withIdentifier identifier:
String, sender: Any?)`が用意されていることを、これを機会に覚えてください。  
次に、`ViewController`の実装を行います。

[code]

    import UIKit
    
    class ViewController: UIViewController {
    
        // カウントダウンの時間。TimeInterval型ではなくて、Date型にしている点に注意。
        var countDownDate = Date(timeIntervalSinceReferenceDate: 0.0)
    
        // カウントダウン時間を表示するラベル。
        @IBOutlet weak var countDownLabel: UILabel!
    
        // Unwindした時に呼ばれるメソッド
        @IBAction func unwindAction(_ segue: UIStoryboardSegue) {
            // 遷移元の取得に失敗したら、何もしない。
            guard let controller = segue.source as? ModalViewController else {
                return
            }
    
            // ModanViewControllerのプロパティtimerIntervalを、countDownDateに受け渡す。
            countDownDate = Date(timeIntervalSinceReferenceDate: controller.timerInterval)
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "HH時間mm分ss秒"
            dateFormatter.timeZone = TimeZone(abbreviation: "GMT")
            countDownLabel.text = dateFormatter.string(from: countDownDate)
            // タイマー開始。
            Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(ViewController.countDown(_:)), userInfo: nil, repeats: true)
        }
    
        // Timerが実行するメソッド。引数にTimer型を取ると、Timerをプロパティにする必要がなくなる。
        func countDown(_ timer: Timer) {
            countDownDate -= 1.0 // Date型で使用可能な、特別な演算子
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "HH時間mm分ss秒"
            dateFormatter.timeZone = TimeZone(abbreviation: "GMT")
            countDownLabel.text = dateFormatter.string(from: countDownDate)
    
            // カウントダウンが終わったら、タイマー終了する。
            if countDownDate.timeIntervalSinceReferenceDate <= 0.0 {
                timer.invalidate()
                countDownLabel.text = "終了"
            }
        }
    
    }
    
[/code]

上のコードでは、`DateFormatter`を用いて、時間の定型表示を行なっています。こんなやり方もあるのだなくらいに受け取ってください。こうしなければいけないといっているわけではありません。

* * *

Delegateを使った、画面遷移時のデータの受け渡しを説明します。Unwind Segueを使った処理の方が、圧倒的に簡潔で、わかりやすいことが明白です。  
まず、`ModalViewController`の実装です。

[code]

    import UIKit
    
    class ModalViewController: UIViewController {
    
        // Delegateを記憶させるプロパティ。型がプロトコルで、かつオプショナル型、そしてweak属性がついている点に注目。
        weak var delegate: ModalDelegate?
    
        @IBOutlet weak var countDownPicker: UIDatePicker!
    
        @IBAction func startTimer(_ sender: Any) {
            // delegateがnilだったら、何もしない。
            guard let delegate = delegate else {
                return
            }
    
            // ViewControllerのメソッドsetTimer(interval:)を呼ぶ。
            delegate.setTimer(interval: countDownPicker.countDownDuration)
            // モーダルを閉じる。
            dismiss(animated: true, completion: nil)
        }
    
    }
    
    // プロトコルModalDelegateの定義
    protocol ModalDelegate: class {
        func setTimer(interval: TimeInterval)
    }
    
    // ViewControllerが、メソッドsetTimer(interval:)を定義していない場合の処理。
    extension ModalDelegate {
        func setTimer(interval: TimeInterval) {
            print("Delegate is not defined.")
        }
    }
    
[/code]

プロトコルの定義のために、Swiftファイルをひとつ作る必要はなく、ひとつのSwiftファイルにまとめて書いておくことができます。  
次に、`ViewController`のコードを編集します。

[code]

    import UIKit
    
    class ViewController: UIViewController, ModalDelegate {
    
        var timerDate = Date(timeIntervalSinceReferenceDate: 0.0)
    
        @IBOutlet weak var countDownLabel: UILabel!
    
        // ViewControllerからModalViewControllerに遷移するタイミングで、ModalViewControllerのプロパティdelegateに、selfを代入。
        override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
            if let controller = segue.destination as? ModalViewController {
                controller.delegate = self
            }
        }
    
        // Delegateメソッド
        func setTimer(interval: TimeInterval) {
            timerDate = Date(timeIntervalSinceReferenceDate: interval)
            let format = DateFormatter()
            format.timeZone = TimeZone(abbreviation: "GMT")
            format.dateFormat = "HH時間mm分ss秒"
            countDownLabel.text = format.string(from: timerDate)
            Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(ViewController.countDown(_:)), userInfo: nil, repeats: true)
        }
    
        func countDown(_ timer: Timer) {
            timerDate -= 1.0
            let format = DateFormatter()
            format.timeZone = TimeZone(abbreviation: "GMT")
            format.dateFormat = "HH時間mm分ss秒"
            countDownLabel.text = format.string(from: timerDate)
    
            if timerDate.timeIntervalSinceReferenceDate <= 0.0 {
                timer.invalidate()
                countDownLabel.text = "終了"
            }
        }
    
    }
    
[/code]

以上が、Delegateの典型的な定型処理です。このパターンを覚えておけば、ほかのDelegate処理に容易に応用できます。ただし、今流で行くなら、クロージャを使った処理の方が多いとは思います。


CC BY-SA 3.0
2017-06-10T09:33:16.780
35459
2017-06-10T09:33:16.780
18540
35446
answer