タスク内容、アイコンの色、時間を入力するだけで簡単に追加できます。
姿勢を検知して、正しい姿勢の時だけタイマーが進みます。
今後はStudyPlus(勉強版Twitter)に完了したタスクを登録する機能を実装します! レポート機能を充実させます!
・View
・ViewModel
・Model
・Extension
AddView -->|追加| AddViewModel HomeView -->|タスク削除| HomeViewModel BodyPoseViewModel -->|姿勢判定| BodyPoseView
HomeViewModel -->|タスク削除| TaskDataModel AddViewModel -->|入力チェック| TaskDataModel PostureModel -->|姿勢取得| BodyPoseViewModel
TaskDataModel -->|更新| id1 PostureModel -->|座標データ取得| id2
プロジェクトで工夫した設計や、コードを具体的に示してください。 該当コードを示して、どんな工夫をしたのか分かりやすく記載してください。
タスクの開始時間と終了時間を可視化するためにタイムチャートを作成しました。
タイムチャート
Lines 27 to 100 in 031942d
ZStack { | |
// 背景の円 | |
Circle() | |
// 円形の線描写するように指定 | |
.stroke(lineWidth: 50) | |
.foregroundColor(.gray.opacity(0.15)) | |
// 現在時刻までmask | |
Circle() | |
// 0:1=0:360 | |
.trim(from: 0, to: isProgress ? timeDegrees/360 : 0) | |
.stroke(style: StrokeStyle(lineWidth: 50, lineCap: .butt, lineJoin: .round)) | |
.foregroundColor(.gray.opacity(isShowRemainingTime ? 0.6 : 0)) | |
.rotationEffect(Angle(degrees: 90)) | |
ForEach(tasks) { data in | |
// 今日のタスクのみ表示 | |
if circularTimeBarViewModel.isEqualToDate(startTime: data.startTime!) { | |
let color: Color = Color(taskColorName: Color.TaskColorNames(rawValue: data.color!) ?? .blue) | |
// 進捗を示す円 | |
Circle() | |
.trim(from: CGFloat(data.startTime!.formattedHourInt())/24 + CGFloat(data.startTime!.formattedMinutesInt())/60, | |
to: min(isProgress ? | |
CGFloat(data.endTime!.formattedHourInt())/24 + CGFloat(data.endTime!.formattedMinutesInt())/60: CGFloat(data.startTime!.formattedHourInt())/24 + CGFloat(data.startTime!.formattedMinutesInt())/60, | |
24)) | |
// 線の端の形状などを指定 | |
.stroke(style: StrokeStyle(lineWidth: 40, lineCap: .butt, lineJoin: .round)) | |
.fill(LinearGradient(gradient: Gradient(colors: [color, color.opacity(0.5)]), | |
startPoint: .top, | |
endPoint: .bottom)) | |
.rotationEffect(Angle(degrees: 90)) | |
} | |
} | |
ForEach(0..<24) { time in | |
// 円の区切り線 | |
Rectangle() | |
.frame(width: 2, height: 300) | |
.foregroundColor(.white) | |
.cornerRadius(20) | |
.rotationEffect(Angle(degrees: Double(time*15))) | |
// 時計の数字 | |
ZStack { | |
Text("\(time)") | |
.position(x: 140, y: -13) | |
.rotationEffect(Angle(degrees: Double(time*15)+174.5)) | |
} | |
} | |
// 時間または残り時間を表示 | |
Text(isShowRemainingTime ? remainingTime : nowTime) | |
.bold() | |
.font(.largeTitle) | |
} | |
.frame(width: 250, height: 250) | |
.onTapGesture { | |
withAnimation { | |
isShowRemainingTime.toggle() | |
} | |
} | |
.onAppear { | |
withAnimation(.easeInOut(duration: 1)) { | |
isProgress = true | |
} | |
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in | |
nowTime = Date().formattedTimeString() | |
remainingTime = Date().getRemainingTimeToday() | |
// 0:24=0:360 | |
// 0:60=0:15 | |
timeDegrees = Double(Date().formattedHourInt())*15+Double(Date().formattedMinutesInt())/4 | |
} | |
} | |
} |
CoreDataからタスクデータを取得して、完了済みのタスクの合計時間を過去7日分Chartで表示しました。
過去7日分のレポート
GoodPostureStudy/GoodPostureStudy_ryu/GoodPostureStudy_ryu/ViewModel/BarMarkViewModel.swift
Lines 10 to 127 in 031942d
class BarMarkViewModel: ObservableObject { | |
// Chart用Data | |
@Published var reportData: [Report] = [] | |
// Calender()のインスタンス生成(グレゴリオ暦を採用) | |
private let calender = Calendar(identifier: .gregorian) | |
// Chart用構造体 | |
struct Report: Identifiable { | |
let id = UUID() | |
// 日付 | |
let day: String | |
// 1日の合計勉強時間 | |
let timeCount: Double | |
} | |
// startTimeとendTimeから時差を求める | |
// 開始時間と終了時間から勉強時間(allTime)を算出 | |
func calculateAllTime(startTime: Date, endTime: Date) -> Double { | |
var allTime: Double = 0.0 | |
if let time = calender.dateComponents([.second], from: startTime, to: endTime).second { | |
allTime = Double(time)/(60*60) | |
} | |
return allTime | |
} | |
// taskから今日+過去6日分の勉強データをreportDataに格納 | |
func fetchReport(tasks: FetchedResults<Task>) { | |
// データの初期化 | |
reportData = [] | |
// 勉強の合計時間 | |
var todayStudyTime: Double = 0 | |
var yesterdayStudyTime: Double = 0 | |
var twoDaysAgoStudyTime: Double = 0 | |
var threeDaysAgoStudyTime: Double = 0 | |
var fourDaysAgoStudyTime: Double = 0 | |
var fiveDaysAgoStudyTime: Double = 0 | |
var sixDaysAgoStudyTime: Double = 0 | |
// 過去6日+今日の年月日を取得 | |
let days = sixDaysAgoDates() | |
// チャートのx軸(日付) | |
let today: String = "今日" | |
let yesterday: String = String(days.first(where: { $1 == 1 })!.key.suffix(5)) | |
let twoDaysAgo: String = String(days.first(where: { $1 == 2 })!.key.suffix(5)) | |
let threeDaysAgo: String = String(days.first(where: { $1 == 3 })!.key.suffix(5)) | |
let fourDaysAgo: String = String(days.first(where: { $1 == 4 })!.key.suffix(5)) | |
let fiveDaysAgo: String = String(days.first(where: { $1 == 5 })!.key.suffix(5)) | |
let sixDaysAgo: String = String(days.first(where: { $1 == 6 })!.key.suffix(5)) | |
for task in tasks { | |
guard let startTime = task.startTime else { return } | |
guard let endTime = task.endTime else { return } | |
// 1つのタスクの合計時間を計算 | |
let studyTime = calculateAllTime(startTime: startTime, endTime: endTime) | |
// startTimeをString型の年月日に変換 | |
let stringDay = startTime.formattedDateString() | |
// startTimeの今日を基準とした経過日数 | |
guard let dayElapsed = daySinceToday(stringDay: stringDay) else { return } | |
// タスクが完了しているか | |
if task.isDone { | |
// タスクの経過日数でデータを分ける | |
switch dayElapsed { | |
case 0: todayStudyTime += studyTime | |
case 1: yesterdayStudyTime += studyTime | |
case 2: twoDaysAgoStudyTime += studyTime | |
case 3: threeDaysAgoStudyTime += studyTime | |
case 4: fourDaysAgoStudyTime += studyTime | |
case 5: fiveDaysAgoStudyTime += studyTime | |
case 6: sixDaysAgoStudyTime += studyTime | |
default: break | |
} | |
} | |
} | |
// 過去6日+今日のチャートデータ | |
let todaysReport = Report(day: today, timeCount: todayStudyTime) | |
let yesterdaysReport = Report(day: yesterday, timeCount: yesterdayStudyTime) | |
let twoDaysAgoReport = Report(day: twoDaysAgo, timeCount: twoDaysAgoStudyTime) | |
let threeDaysAgoReport = Report(day: threeDaysAgo, timeCount: threeDaysAgoStudyTime) | |
let fourDaysAgoReport = Report(day: fourDaysAgo, timeCount: fourDaysAgoStudyTime) | |
let fiveDaysAgoReport = Report(day: fiveDaysAgo, timeCount: fiveDaysAgoStudyTime) | |
let sixDaysAgoReport = Report(day: sixDaysAgo, timeCount: sixDaysAgoStudyTime) | |
// チャートデータを追加 | |
reportData.append(contentsOf: [sixDaysAgoReport, | |
fiveDaysAgoReport, | |
fourDaysAgoReport, | |
threeDaysAgoReport, | |
twoDaysAgoReport, | |
yesterdaysReport, | |
todaysReport]) | |
} | |
// 今日から5日前までの年月日を取得 | |
func sixDaysAgoDates() -> [String: Int] { | |
let day: Double = 60*60*24 | |
let days: [String: Int] = [ | |
Date().formattedDateString(): 0, // 今日 | |
(Date() - day).formattedDateString(): 1, // 昨日 | |
(Date() - 2*day).formattedDateString(): 2, // 2日前 | |
(Date() - 3*day).formattedDateString(): 3, // 3日前 | |
(Date() - 4*day).formattedDateString(): 4, // 4日前 | |
(Date() - 5*day).formattedDateString(): 5, // 5日前 | |
(Date() - 6*day).formattedDateString(): 6 // 6日前 | |
] | |
return days | |
} | |
// 年月日から経過日数を計算 | |
func daySinceToday(stringDay: String) -> Int? { | |
let days = sixDaysAgoDates() | |
// 経過日数 | |
var dayElapsed: Int? | |
for day in days { | |
if day.key == stringDay { | |
dayElapsed = day.value | |
} | |
} | |
return dayElapsed | |
} | |
} |