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 Default

DayView(day: Day(number: 22, isHeighlighted: true))

heighlighted Heighlighted

DayView(day: Day(number: 22, isSelected: true))

selected Selected

DayView(day: Day(number: 23, isCurrent: true))

current Current

DayView(day: Day(number: 31, isEnabled: false))

disabled 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)
            }
        }
    }
}

week

A few weeks in a row is a Month

Similar to with week, we can now compose MonthView with few a WeekViews in VStack.

struct MonthView: View {
    let weeks: [Week]

    var body: some View {
        VStack {
            ForEach(weeks, content: WeekView.init(week:))
        }
    }
}

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 😉.

week