【Swift】TimerクラスのscheduledTimerメソッドでアニメーションの使い方

  • URLをコピーしました!

`Timer.scheduledTimer` は今でも使えますが、2026 年時点では「繰り返し処理を手軽に回す道具」として理解しつつ、止め忘れと参照関係には気を付けるのが基本です。この記事の例のような学習用サンプルには向いています。

目次

今の補足ポイント

  • 繰り返しタイマーは不要になったら `invalidate()` する
  • UI 更新はメインスレッド側で行う
  • 細かいアニメーションは `UIView.animate` や `CADisplayLink` の方が向く場合もある
  • closure 版を使うときは retain cycle に注意する

タイマーは便利ですが、秒数どおりに厳密実行される保証まではありません。なので、表示演出として使うのか、ロジックの周期処理として使うのかを分けて考えると設計しやすいです。

この記事の内容は入門として有効で、特に「時間経過で処理を動かす」という感覚を掴むのに向いています。現行環境では、その上で停止処理やライフサイクルも一緒に見るのが実践的です。

Swift/Xcodeの学習をしていますが難しめになってきたように思います。というのもこの回からアニメーションをやります。 アニメーションは画面上のオブジェクトを時間とともに描写位置を変えていくことで実現します。 そのためのクラスがTimerでメソッドはscheduledTimerです。今日はこれらのクラスについてまとめるとともに自分が作りたいアプリ(Peek-a-booアプリ)の前進になるようなテストアプリを作っていこうと思います。

scheduledTimerを使う前のオブジェクトの配置

