#SwiftUIを使って学習がてらタイマーアプリを作ってみよう Part6
###前置き
前回投稿したPart5の続きです。
今回のゴールとしては下記の処理の実装とします。
1.アプリがフォアグラウンドからバックグランドに行ってもタイマー処理が終了せずに動くようにする
次回はアプリがバックグランドに行っていても音が鳴るようにする所をやる予定です(投稿日は未定)
###開発環境
Mac (OS Big Sur version:11.3.1)
Xcode (version:12.5)
Swift (version: 5.4)
対象IOS(version:14.5以上)
###バックグラウンドでもTimer処理を続行させる
昔の記事だけど、方法を見つけた
フォアグラウンドからバックグラウンドに行っても処理を続行するする処理を調べているとこちらの記事を見つけた。
どうやらAppDelegateを使ってアプリがバックグラウンドへ移行したりフォアグランドに移行する時に呼ばれる関数を登録し、
その関数内でbeginBackgroundTaskを呼び出す事でフォアグランドの処理が実装ができるみたい。
問題が発生した
見つけた記事を参考にbeginBackgroundTaskを試そうとAppDelegateを使えるように試していた。
SwiftUIのライフサイクルでAppDelegateを使う方法もこちらの記事に乗っていたので
class AppDelegate: UIResponder, UIApplicationDelegate {
var backgroundTaskId: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier(rawValue: 0)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
return true
}
func applicationWillResignActive(_ application: UIApplication) {
print("アプリ閉じそうな時に呼ばれる")
self.backgroundTaskId = application.beginBackgroundTask(withName: ""){
[weak self] in
application.endBackgroundTask((self?.backgroundTaskId)!)
self?.backgroundTaskId = UIBackgroundTaskIdentifier.invalid
}
}
func applicationDidEnterBackground(_ application: UIApplication) {
print("アプリを閉じた時に呼ばれる")
}
func applicationWillEnterForeground(_ application: UIApplication) {
print("アプリを開きそうな時に呼ばれる")
}
func applicationDidBecomeActive(_ application: UIApplication) {
print("アプリを開いた時に呼ばれる")
application.endBackgroundTask(self.backgroundTaskId)
}
func applicationWillTerminate(_ application: UIApplication) {
print("フリックしてアプリを終了させた時に呼ばれる")
}
}
@main
struct TimerApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
上記のように実装をしてシュミレーターでアプリを起動させて試してみると、
Timer処理が止まらずに動くどころかprint()のテキストすら一切出やしない。
BreakPointをしてもappliction()関数は呼ばれていることは確認できるが、それ以外のapplicationDidEnterBackground等の関数はいくら試しても一向に呼ばれやしない。
正直AppDelegteで問題が出るとは思ってはいなかったなぁ
別の方法を探した。
AppDelegeteの変数がよばれない問題が発生し、原因を調べてもよく分からない。
しょうがない(良くはないだろうけど)ので別の方法をさらに探してみた。
するとこちらの記事を見つけ、どうやらCapabilityに設定をしたやりたい事ができるみたい。
早速試してみた。
とりあえずはなんかバックグランドに行くとタイマーで設定している関数の呼び出し速度がOutPutを見る限り落ちているが、
ひとまずはアプリがバックグランドに行ってもタイマー処理が止まらずに動くという当初の目的は達成しているので色々と不安はあるが、とりあえずは良しとしよう。
(実際にApple Storeとかで公開するアプリで使うなら、どのくらいの時間なら問題なく動くとか、なんかのタイミングで終了するんじゃないとかしっかりと
調べる必要はある)
後は実機ではなくシュミレーターでしか試してないから実機で問題ないか調べないとなぁ
↑AppTargetのSigning & Capabilities で+CapabiltiyをクリックしてBackground Modesの追加&設定スクショ
↑エミュレータとxcodeのoutputの動画
*アプリがバックグランドに行ってもタイマー自体は動いているのがわかると思います。(count = 表示時間)
####実機で試してみたが
実機にインストールして動かすとバックグランドにアプリがいくとタイマー処理が止まってしまう。
なんでだ?エミュレータはある程度上手くいっているように見えたのに...
####結局は
再び検索してUIApplication.shared.beginBackgroundTaskについて書いてある記事を見つけたのでそれを参考にバックグランドでの処理を実装することにした。
AppDelegateを利用した処理はやはり原因は分からんが呼ばれんので、
直接TimerModelのタイマースタート関数とタイマー終了関数で
beginBackgroundTaskとendBackgroundTaskをセットで呼んでやることでどうにかなった。
が、参考記事曰くどの程度アプリがバックグランドいる時の処理が生きているかは保証できないとあったので今回のような意図で使うには実用性はないと思われる。と言うかApple的には常駐してなんか処理するような事はするなって感じなんだろうね。
//追加処理のみ抜粋
var backgroundTaskId = UIBackgroundTaskIdentifier.init(rawValue: 0)
func start(_ interval: Double = 0.05){
print("start Timer")
if self.displayTime <= 0 {
// countが0以下になら処理を行わない
return
}
// 中断されたら困る処理の起点にコレを書く
self.backgroundTaskId = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
以下略
}
func finish(){
stop()
print("finish Timer")
timer = nil
// 処理が終わったらコレを書く
UIApplication.shared.endBackgroundTask(self.backgroundTaskId)
}
###参考・引用元
・Qiita記事:[Swift]バックグラウンドでも処理を続ける方法
・Qiita記事:【SwiftUI】SwiftUIでAppDelegateの処理を実装する方法
・Qiita記事:[Swift] iOSのバックグラウンド処理について
・[Xcode][iOS] アプリのバックグラウンド処理やバックグラウンドからの復帰処理のデバッグ方法
###シリーズ:「SwiftUIの学習でタイマーアプリを作ってみよう」のリンク
part1:プロジェクトの作成とGitHubに登録
part2: Timerを利用したカウント処理の実装
part3:計測画面でのプログレスバーの実装
part4:スピナー実装
part5:ライブラリから音楽を選択して再生
part6:タイマー処理のバックグランド対応←今ここ
part7:音楽のバックグランド再生対応
part8:PageViewを作成
part9:PageViewのインジゲータを作成