SwiftUIを使って学習がてらタイマーアプリを作ってみよう3

#SwiftUIを使って学習がてらタイマーアプリを作ってみよう　Part3

####前置き　
前回投稿した[Part2](https://qiita.com/scream_episode/items/8d42198698fcf7513b54)の続きです。
今回のゴールとしては
適当に設定した秒数が表示されているTextとボタンが表示されている画面を作り
ボタンを押下すると一回だけカウントダウンができるようにする
また、カウントダウンに合わせて何か動きがある所までとしようと思います。
次回は[簡易な設定画面の作成と設定画面からタイマー画面にカウントダウン時間を渡して画面の遷移をする所](https://qiita.com/scream_episode/items/7bb249ee55140424829b)を実装します。

###開発環境
Mac (OS Big Sur version:11.3.1)
Xcode (version:12.5)
Swift (version: 5.4) 

### Timer用のプログレスバーを作成
調べていると簡単に枠のみの真円かつ表示面積をいじれるのViewの使い方を紹介している[記事](https://qiita.com/tsuzuki817/items/2273294c1a7d75024422)があったんでそれを参考に作成をしました。

####・背景を作成

```
    var body: some View {
            Circle()
                .stroke(Color.green, style: StrokeStyle(lineWidth:10))
                .padding(10)
    }

``` 
> Circle()

真円を描画してくれる

> .stroke(Color.green, style: StrokeStyle(lineWidth:10))

真円を輪にしてくれる
第一引数で色を第二引数でスタイル（幅とか）を指定している。

そしたら↓の感じで描画ができるので
![スクリーンショット 2021-07-01 15.49.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/436163/e1ce9d2b-cebc-b25c-92d5-271458000f05.png)
 後はZStackを使って同じ感じに重ねてやればOK


    var body: some View {
        ZStack(alignment: .center){
            Circle()
                .stroke(Color.green, style: StrokeStyle(lineWidth:10))
                .scaledToFit()
                .padding(10)
            
            Circle()
                .trim(from: 0.0, to: 0.5)
                .stroke(Color.yellow, style: StrokeStyle(lineWidth:10, lineCap: .round))
                .scaledToFit()
                .padding(10)
                .rotationEffect(.degrees(-90))

        }
    }

StrokeStyleでlineCapを設定してると枠の断面？を丸めたりすることができる。

> trim(from: 0.0, to: 0.5)
from:トリミング開始位置 
to:トリミング終了位置　０.0 〜 1.0

この関数を使うことで指定した範囲でトリミングをすることができる

> rotationEffect(.degrees(-90))

バー部分の円をトリミングするときに初期の支点（0度）がどうやら方位東（写真で言うと中心点から真右）のようなので回転させて開始位置を方位北に変更してやる
そうすると下記のスクショのようになるので後はカウントダウンと共にtrim関数の引数toを変えてやればプリインストールされているタイマーアプリのようなプログレスバーが使える。
![スクリーンショット 2021-07-01 16.00.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/436163/20d28d00-a4d6-c80d-a380-97b07bdc3a32.png)

### カウントダウン機能の作成とViewの改修
前回作成したTimerViewでZStackを使い作成したプログレスバーを重ねて表示させる。

``` TimerView.swift
    var body: some View {
        ZStack(){
            TimerProgressBar(trimValue: $timerContorller.duration)

            VStack(alignment: .center){
　　　
　            以下略

            }.onAppear(){
                timerContorller.displayTime = countDwon
                timerContorller.maxValue = countDwon
            }

        }
    }

``` 
> TimerProgressBar(trimValue: $timerContorller.duration)
TimerModelのPublishedタグをつけている変数を参照渡しをしている。
渡している変数の値がtrim（）のトリミング位置を調整する値になる。
Timer処理の中でdurationの値が変更されると自動でView側の表示が更新される。

> onAppler{}
onApplerはViewが初めて描画される一回のみに呼ばれる関数で
表示時間と計測時間を初期化している。


``` TimerModel.swift
    @Published var displayTime: Double = 0.0 // 表示時間
    @Published var duration: CGFloat = 1.0 // プログレスバー位置
    @Published var timer: AnyCancellable!
    
    var maxValue  = 0.0 // 開始時間
    var remainingTime = 0.0 // 残り時間
        
    // タイマー処理の開始
    func start(_ interval: Double = 0.05){

                 略

        // TimerPublisherが存在しているときは念の為処理をキャンセル
        if let _timer = timer{
            _timer.cancel()
        }else{
　　　　　　　　　　　　　　　　　　　　　　　　　// タイマーの実態を取得する場合は経過時間の初期化をする
            self.remainingTime = self.displayTime
        }
        
        // in: -> .defalut:操作系の入力と同様に処理をする : .common それ以外
        timer = Timer.publish(every: interval, on: .main, in: .common)
            // 繰り返し処理の実行
            .autoconnect()
            //　レシーバーが動くスレッドを指定 .main -> メインスレッド(UI)　, .global() -> 別スレッド
            .receive(on: DispatchQueue.main)
            .sink(receiveValue: ({_ in
                    // 設定した時間ごとに呼ばれる
                    self.remainingTime -= interval
                    // 切り捨て位置変更
                    let floorOption: Double = 10.0
                    self.displayTime = floor((self.remainingTime * floorOption)) / floorOption
                    
                    self.duration = CGFloat((self.remainingTime / self.maxValue))
                    if self.displayTime <= 0.00 {
                        // countが0以下になったらタイマーを終了
                        self.duration = 0
                        self.stop()
                    }

                })
            )
        
    
    }

                 略
}
``` 
やっている事は単純に
タイマーに設定した繰り返し時間ごとに
計測時間 - インターバル時間　を行い、表示用変数に小数点第二位で切り捨てを行った値をセットし、その後にプログレスバーの表示更新用変数に(経過時間/計測時間)で0.0 〜 1.0になるように更新をしています。


![ezgif.com-gif-maker.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/436163/4c0607d4-bd30-29ed-cb21-7f68750b78cb.gif)

---
###参考・備考
[作りながら学ぶ！ SwiftUIアニメーション インジケーター編](https://qiita.com/tsuzuki817/items/2273294c1a7d75024422)!

###シリーズ：「SwiftUIの学習でタイマーアプリを作ってみよう」のリンク
[part1:プロジェクトの作成とGitHubに登録](https://qiita.com/scream_episode/items/f8bfcba4c65c3b758db8)
[part2: Timerを利用したカウント処理の実装](https://qiita.com/scream_episode/items/8d42198698fcf7513b54)
[part3:計測画面でのプログレスバーの実装]　← 今ここ
[part4:スピナー実装](https://qiita.com/scream_episode/items/7bb249ee55140424829b)
[part5:ライブラリから音楽を選択して再生](https://qiita.com/scream_episode/items/5e60b85fec58ebf50e2f)
[part6:タイマー処理のバックグランド対応](https://qiita.com/scream_episode/items/33586875cf609a884184)
[part7:音楽のバックグランド再生対応](https://qiita.com/scream_episode/items/abb2168fe66b90836443)
[part8:PageViewを作成](https://qiita.com/scream_episode/items/fb928f114de7a857b480)
[part9:PageViewのインジゲータを作成](https://qiita.com/scream_episode/items/7950d45a17f8f53b4094)