開発環境
MacOS BigSur 11.4
Xcode 12.4
Swift 5
概要
記録系のアプリを制作していて、この1週間あたりどのくらいの記録ができたか、棒グラフで可視化できたらいいなーという人向け
↓イメージ画像
写真はGoals Fitnessのものです。
使用するデータ
今回CoreDateで使用するデータの仕様
エンティティ名: PostEntity
実装
今回は投稿系アプリで1日あたりの投稿数を可視化する棒グラフを実装します。
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週間前の登録データ数を表示させます。
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という数値が返されます。
実装結果
Previewにテストデータを挿入してグラフを確認。うまくいったようです。
テストデータの挿入コードは以下のようになります。
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)
}
}