37699
タイマーで0.01秒ごとに数字を１増やし、その数字をキーとして辞書から取り出した値をUILabelに表示するというアプリを作りました。が、ラベルにアニメーションをつけたところ、それも0.01秒ごとに実行されてしまい、ラベルが点滅したりしてしまいます。

```

    override func viewDidLoad() {
        super.viewDidLoad()
        timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(labelUpdate), userInfo: nil, repeats: true)
    }
    
    func labelUpdate() {
        testInt += 1
        if let label = testDict[testInt] {
            let anime = CATransition()
            anime.type = kCATransitionMoveIn
            anime.subtype = kCATransitionFromBottom
            anime.duration = 0.2
            testLabel.layer.add(anime, forKey: nil)
            testLabel.text = label
        }
    }
    
```

辞書のキーは毎回異なるのでこのように、キーがnilでないときにラベルを更新という処理にしたのですが、nilのときにもanimeが実行されているようです・・・  
変更するときのみ、アニメーションしてくれればそれでいいのですが・・・

  
  
**追記**  
辞書は例えば`var testDict = [1: "りんご", 200:"バナナ", 470:"卵"]`のようなものです。  
testIntは任意のタイミングでUIButtonでリセットします。

理想は上記の例で言えば"りんご"から"バナナ"に変更されるときに１度だけアニメーションするというものです。

今のままで実行すると、"りんご"や"バナナ"と表示されたUILabelが点滅したあとに突然指定したアニメーションをしたり、"りんご"という文字が点滅した文字の下にもう一つ現れてそこに点滅している文字に重なったりと、正しい動きではないと思われる動きになってしまいます。

  
  
**さらに追記**  
再度、確認させてもらったところ、  
点滅したり文字が重なったりということは起こらず、ただアニメーションに関するコードが全くないかのように普通に文字が入れ替わるという動作になってしまいました。

  
  
**コード追加**

```

    var testDict = ["00:01": "りんご", "00:05":"バナナ", "00:07":"卵"]
    func labelUpdate() {
        let min = Int(player.currentPlaybackTime) / 60
        let sec = Int(player.currentPlaybackTime.truncatingRemainder(dividingBy: 60))
        timeKey = String(format: "%02d:%02d", min, sec)
        //この部分が問題でした。大変申し訳ございません
        if let label = testDict[timeKey] {
            let anime = CATransition()
            anime.type = kCATransitionMoveIn
            anime.subtype = kCATransitionFromBottom
            anime.duration = 0.2
            testLabel.layer.add(anime, forKey: nil)
            testLabel.text = label
        }
    }
    
```

timerを0.01秒ごとにしたのはmm:ssが実際の再生中の曲の再生時間と同期するためです。  
currentPlaybackTimeが１秒増えるタイミングとtimerで更新しているmm:ssの値がずれてしまうとコンマ数秒単位でずれてしまうので・・・

しかしこのせいで、testDict[timeKey]がnilでないときの処理が0.01x100回実行されてしまうようです。

点滅するのは100回MoveInが実行されるため。  
重なって表示されるのは次の秒数になった瞬間、testLabel.text = labelが保持されたまま0.2秒かけてアニメーションも実行されたためでした。

本当に申し訳ない話ですが、ここまで気がつきませんでした。

見た目では問題なかった（気がつかなかった）のですが、こうなるとtestLabel.text =
labelも１００回繰り返されていることになっていますよね・・・。  
その場合、どうしたらいいのでしょうか。  
今は辞書キーがnilじゃなかったら更新を1秒に１００回繰り返していますが、  
１度変わってしまえば残り９９回は必要ありませんし、そうすればアニメーションも正常に機能すると思うのです・・・。

つまり、
**辞書キーがnilじゃないとき、１度のみUILabelの表示を更新してそのときアニメーションを実行、また別のキーと値が取り出せたらそれも一回のみUILabelの表示の更新とアニメーション**
という処理をしたいということでした。
CC BY-SA 3.0
2017-09-03T07:01:11.687
37692
2017-09-03T13:43:47.810
2017-09-03T12:29:48.100
25183
25183
question
ios swift3 ios10
CATransitionとタイマー
すでにご質問の中で根本的な原因が記載されていますが、秒部分までをキーとして0.01秒ごとに一致するキーがあるかどうかをチェックしているので、時間が一致している間の1秒間、およそ100回アニメーションを実施している、という状態になっています。

アニメーションの開始タイミングを`player`の現在再生時間(`currentPlaybackTime`)に同期させたいという要求があるようですので、適当な時間でポーリングを行うのは仕方ないところでしょうから、単純に「1回アニメーションを始めたら、時間が変わるまで次のアニメーションは始めない」と言うチェックを入れるのが、簡単で確実だろうと思います。

(ちなみにポーリングの間隔は0.1秒程度でもほとんどの普通の人には違和感がないと思いますが。)

```

    //最後にアニメーションしたタイミング
    var lastTimeKey = "--:--"
    
    func labelUpdate() {
        let min = Int(player.currentPlaybackTime) / 60
        let sec = Int(player.currentPlaybackTime.truncatingRemainder(dividingBy: 60))
        let timeKey = String(format: "%02d:%02d", min, sec)
        //タイミングが変わった時だけアニメーションすべきかどうかチェックを入れる
        if timeKey != lastTimeKey, let label = testDict[timeKey] {
            let anime = CATransition()
            anime.type = kCATransitionMoveIn
            anime.subtype = kCATransitionFromBottom
            anime.duration = 0.2
            testLabel.layer.add(anime, forKey: nil)
            testLabel.text = label
    
            //最後にアニメーションしたタイミングを覚えておく
            lastTimeKey = timeKey
        }
    }
    
```

もちろん`lastTimeKey`の値は`player.currentPlaybackTime`がリセットされるような操作をした場合には、再度初期値に戻しておく必要があります。

他にももっと良い方法があるかもしれません。ご自身でより良い方法を見つけられた場合には、遠慮なさらず是非とも回答の形で投稿していただければと思います。
CC BY-SA 3.0
2017-09-03T13:43:47.810
37699
2017-09-03T13:43:47.810
13972
37692
answer