ボタンを2個用意して、「いないいない」「ばあ」をニョキッと表示させるだけのアプリです。 まずはXcodeで新規プロジェクトの作成です。プロジェクト名は「myTimerTest」です。 今回はスマホの画面を横にします。画面下部にあるOrientationのところで横向きスマホをクリクして横にします。 ボタンを2個用意します。画面右下のオブジェクトライブラリからButtonを2個、storyboard上に配置しました。またボタン文字を記述しました。 このボタン2個をAction接続でソースコードと接続します。 まずはAssistant editorボタンをクリックして画面右側にソースコードを表示し ボタン2個ともソースコード上にcontrolを押しながらドラッグして接続。 これでButtonをタップしたときに起こすメソッドが定義できました。 @IBAction func tapBtn1と @IBAction func tapBtn2がメソッドです。 ここにそれぞれのボタンが押されたときの処理を記述していきます。 この時点でシミュレーターを起動してみます。シミュレーターのスマホは縦になっているので横向きにします。画面上部にあるHardware→Rotate Leftとポイントすればスマホが横向きになります。 いまはボタンが2個配置されているだけです まずはボタン1が押された時に「いないいない」を表示し、ボタン2が押された時に「ばあ」を表示するだけの実装をします。
@IBAction func tapBtn1(_ sender: UIButton) {
    print("tap btn1")
    let label = UILabel()
    label.text = "いないいな~い"
    label.font = UIFont(name: "HiraginoSans-W6", size:40)
    label.sizeToFit()
    label.frame.origin = CGPoint(x: (self.view.frame.width/2 - label.frame.width/2), 
                                 y: (self.view.frame.height/2 - label.frame.height/2))
    self.view.addSubview(label)
}
2行目はprintでこのボタンが押されたときにこのメソッドを通過するか表示しているだけです。 3行目でUILabelのインスタンスを生成しています。「いないいな~い」はlabelで表示します。 4行目はlabe.textで文字列を設定し、fontプロパティでフォントファミリーやフォントサイズを指定しました。 5行目はフォントサイズを指定したあとにlabel.sizeToFitメソッドでラベルのサイズを文字の大きさに合わせて拡大しています。 6行目はlabel.frame.originはラベルを表示するx,y座標を指定します。ラベルの左上角です。 画面中央にラベルを表示させたいので、スマホの画面の横幅÷2からラベル幅÷2を引いた座標をXにします。 同様にy座標は、スマホの縦長÷2からラベル高さ÷2を引いた座標をYにします。これでラベルが中央に表示されます。 と、思ったらこんな面倒な計算しなくたって便利なプロパティがあったよ。
label.center.x = self.view.center.x
label.center.y = self.view.center.y
たったこれだけでいいんじゃ~~~! 最後にViewController自身(self)のview.adSubviewでラベルを乗せて表示、となります。これが基本形です。 次にボタン2が押されたときは「ばあ」のみを表示させたいので、「いないいない」のラベルは消えて欲しいワケですので、そのように実装していきます。 ラベルはViewControllerのましたに記述してどこからでも触れるようにしました。以下のようなソースコードになりました。
class ViewController: UIViewController {
    let label1 = UILabel()
    let label2 = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    @IBAction func tapBtn1(_ sender: UIButton) {
        print("tap btn1")
        label2.text = "" //「ばぁ」は消す
        label1.text = "いないいな~い"
        label1.font = UIFont(name: "HiraginoSans-W6", size:40)
        label1.sizeToFit()
        label1.center.x = self.view.center.x
        label1.center.x = self.view.center.y
        self.view.addSubview(label1)
    }
    @IBAction func tapBtn2(_ sender: UIButton) {
        print("tap btn2")
        label1.text = "" //「いないいない」は消す
        label2.text = "ばぁっ!"
        label2.font = UIFont(name: "HiraginoSans-W6", size:40)
        label2.sizeToFit()
        label2.center.x = self.view.center.x
        label2.center.y = self.view.center.y
        self.view.addSubview(label2)
    }
さてここからアニメーションの実装をしていきます。

scheduledTimerを使ってアニメーションを実装

btn1をタップしたら「いないいない」Labelが徐々に大きくなるようにTimerクラスのscheduledTimerメソッドを使います。 このメソッドは「アニメーションをするためのメソッド」ではありません。このクラスではタスクのスケジュールを管理し何かしらのメソッドを実行したり停止したり、設定した時間ごとに繰り返したい場合に使います。 scheduledTimerメソッドのリファレンスを見ると定義部分は以下のようになっていました。
open class func scheduledTimer(
	timeInterval ti: TimeInterval,
	target aTarget: Any,
	selector aSelector: Selector,
	userinfo: Any?,
	repeats yesOrNo: Bool) -> Timer
scheduledTimerメソッドは引数が難しいですね。 例として以下のように記述します。
Timer.scheduledTimer(
    timeInterval: 0.001, //秒
    target: self,
    selector: #selector(self.label1kakudai(_:)), //実行するメソッド
    userInfo: 1, //何かしらのパラメータ
    repeats: true //繰り返し
)

scheduledTimerメソッドの引数

引数は以下のように指定、意味があります。
第1引数timeInterval インターバルの時間です。秒を指定する。1/1000と書けば1000分の1秒になる。例えばアニメーションしようと思って繰り返しで0.001を指定すると0.1よりなめらかに動く。
第2引数target メソッドがどこにあるか場所を指定。selfだとViewControllerになっている。のでその中に動かしたいメソッドを書くことになります。
第3引数selector タイマーが起動したときに実行するメソッド名を書く。 ここではself.label1kakudaiメソッドを指定しました。引数にはTimer自身を入れます。
第4引数userInfo パラメータとして渡したい何かを記述。何型でもよくIntの1や、拡大させるLabelを渡しても良い。しかし渡した先でas!を使って型を明示する必要がある。渡すものがなければnilでもいい。
第5引数repeats Bool型で繰り返すか否かの設定を行います。繰り返す場合はtrueで、1度だけ実行する場合はfalseにします。
このTimerで呼ばれるメソッドにlabel1kakudai()を指定しました。なのでこのメソッドを実装していきます。 例えば以下のように実装しました。
@objc func label1kakudai(_ sender: Timer){
    let sokudo = sender.userInfo as! CGFloat
    label1.font = label1.font.withSize(label1.font.pointSize + sokudo) 
    label1.sizeToFit()
    label1.center.x = self.view.center.x
    label1.center.y = self.view.center.y
    if label1.font.pointSize >= 50 {  sender.invalidate()  }
}
2行目でuserInfoをCGFloat型で取り出してsokudoという定数に入れています。 これはuserInfoで拡大する文字のスピード(フォントサイズにいくつ足すか)を渡すようにした例です。 label1kakudaiメソッドの引数はTimer自身が入ってきてこのメソッド内ではsenderという変数名で使います。 label1.font = label1.font.withSize(label1.font.pointSize + sokudo) は現在のLabel1のフォントサイズにsokudoという定数(1)を足して、現在のLabelのフォントサイズに再設定しています。つまりフォントサイズを1大きくしているわけです。 最後にif文でフォントサイズが50位上になったら、Timerを停止するようにsender.invalidateを呼んでいます。 例2 もしuserInfoにLabel1を渡すならこのように記述。Label1はViewControllerクラスの変数なので渡さなくても使えるけど、渡し方のサンプルとしてメモした。
class ViewController: UIViewController {
    let label1 = UILabel()
    let label2 = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    @IBAction func tapBtn1(_ sender: UIButton) {
        print("tap btn1")
        label2.text = ""
        label1.text = "いないいな~い"
        label1.font = UIFont(name: "HiraginoSans-W6", size:10)
        label1.sizeToFit()
        label1.center.x = self.view.center.x
        label1.center.y = self.view.center.y
        self.view.addSubview(label1)
 
        Timer.scheduledTimer(
            timeInterval: 0.01,
            target: self,
            selector: #selector(self.kakudai(_:)),
            userInfo: label1, //ここでlabelをわたしてみる
            repeats: true
        )
    }
    
