CalendarView: Part 1: From Day to Month
A few days ago, I decided to implement own version of CalendarView where user can easily select a day, or see some events in the selected month. Because it’s a little bit complicated element I decide to divide it into a few parts. And this post is about Views from a Day to the Month. And of course, it is 100% SwiftUI.
One day
Everything should start from the smallest piece of component, from the smallest issue, in my case it is DayView
.
struct DayView: View {
var body: some View {
ZStack {
Text("10")
}
}
To define attributes, I created Day
state which contains all values which will be available to set on DayView
.
struct Day {
let number: Int
var isHeighlighted = false
var isSelected = false
var isEnabled = true
var isCurrent = false
var onSelect: (() -> Void) = {}
}
Now I can extend DayView
with all attributes and create a view with any Day
configuration.
struct DayView: View {
let day: Day
var body: some View {
ZStack {
Text("\(day.number)")
.font(.system(size: 14))
.foregroundColor(day.isCurrent ? .blue : .none)
.frame(width: 30, height: 30)
.overlay(
Circle()
.stroke(
Color.blue,
lineWidth: day.isSelected ? 1 : 0
)
)
if day.isHeighlighted {
Circle()
.frame(width: 4, height: 4)
.offset(x: 0, y: 12)
.foregroundColor(.blue)
}
}
.opacity(day.isEnabled ? 1 : 0.25)
.padding(4)
.onTapGesture(perform: day.onSelect)
}
}
Here are examples of few days configurations.
DayView(day: Day(number: 10))
Default
DayView(day: Day(number: 22, isHeighlighted: true))
Heighlighted
DayView(day: Day(number: 22, isSelected: true))
Selected
DayView(day: Day(number: 23, isCurrent: true))
Current
DayView(day: Day(number: 31, isEnabled: false))
Disabled
Seven days in a row is a Week
When we have one day, we can compose one aweek to a specific WeekView
component. I create a new Week
states to handle days
and unique identifier.
struct Week: Identifiable {
let id = UUID()
let days: [Day]
}
Now WeekView
shows all days in a horizontal stack.
struct WeekView: View {
let week: Week
var body: some View {
HStack {
ForEach(week.days, id: \.number) {
DayView(day: $0)
.frame(maxWidth: .infinity)
}
}
}
}
A few weeks in a row is a Month
Similar to with week, we can now compose MonthView
with few a WeekView
s in VStack
.
struct MonthView: View {
let weeks: [Week]
var body: some View {
VStack {
ForEach(weeks, content: WeekView.init(week:))
}
}
}
In the end
Every time when you see something new from your app designer and on first look is a little bit complicated, you still can make a deep breath and separate it into small components. Then every complicated a View, for example, CalendarView is more simple.
And your code is more readable 😉.