WidgetKit (9)
Calendar에 TapGesture 추가
현재는 캘린더에 어떠한 액션도 적용이 되어있지 않다.
이젠 캘린더를 탭했을때의 Gesture를 추가해 보도록 한다.
onTapGesture
Modifier를 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.onTapGesture {
if day.date!.dayInt <= Date().dayInt {
day.didStudy.toggle()
do {
try viewContext.save()
print("👆 \(day.date!.dayInt) now studied.")
} catch {
print("Failed to save context")
}
} else {
print("Can't study in the future!")
}
}
현재일수 기준으로 과거만 적용이 되고 미래는 적용이 되지 않게 한다.
그리고 값이 변화를 하면 반드시 viewContext.save()
를 통해 저장 해줘야한다.
현재 글을 작성하는 시점은 12월 7일 이기에 7일까지만 나온다 그 이후에는 Console에 프린트 된다.
1
2
3
4
Can't study in the future!
Can't study in the future!
Can't study in the future!
Can't study in the future!
StreakView 만들기
공부를 퐁당퐁당 하루 걸러서 하는게아니라 연달아서 하는경우에 뭔가 강조를 하기위한 View로 생각을 하면 될듯 하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
@State private var streakValue = 0
var body: some View {
VStack {
Text("20")
.font(.system(size: 200, weight: .semibold, design: .rounded))
.foregroundStyle(streakValue > 0 ? .orange : .pink)
Text("Current Streak")
.font(.title2)
.bold()
.foregroundStyle(.secondary)
}
.offset(y: -50)
}
StreakValue 계산 함수 구현하기
그전에 Coredata를 불러오는 FetchRequest를 여기다가도 구현해준다.
1
2
3
4
5
6
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Day.date, ascending: true)],
predicate: NSPredicate(format: "(date >= %@) AND (date <= %@)",
Date().startOfMonth as CVarArg,
Date().endOfMonth as CVarArg))
private var days: FetchedResults<Day>
이전글에서는 Date().startOfCalendarWithPrefixDays
로 되어있어서 달력의 1일이 일요일이 아닌경우 그 앞의 공백을 계산하기 위함이었는데, 여기 View에서는 단순히 현재 달을 기준으로 연속으로 공부한 일이 있는지를 계산을 하기 위함이라 위와 같이 작성을 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.onAppear {
streakValue = calculateStreakValue()
}
func calculateStreakValue() -> Int {
guard !days.isEmpty else { return 0 }
let nonfutureDays = days.filter { $0.date!.dayInt <= Date().dayInt }
var streakCount = 0
for day in nonfutureDays.reversed() {
if day.didStudy {
streakCount += 1
} else {
if day.date!.dayInt != Date().dayInt {
break
}
}
}
return streakCount
}
해담 함수를 자세히 알아보면
- Core Data 값 확인
guard
문을 통해 Core Data에서 값을 가져오지 못했을 경우0
을 반환한다.- 예외 처리로 값이 없을 때 연속 학습 일수를
0
으로 간주.
- 예외 처리로 값이 없을 때 연속 학습 일수를
- 과거 데이터 필터링
nonfutureDays
를 사용해 오늘(Date()
) 이전 또는 같은 날짜의 데이터만 필터링한다.- 예: 오늘이 12월 7일이면, 1일부터 7일까지의 데이터를 가져온다.
- 초기화
- 연속 학습 일 수를 계산하기 위해
streakCount
를 생성하고0
으로 초기화.
- 연속 학습 일 수를 계산하기 위해
- 역순 반복
reversed
를 사용해 가장 최근 날짜부터 과거로 탐색.- 예:
1~7
→7~1
로 변경하여 반복문 실행.
- 예:
- 학습 여부 확인
if day.didStudy
: 해당 날짜에 학습했는지 확인.- 학습했다면
streakCount
를1
증가.
- 학습했다면
- 학습하지 않은 경우
else
블록에서 학습하지 않았을 경우:- 오늘 날짜와 일치하지 않는 경우:
- 연속 학습이 중단된 것으로 간주하고
break
로 반복문 종료.
- 연속 학습이 중단된 것으로 간주하고
- 오늘 날짜와 일치하는 경우:
- 학습을 중단하지 않은 것으로 간주하고 과거 데이터 탐색을 계속 진행.
- 오늘 날짜와 일치하지 않는 경우:
실행에 앞서 View를 확인해야하니 TabView를 만들어 준다.
학습하지 않은 경우에 대해서
else
블록에서는 해당 날짜에 학습을 하지 않은 경우를 처리한다. 이때 조건에 따라 두 가지 상황으로 나뉘어진다.
1. 오늘 날짜와 일치하지 않는 경우
- 과거에 학습하지 않은 날짜가 나타난 경우, 이는 연속 학습이 중단된 것으로 간주한다.
- 이 상황에서는
break
문을 사용해 반복문을 즉시 종료하고 더 이상 데이터를 탐색하지 않는다.
예시
현재 날짜가 12월 7일이고, 다음과 같은 학습 기록이 있다고 가정
날짜 | 학습 여부 (didStudy ) |
---|---|
12월 7일 | ❌ (false) |
12월 6일 | ✅ (true) |
12월 5일 | ✅ (true) |
12월 4일 | ❌ (false) |
- 탐색 순서:
12월 7일
→12월 6일
→12월 5일
→12월 4일
- 12월 7일: 학습하지 않았으므로
streakCount
계산은 진행되지 않음. - 12월 6일:
didStudy == true
,streakCount += 1
. - 12월 5일:
didStudy == true
,streakCount += 1
. - 12월 4일: 학습하지 않았으므로 연속 학습이 중단된 것으로 간주하고
break
.
최종적으로, streakCount = 2
가 반환된다.
2. 오늘 날짜와 일치하는 경우
- 학습하지 않은 날짜가 오늘이라면, 이는 연속 학습의 중단으로 간주하지 않는다.
- 오늘 학습하지 않았더라도 과거 데이터를 탐색하여 연속 학습을 확인할 수 있도록 반복문을 계속 진행한다.
예시
현재 날짜가 12월 7일이고, 다음과 같은 학습 기록이 있다고 가정
날짜 | 학습 여부 (didStudy ) |
---|---|
12월 7일 | ❌ (false) |
12월 6일 | ✅ (true) |
12월 5일 | ✅ (true) |
12월 4일 | ❌ (false) |
- 탐색 순서:
12월 7일
→12월 6일
→12월 5일
→12월 4일
- 12월 7일: 오늘 학습하지 않았으므로
streakCount
계산은 진행되지 않지만, 연속 학습 중단으로 간주하지 않음.- 반복문은 계속 진행.
- 12월 6일:
didStudy == true
,streakCount += 1
. - 12월 5일:
didStudy == true
,streakCount += 1
. - 12월 4일: 학습하지 않았으므로 연속 학습이 중단된 것으로 간주하고
break
.
최종적으로, streakCount = 2
가 반환된다.
결론
- 오늘 날짜에 학습하지 않았다면
streakCount
에는 영향을 미치지 않지만, 과거의 학습 기록을 계속 탐색하여 연속 학습을 계산한다. - 이 논리는 오늘이 지나기 전까지 학습하지 않은 상태를 허용하며, 과거 기록에만 영향을 준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var body: some Scene {
WindowGroup {
TabView {
CalendarView()
.tabItem {
Label("Calendar", systemImage: "calendar")
}
StreakView()
.tabItem {
Label("Streak", systemImage: "swift")
}
}
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
현재 tabItem은 Deprecated 될 예정이라고 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
var body: some Scene {
WindowGroup {
TabView {
Tab("Calendar", systemImage: "calendar") {
CalendarView()
}
Tab("Streak", systemImage: "swift") {
StreakView()
}
}
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
이젠 이렇게 하도록 하자.
TabView Docs에 예시가 있다.
실행하면 다음과 같다
현재 기준으로 어제까지 한게 없다면 과거에 며칠을 했어도 연속은 끊기게 된다.
오늘은 개인사정으로 여기까지…