LoginSignup
4
3

More than 3 years have passed since last update.

【SwiftUI】芝生カレンダー作った

Last updated at Posted at 2021-04-18

SwiftUIでヒートマップ形式のカレンダー(芝生、草とか言われてるやつ)を作ってみたので簡単な仕組みではありますがメモしておこうと思います。

MMHeatmapライブラリ


折角ライブラリを作ったので学習も兼ねてSwift Package Managerに対応し、公開してみました。

View構造

最下層から順に説明します。

1週間用VStack

まず1週間分(7つのセル)を次のような感じでVStackで縦に並べます。

VStack(spacing:2){
            ForEach(0..<7){ i in
                RoundedRectangle(cornerRadius: 2).frame(width: 10,height: 10).modifier(CellColorModifier(isRange: (i >= start && i <= end ) , value: values[i], maxValue: maxValue,minColor: style.minCellColor,baseColor: style.baseCellColor))
            }
        }

MMHeatmapColumnView.swift

プレビューしにくいのでこの階層ではDate型は扱わず、上のViewから与えられた表示範囲と値に従ってセルの色を決定します。
※CellColorModifierについては後述します。

1ヵ月用HStack

次にHStackで1週間用VStackを週数分並べます。
1ヶ月分の週数の特定は、翌月の0日(つまりその月の最終日)を設定したDateComponentsをDate型にした後、Calendarで週数を求めることができます。

var comp = DateComponents()
comp.year = 2021
comp.month = MM + 1
comp.day = 0
let date = calendar.date(from: comp)!
let weeks = calendar.component(.weekOfMonth, from: date)

数ヶ月用HStack

最後に1ヶ月用HStackを指定範囲の月数分並べます。

セルの色決定

データとなる数値配列の中で最大値の色をbaseColor、最小値0の色をminColorとしてbaseColorとminColorの中間色を求めます。
尚、データの値をnilとした場合はセルの色を透明(Color.clear)に設定しています。

次のモディファイアの

saturation = (saturation - secondSaturation) * pct + secondSaturation
brightness = (brightness - secondBrightness) * pct + secondBrightness

の部分で中間色(彩度、明度)を決定しています。
(このコードでは色相の中間色は求めてませんでした)

fileprivate struct CellColorModifier:ViewModifier {
    init(isRange:Bool,value:Int?,maxValue:Int,minColor:UIColor,baseColor:UIColor) {
        self.isRange = isRange
        if let v = value{
        let pct:CGFloat = CGFloat(v) / CGFloat(maxValue)
            var secondHue:CGFloat = 0
            var secondSaturation:CGFloat = 0
            var secondBrightness:CGFloat = 0
            var secondAlpa:CGFloat = 0
        minColor.getHue(&secondHue, saturation: &secondSaturation, brightness: &secondBrightness, alpha: &secondAlpa)
        var hue:CGFloat = 0
        var saturation:CGFloat = 0
        var brightness:CGFloat = 0
        var alpha:CGFloat = 0
        baseColor.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
        saturation = (saturation - secondSaturation) * pct + secondSaturation
        brightness = (brightness - secondBrightness) * pct + secondBrightness
        self.rangeColor = Color(UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha))
        }else{
            self.rangeColor = Color(UIColor.clear)
        }
    }
    let isRange:Bool
    let rangeColor:Color
    func body(content: Content) -> some View {
        Group{
        if(isRange){
            content.foregroundColor(rangeColor)
        }else{
            content.foregroundColor(Color.clear)
        }
        }
    }
}

Swift Package

次の記事を参考にしました。jjjkkkjjj様に感謝します。
https://qiita.com/jjjkkkjjj/items/727517263292ae7a3a87

ちなみにSwiftUIのプレビューをする際に大量のエラーが出る場合は、プレビューするファイル以外を閉じて仮想端末をMacからiPhoneやiPadにすると直りました。

終わりに

以上で基本的な芝生カレンダーを作ることができました。
改善するやる気が起きたらGeometryReaderを使ってMMHeatmapを画面サイズに応じたカレンダーに改良したいと思います。

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3