Tistory iOYES - SwiftUI로 캘린더 직접 구현하기 게시글의 커스텀 캘린더의 코드를 사용하여 기능을 추가 하는 게시글입니다.
이 게시글은 위 블로그에서 구현한 캘린더 = 커스텀 캘린더 에 다음 기능을 추가하는 과정을 설명합니다.
커스텀 캘린더 코드를 xcode로 가져온다.
커스텀 캘린더 의 CalenderView 오타를 고친다.CalenderView 우클릭 -> refactor -> rename -> CalendarView
CellView 에 .scaledToFit 추가CellView가 날짜 한 칸의 뷰이다.
private struct CellView: View {
var day: Int
var clicked: Bool = false
init(day: Int, clicked: Bool) {
self.day = day
self.clicked = clicked
}
var body: some View {
VStack {
RoundedRectangle(cornerRadius: 5)
.opacity(0)
.overlay(Text(String(day)))
.foregroundColor(.blue)
if clicked {
Text("Click")
.font(.caption)
.foregroundColor(.red)
}
}
.scaledToFit()
}
}

CellView에 cellDate: Date 속성 추가하기private struct CellView: View {
var day: Int
var clicked: Bool = false
var cellDate: Date
init(day: Int, clicked: Bool, cellDate: Date) {
self.day = day
self.clicked = clicked
self.cellDate = cellDate
}
// body 생략
}
calendarGridView 안에 CellView 호출 하는 부분에서 cellDate: date 인자 추가하기CellView(day: day, clicked: clicked, cellDate: date)
extension Date {
private func startOfDay() -> Date {
Calendar.current.startOfDay(for: self)
}
func isSameDate(date: Date) -> Bool {
self.startOfDay() == date.startOfDay()
}
}
Cellview 의 body 에 로직에 따라 뷰가 바뀌도록 if문과 함수 추가private struct CellView: View {
// 프로퍼티 및 생성자 생략
var body: some View {
ZStack {
// Date() 는 현재 시간의 Date를 반환한다.
// cellDate 와 Date() 이 같은날이면 Circle 표시
if cellDate.isSameDate(date: Date()) {
Circle()
}
RoundedRectangle(cornerRadius: 5)
.opacity(0)
.overlay(Text(String(day)))
.foregroundColor(.blue)
// clicked 로직 삭제함
}
.scaledToFit()
}
}

// MyModel.swift
import SwiftUI
import SwiftData
@Model
class MyModel {
var date: Date
var content: String
init(date: Date, content: String) {
self.date = date
self.content = content
}
}
modelContainer 넣어주기// MyCalendarApp.swift
import SwiftUI
import SwiftData
@main
struct MyCalendarApp: App {
var body: some Scene {
WindowGroup {
ContentView(models: sampleData)
.modelContainer(for: MyModel.self)
}
}
}
ContentView 의 프로퍼티에 @query 프로퍼티 래퍼로 모델 배열 만들기//ContentView.swift
import SwiftUI
import SwiftData
struct ContentView: View {
@Query var myModels: [MyModel] // @Query 로 모델 배열 넣음
var body: some View {
CalendarView(month: Date())
.padding()
// 기타 다른 뷰
RoundedRectangle(cornerRadius: 25.0)
.fill(Color.gray)
}
}
// SampleDate.swift
import SwiftData
import SwiftUI
@MainActor
let sampleData: ModelContainer = {
do {
let container = try ModelContainer(
for: MyModel.self,
configurations: ModelConfiguration(isStoredInMemoryOnly: true)
)
let modelContext = container.mainContext
if try modelContext.fetch(FetchDescriptor<MyModel>()).isEmpty {
mockData.forEach { container.mainContext.insert($0) }
}
return container
} catch {
fatalError("Failed to create container")
}
}()
var mockData = [
MyModel(date: Date().addingTimeInterval(-1233), content: "아침에 달리기 좋았음"),
MyModel(date: Date().addingTimeInterval(-252200), content: "오늘은 좀 피곤했음"),
MyModel(date: Date().addingTimeInterval(-482000), content: "괜찮은 운동이었음"),
MyModel(date: Date().addingTimeInterval(-614800), content: "끝까지 하기 힘들었음"),
MyModel(date: Date().addingTimeInterval(-834000), content: "평범한 하루"),
MyModel(date: Date().addingTimeInterval(-1396000), content: "개인 기록 경신"),
MyModel(date: Date().addingTimeInterval(-1792730), content: "일정한 속도 유지")
]
위와 같이 작성한 후
프리뷰를 다음과 같이 작성하면 modelContainer에 임시 데이터를 넣어서 프리뷰 할 수 있다.
#Preview {
ContentView()
.modelContainer(sampleData)
}
다음과 같이 상위 뷰에서 모델 배열 데이터가 CellView 까지 이동한다.

@Query 를 프로퍼티 래퍼로 붙혀준 프로퍼티는 값이 변경될 때 마다 연관된 뷰가 자동으로 업데이트 된다.
그러므로 클래스의 인스턴스의 배열인 myModels: [MyModel] 은 그냥 하위 뷰로 전달만 해주면 변경될때 마다 뷰가 자동으로 업데이트 된다.
CalendarView 에 myModels 를 추가한다.// ContentView 에서의 호출부
CalendarView(month: Date(), myModels: myModels)
struct CalendarView: View {
@State var month: Date
@State var offset: CGSize = CGSize()
@State var clickedDates: Set<Date> = []
var myModels: [MyModel]
// 이하 생략
myModels 에서 셀의 날짜에 알맞은 날짜의 데이터를 가져오는 함수 작성/// myModels 에서 otherDate 와 동일한 날인 데이터 하나 찾아서 반환. 없으면 nil 반환
func myModelByDate(otherDate: Date) -> MyModel? {
return myModels.first { myModel in
startOfDay(date: myModel.date) == startOfDay(date: otherDate)
}
}
/// Date 를 해당 날짜의 자정으로 바꿔줌 (시 분 초기화)
func startOfDay(date: Date) -> Date {
Calendar.current.startOfDay(for: date)
}
calendarGridView 내부 Cellview 호출 부분 위에 함수 사용하여 날짜에 맞는 모델 저장,Cellview 에 var myModel: MyModel? 프로퍼티 추가 및 호출부 수정myModel?이 nil 이 아닐 경우 빨간색 원 표시되도록 추가//..
let myModel = myModelByDate(otherDate: date)
CellView(day: day, clicked: clicked, cellDate: date, myModel: myModel)
//..
private struct CellView: View {
var day: Int
var clicked: Bool = false
var cellDate: Date
var myModel: MyModel?
init(day: Int, clicked: Bool, cellDate: Date, myModel: MyModel?) {
self.day = day
self.clicked = clicked
self.cellDate = cellDate
self.myModel = myModel
}
var body: some View {
ZStack {
if let myModel {
// myModel 에 데이터가 있을 경우 표시될 뷰
Circle()
.fill(Color.red)
}
if cellDate.isSameDate(date: Date()) {
Circle()
}
RoundedRectangle(cornerRadius: 5)
.opacity(0)
.overlay(Text(String(day)))
.foregroundColor(.blue)
}
.scaledToFit()
}
}