    //userInfoにLabelを渡した場合のメソッド
    @objc func kakudai(_ sender: Timer){
        let tmpLabel = sender.userInfo as! UILabel //userInfoはUILabelとして設定
        tmpLabel.font = tmpLabel.font.withSize(tmpLabel.font.pointSize + 1) //フォントサイズは+1固定にした
        tmpLabel.sizeToFit()
        tmpLabel.center.x = self.view.center.x
        tmpLabel.center.y = self.view.center.y
        if tmpLabel.font.pointSize >= 50 {sender.invalidate()}
    }
しかしuserInfoに2つの値をもたせるようなことはできないのだろうか。 たしか、swifって型の違う値を配列に入れて渡せたような気がするが。 「いないいない」と「ばぁ」をそれぞれ拡大するようにソースコードを編集した。全体的なソースは以下のようになった。
import UIKit

class ViewController: UIViewController {
    let label1 = UILabel()
    let label2 = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
    @IBAction func tapBtn1(_ sender: UIButton) {
        print("tap btn1")
        label2.text = ""
        label1.text = "いないいな〜い"
        label1.font = UIFont(name: "HiraginoSans-W6", size:10)
        label1.sizeToFit()
        label1.center.x = self.view.center.x
        label1.center.y = self.view.center.y
        self.view.addSubview(label1)
 
        Timer.scheduledTimer(
            timeInterval: 0.01,
            target: self,
            selector: #selector(self.kakudai(_:)),
            userInfo: label1,
            repeats: true
        )
    }

    
    @IBAction func tapBtn2(_ sender: UIButton) {
        print("tap btn2")
        label1.text = ""
        label2.text = "ばぁっ!"
        label2.font = UIFont(name: "HiraginoSans-W6", size:10)
        label2.sizeToFit()
        label2.center.x = self.view.center.x
        label2.center.y = self.view.center.y
        self.view.addSubview(label2)
        
        Timer.scheduledTimer(
            timeInterval: 0.005,
            target: self,
            selector: #selector(self.kakudai(_:)),
            userInfo: label2,
            repeats: true
        )
    }
    
    //userInfoにLabelを渡した場合のメソッド
    @objc func kakudai(_ sender: Timer){
        let tmpLabel = sender.userInfo as! UILabel
        tmpLabel.font = tmpLabel.font.withSize(tmpLabel.font.pointSize + 1)
        tmpLabel.sizeToFit()
        tmpLabel.center.x = self.view.center.x
        tmpLabel.center.y = self.view.center.y
        if tmpLabel.font.pointSize >= 50 {sender.invalidate()}
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}
btn1を押したときはlabel1に「いないいない」を設定しTimerを0.001秒ごとに起動、label1のフォントサイズを1ずつ拡大。 btn2を押したときはlabel2に「ばぁ」を設定しTimerを0.005秒ごとに起動、label2のフォントサイズを1ずつ拡大。 これで以下のような動きを実現することができました。 これでscheduledTimerメソッドの使い方がわかりました。

プログラミング学習を体系的に進めたい人へ

独学で基礎を確認したあとに、質問できる環境や転職・副業向けの学習サポートも比べたい人は、プログラミングスクールの比較記事も参考にしてください。学習目的、料金、受講期間、サポート範囲を分けて見ると、自分に合う学び方を選びやすくなります。


目次