3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

SwiftUI + CoreDataで1週間ごとのデータ登録数が目に見える棒グラフを実装する

Last updated at Posted at 2021-07-18

開発環境

MacOS BigSur 11.4
Xcode 12.4
Swift 5

概要

記録系のアプリを制作していて、この1週間あたりどのくらいの記録ができたか、棒グラフで可視化できたらいいなーという人向け
↓イメージ画像

写真はGoals Fitnessのものです。

使用するデータ

今回CoreDateで使用するデータの仕様
エンティティ名: PostEntity

スクリーンショット 2021-07-18 21.24.17.png
Date型でデータの追加日を格納する。

実装

今回は投稿系アプリで1日あたりの投稿数を可視化する棒グラフを実装します。

BarGraph.swift
import SwiftUI
import CoreData

struct BarGraph: View {
    var body: some View {
            HStack(alignment: .center, spacing: 10)
            {
                ForEach(0..<7){ //6週間前から今週までの投稿数を表示させる。
                    v in
     
                    BarView(value: v) // BarViewに0から6の数字をわたす


                }
            }.padding(.top, 23).animation(.default)
    }

}

BarView.swiftではBarGraph.swiftから受け取った値(value)に応じた週のデータ数を表示した棒グラフを生成します。
受け取った値が0なら6週間前、1なら5週間前の登録データ数を表示させます。

BarView.swift
import SwiftUI
import CoreData

//任意の週の日曜日の0時0分0秒を取得。
func start_of_week(num: Int) -> Date {
    let thisWeekDay = Calendar.current.dateComponents([.weekday], from: Date()).weekday! //今日の曜日を数値で取得(日から土で1~7)
    let n = thisWeekDay - 1
    let start_of_thisweek = Calendar.current.date(byAdding: .day, value: -n, to: Date())! //今週の日曜日を取得
    let week = Calendar.current.date(byAdding: .weekOfMonth, value: num-6, to: start_of_thisweek)! //ここで今日の日付から任意の週巻き戻した日時を取得
    let start_of_date = Calendar(identifier: .gregorian).startOfDay(for: week) //weekで取得した日付の始まりの時刻を取得

    return start_of_date
}

//任意の週の土曜日の23時59分59秒を取得
func end_of_week(num: Int) -> Date {
    let thisWeekDay = Calendar.current.dateComponents([.weekday], from: Date()).weekday! //今日の曜日を数値で取得(日から土で1~7)
    let n = 7 - thisWeekDay
    let end_of_thisweek = Calendar.current.date(byAdding: .day, value: n, to: Date())! //今週の土曜日を取得
    let week = Calendar.current.date(byAdding: .weekOfMonth, value: num-6, to: end_of_thisweek)! //ここで今日の日付から任意の週巻き戻した日時を取得
    let end_of_date = Calendar(identifier: .gregorian).date(bySettingHour: 23, minute: 59, second: 59, of: week)! //weekで取得した日付の23:59:59を取得
    return end_of_date
}


struct BarView: View{
    //BarGraphから受け取る値
    var value: Int
    
    @Environment(\.managedObjectContext) var viewContext
    var postsRequest : FetchRequest<PostEntity>
    
    func getWeekFromDate(num: Int) -> String {
        let thisWeekDay = Calendar.current.dateComponents([.weekday], from: Date()).weekday! //今日の曜日を数値で取得(日から土で1~7)
        let n = thisWeekDay - 1
        let start_of_thisweek = Calendar.current.date(byAdding: .day, value: -n, to: Date())! //今週の日曜日を取得
        let date = Calendar.current.date(byAdding: .weekOfMonth, value: -6+num, to: start_of_thisweek)! //ここで今日の日付から任意の週巻き戻した日時を取得
        let formatter = DateFormatter()
        formatter.locale = .current
        formatter.dateFormat = "M/d"
        return formatter.string(from: date)
    }
    
    //投稿数に応じて棒グラフの長さを返す関数
    func getHeightOfBar(num: Int) -> Int {
        var height = num * 20
        
        if height > 200 { // 投稿数が多くてbarのheightが上限を超えないように関数で処理する
            height = 200
        }
        return height
    }
    
    //BarGraphから受け渡される数値とCoreDataのFetchRequestは初期化処理をする。
    init(value: Int){
        self.value = value
        self.postsRequest = FetchRequest(entity: PostEntity.entity(),
                             sortDescriptors: [NSSortDescriptor(keyPath: \PostEntity.date,
                                                                                        ascending: false)],
                                 
                             predicate: NSPredicate(format:"date BETWEEN {%@ , %@}", start_of_week(num: value) as NSDate, end_of_week(num: value) as NSDate)
                             )
        
            
    }
    
    //指定された日付の投稿を取得 .countプロパティで投稿数を取得
    var postList: FetchedResults<PostEntity>{postsRequest.wrappedValue}
    

    //グラフのビューを生成
    var body: some View {
        VStack {
            VStack {

                ZStack (alignment: .bottom) {
                    RoundedRectangle(cornerRadius: 4)
                        .frame(width: 30, height: 200).foregroundColor(Color(.white)) //グラフの最大の長さ
                    RoundedRectangle(cornerRadius: 4)
                        .frame(width: 30, height: CGFloat(getHeightOfBar(num: postList.count))).foregroundColor(.green) //投稿数に応じたグラフの長さ
                    Text(postList.count.description).font(.footnote).foregroundColor(Color(red:0.64, green:0.5, blue: 0.33))
                    
                }.padding(.bottom, 8)
                
            }
            Text(getWeekFromDate(num: value)) //棒グラフに対応する週の開始日を表示
            
           
            
        }
        
    }
}


