포스트

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()를 통해 저장 해줘야한다.

Dec-07-2024 07-12-51

현재 글을 작성하는 시점은 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로 생각을 하면 될듯 하다.

CleanShot 2024-12-07 at 07 17 48

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
}

해담 함수를 자세히 알아보면

  1. Core Data 값 확인
    • guard 문을 통해 Core Data에서 값을 가져오지 못했을 경우 0을 반환한다.
      • 예외 처리로 값이 없을 때 연속 학습 일수를 0으로 간주.
  2. 과거 데이터 필터링
    • nonfutureDays를 사용해 오늘(Date()) 이전 또는 같은 날짜의 데이터만 필터링한다.
      • 예: 오늘이 12월 7일이면, 1일부터 7일까지의 데이터를 가져온다.
  3. 초기화
    • 연속 학습 일 수를 계산하기 위해 streakCount를 생성하고 0으로 초기화.
  4. 역순 반복
    • reversed를 사용해 가장 최근 날짜부터 과거로 탐색.
      • 예: 1~77~1로 변경하여 반복문 실행.
  5. 학습 여부 확인
    • if day.didStudy: 해당 날짜에 학습했는지 확인.
      • 학습했다면 streakCount1 증가.
  6. 학습하지 않은 경우
    • 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에 예시가 있다.

Dec-07-2024 07-33-24

실행하면 다음과 같다

현재 기준으로 어제까지 한게 없다면 과거에 며칠을 했어도 연속은 끊기게 된다.

오늘은 개인사정으로 여기까지…

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.