はじめに
【Combine】Timerの処理をCombineを使って置き換える - Swift・iOSの続きです。Stack Overflowの記事(ios - SwiftUI - Animating count text from 0 to x - Stack Overflow)を参考に、CombineのTimerを使ってカウントアップのアニメーションを実装してみました。
本題
サンプルイメージ
開発環境
- macOS Big Sur 11.5.2
- Xcode 12.5.1
- Swift 5.4.2
全体の実装
ContentView.swift
import SwiftUI struct ContentView: View { @ObservedObject private var viewModel: ContentViewModel init(viewModel: ContentViewModel) { self.viewModel = viewModel } var body: some View { VStack { Text("\(viewModel.count)") .font(.title) .fontWeight(.bold) } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView(viewModel: ContentViewModel()) } }
ContentViewModel.swift
import Foundation import Combine final class ContentViewModel: ObservableObject { @Published var count = 0 private let endCount: Int = 1000 private var cancellable: AnyCancellable? init() { cancellable = Timer.publish(every: 0.02, on: .main, in: .common) .autoconnect() .sink { [weak self] _ in guard let self = self else { return } if self.count < self.endCount { self.count += 1 } else { self.cancellable?.cancel() } } } }
おまけ:幅広い数字に対応する
サンプルでは1,000になるまで1を足し続けていますが、例えばSuicaアプリのチャージ機能のように、500円〜10,000円の範囲でアニメーションする可能性がある場合、大きい数字になればなるほどアニメーションが完了するまでの時間がかかってしまい、ユーザーを待たせてしまいます。そのため、アニメーションしたい数字に合わせて足す数字を変更して対応してみます。以下のようにViewModelの処理を変更、追加しました。
ContentViewModel.swift
import Foundation import Combine final class ContentViewModel: ObservableObject { @Published var count = 0 // ここの数字を変更して動作を確認してみてください private let endCount: Int = 10000 private var cancellable: AnyCancellable? init() { cancellable = Timer.publish(every: 0.02, on: .main, in: .common) .autoconnect() .sink { [weak self] _ in guard let self = self else { return } // 追加 let additionNumber = Int(ceil(Double(self.endCount) / 50.0)) if self.count < self.endCount { // 変更 self.count += additionNumber } else { // 追加 self.count = self.endCount self.cancellable?.cancel() } } } }
※追記:アニメーション時の数字の横振れに対応する
タイマーのペースを上げると、数字が横振れしながらカウントアップするように見えてしまいます。等幅フォントに変更すると、横振れを抑えることができます。以下の記事に詳しく記載されており、参考にさせていただきました。
※参考:【SwiftUI】SFフォントを等幅フォントとして扱う方法 - おもちblog
今回のサンプルでは、ContentView.swiftのTextのフォント周りの実装を以下のように変更します。
変更前
Text("\(viewModel.count)") .font(.title) .fontWeight(.bold)
変更後
Text("\(viewModel.count)") .font(Font(UIFont.monospacedDigitSystemFont(ofSize: 30, weight: .bold)))
おわりに
「幅広い数字に対応する」で書いたことが今回記事に残したかったことなのですが、読み手からすればしょうもない内容なのでおまけにしました笑