色々な関数があってごちゃごちゃしていますが、一つずつ見ていきます。

func start_of_week(num: Int) -> Date {
    let thisWeekDay = Calendar.current.dateComponents([.weekday], from: Date()).weekday! //今日の曜日を数値で取得(日から土で1~7)
    let n = thisWeekDay - 1
    let start_of_thisweek = Calendar.current.date(byAdding: .day, value: -n, to: Date())! //今週の日曜日を取得
    let week = Calendar.current.date(byAdding: .weekOfMonth, value: num-6, to: start_of_thisweek)! //ここで今日の日付から任意の週巻き戻した日時を取得
    let start_of_date = Calendar(identifier: .gregorian).startOfDay(for: week) //weekで取得した日付の始まりの時刻を取得

    return start_of_date
}

func end_of_week(num: Int) -> Date {
    let thisWeekDay = Calendar.current.dateComponents([.weekday], from: Date()).weekday! //今日の曜日を数値で取得(日から土で1~7)
    let n = 7 - thisWeekDay
    let end_of_thisweek = Calendar.current.date(byAdding: .day, value: n, to: Date())! //今週の土曜日を取得
    let week = Calendar.current.date(byAdding: .weekOfMonth, value: num-6, to: end_of_thisweek)! //ここで今日の日付から任意の週巻き戻した日時を取得
    let end_of_date = Calendar(identifier: .gregorian).date(bySettingHour: 23, minute: 59, second: 59, of: week)! //weekで取得した日付の23:59:59を取得
    return end_of_date
}

まずは、BarGraph.swiftから受け取る値(0から6)に対して、(num - 6)週間巻き戻した週の日曜日0時0分0秒と土曜日23時59分59秒を取得する関数です。
Swiftの日付操作については以下の記事が参考になりました。
Swift 3 の日時操作チートシート
Swift3 で今日の0時0分0秒を取得する

init(value: Int){
        self.value = value
        self.postsRequest = FetchRequest(entity: PostEntity.entity(),
                             sortDescriptors: [NSSortDescriptor(keyPath: \PostEntity.date,
                                                                                        ascending: false)],
                                 
                             predicate: NSPredicate(format:"date BETWEEN {%@ , %@}", start_of_week(num: value) as NSDate, end_of_week(num: value) as NSDate)
                             )
    }

そして、初期化処理内のFetchRequestでstart_of_weekとend_of_weekで取得した日付の間の投稿を取得します。
取得した投稿をpostListとしておきます。

RoundedRectangle(cornerRadius: 4)
                        .frame(width: 30, height: CGFloat(getHeightOfBar(num: postList.count))).foregroundColor(.green)

そしてView内でpostListの要素数をcountプロパティで取得して、getHeightOfBarという関数に渡します。

func getHeightOfBar(num: Int) -> Int {
        var height = num * 20
        
        if height > 200 { // 投稿数が多くてbarのheightが上限を超えないように関数で処理する
            height = 200
        }
        return height
    }

この関数によって投稿数に応じてheightという数値が返されます。

実装結果

スクリーンショット 2021-07-18 21.20.24.png

Previewにテストデータを挿入してグラフを確認。うまくいったようです。

テストデータの挿入コードは以下のようになります。

BarGraph.swift

struct BarGraph_Previews: PreviewProvider {
    
    static let container = (UIApplication.shared.delegate as! AppDelegate).persistentContainer
    
    static let context = container.viewContext
    
    static var previews: some View {
    let request = NSBatchDeleteRequest(
       fetchRequest: NSFetchRequest(entityName: "PostEntity"))
    try! container.persistentStoreCoordinator.execute(request, with: context)
        
    // テストデータを追加
        
        
    PostEntity.create(in: context,
                      content: "tes", detail: "tes", rate: 3, date: Calendar.current.date(byAdding: .weekOfMonth, value: -2, to: Date())!)
    PostEntity.create(in: context,
                      content: "tes", detail: "tes", rate: 3, date: Calendar.current.date(byAdding: .weekOfMonth, value: -1, to: Date())!)
    PostEntity.create(in: context,
                      content: "tes", detail: "tes", rate: 3, date: Calendar.current.date(byAdding: .weekOfMonth, value: -1, to: Date())!)
    PostEntity.create(in: context,
                      content: "tes", detail: "tes", rate: 3, date: Calendar.current.date(byAdding: .weekOfMonth, value: -1, to: Date())!)
    PostEntity.create(in: context,
                      content: "tes", detail: "tes", rate: 3, date: Calendar.current.date(byAdding: .weekOfMonth, value: -1, to: Date())!)
    PostEntity.create(in: context,
                      content: "tes", detail: "tes", rate: 3, date: Date())
    PostEntity.create(in: context,
                      content: "tes", detail: "tes", rate: 3, date: Date())
    return BarGraph()
        .environment(\.managedObjectContext, context)
            
        
    }
}

参考記事

SwiftUI棒グラフ
Swift 3 の日時操作チートシート
Swift3 で今日の0時0分0秒を取得する

3
5
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
3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?