I’m trying to make a scheme in which I have 7 fixed days above, the current day is always the middle one, but I’d like to make the other days clickable too, so that when you click on the day the card below changes with the corresponding descriptions, as if each one were linked to a specific day. I managed to set the days, but I can’t figure out if I need to use a state or something similar to make them clickable.
At first I tried using state and manipulating it in zstack, but it didn’t work.
import SwiftUI
struct DayPlanView: View {
@State var monthString: String = "Not Set"
let calendar = Calendar.current
var body: some View {
let dates = getWeek()
VStack {
// MARK: Change Date() to some identifier of selected date.
HStack {
Text("(getDayNumber(date: Date()))")
.font(.title2)
.fontWeight(.semibold)
Text("of")
.font(.title2)
.fontWeight(.semibold)
Text(getMonth(date: Date()))
.font(.title2)
.fontWeight(.semibold)
}
HStack {
ForEach(dates, id: .self) { day in
VStack {
let dayNumber = getDayNumber(date: day)
if dayNumber == getDayNumber(date: Date()) {
ZStack {
Circle()
.fill(.blue)
.scaleEffect(1.3)
Text("(getDayNumber(date: day))")
.font(.title)
.fontWeight(.semibold)
.foregroundColor(.white)
}
} else {
Text("(getDayNumber(date: day))")
.font(.title)
}
}
.frame(width: getWidth() / 8, height: getHeight() / 20)
.padding(.horizontal, -3)
}
}
.frame(width: getWidth())
Spacer()
}
.padding(.vertical, getHeight() / 20)
}
func getMonth(date: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "LLLL"
return dateFormatter.string(from: date)
}
func getDayShort(date: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "E"
return dateFormatter.string(from: date)
}
func getDayNumber(date: Date) -> Int {
let calendar = Calendar.current
let components = calendar.dateComponents([.day], from: date)
return components.day ?? 0
}
func getWeek() -> [Date] {
let currentDate = Date()
let calendar = Calendar.current
let dayOfWeek = calendar.component(.weekday, from: currentDate)
let range = calendar.range(of: .day, in: .month, for: currentDate)!
let daysMonth = (range.lowerBound - 1 ..< range.lowerBound + 6)
.compactMap { calendar.date(byAdding: .day, value: $0 - dayOfWeek, to: currentDate) }
return daysMonth
}
}
struct DayPlanView_Previews: PreviewProvider {
static var previews: some View {
DayPlanView()
}
}
2
Answers
I would go with a binding. From copying your code and modifying it you can check the following.
It is not perfect but it should get you on the right track.
Yes, you can use an
@State
in your top view which is passed to a@Binding
in your day selector view.The first thing I did was create a day selector view, which accepts an array of
Dates
to show and aselectedDate
binding –An
onTapGesture
is used to assign the selected date to the@Binding
propertyI created a simple top-level view and
DayContentView
to show how to use it:I also used a handy extension on
Date
(from this answer to find the first day of the week for a givenDate
:With a bit of a tweak you can use a
matchedGeometryEffect
to get a nice animation of the blue selection circle: