Using iOS16.3, XCode14.2, Swift5.7.2,
I try to create a nice looking Date-Picker where you can tap on a starting date and then tap on an ending date. And the entire date-range shall be highlighted.
Since this does not seem to exist even with the new MultiDatePicker under iOS16 and SwiftUI, I wonder if there is a good library out there to fulfill that task.
Moreover, I would like to add presets of DateRanges, such as this week, last week, this month, last month etc.
Here is what I tried in Code to achieve the week preset (see code below).
The problem is that once the user taps into the dates, then the preset-button no longer works.
Any idea how to achieve all this ?
import SwiftUI
struct CalendarView: View {
#Environment(\.calendar) var calendar
#Environment(\.timeZone) var timeZone
var bounds: Range<Date> {
let start = calendar.date(from: DateComponents(
timeZone: timeZone, year: 2022, month: 6, day: 1))!
let end = calendar.date(from: DateComponents(
timeZone: timeZone, year: 2023, month: 6, day: 30))!
return start ..< end
}
#State var dates: Set<DateComponents> = []
var body: some View {
VStack {
MultiDatePicker("Dates Available", selection: $dates, in: bounds)
HStack {
Button {
dates = []
let calendar = Calendar.current
let startDateComponents = DateComponents(year: 2023, month: 1, day: 15)
let endDateComponents = DateComponents(year: 2023, month: 2, day: 10)
let startDate = calendar.date(from: startDateComponents)!
let endDate = calendar.date(from: endDateComponents)!
var currentDate = startDate
while currentDate <= endDate {
dates.insert(calendar.dateComponents([.year, .month, .day], from: currentDate))
currentDate = calendar.date(byAdding: .day, value: 1, to: currentDate)!
}
} label: {
Text("week")
}
}
Spacer()
.frame(height: 100)
}
}
}
#workingdog:
Here is the screenshot when trying to add RKCalendar with SPM:
Ah and by the way: as an improvement, I suggest the following small changes to your horizontalView.
If you are using isContinuous = false and isVertical = false then I suggest the following replacements:
replace:
ScrollView(.horizontal) by ScrollView(.horizontal, showsIndicators: false)
and 2. replace:
HStack by HStack(alignment: .center, spacing: 0)
This makes the PageView fit into the center (and is not off by some strange offset once you swipe).
public var horizontalView: some View {
GeometryReader { geometry in
// change-suggestions here.....
ScrollView(.horizontal, showsIndicators: false) {
// and change-suggestions here.....
HStack(alignment: .center, spacing: 0) {
ForEach(pages) { page in
page.frame(width: geometry.size.width, height: geometry.size.height)
}
}
}
.content.offset(x: isGestureActive ? offset : -geometry.size.width * CGFloat(index))
.frame(width: geometry.size.width, height: nil, alignment: .leading)
.simultaneousGesture(DragGesture()
.onChanged({ value in
isGestureActive = true
offset = value.translation.width - geometry.size.width * CGFloat(index)
})
.onEnded({ value in
if abs(value.predictedEndTranslation.width) >= geometry.size.width / 2 {
var nextIndex: Int = (value.predictedEndTranslation.width < 0) ? 1 : -1
nextIndex += index
index = nextIndex.keepIndexInRange(min: 0, max: pages.endIndex - 1)
}
withAnimation { offset = -geometry.size.width * CGFloat(index) }
DispatchQueue.main.async { self.isGestureActive = false }
})
)
}.onAppear(perform: { index = todayIndex() })
}
Related
I am fairly new in Swift(UI) and I am just trying to fiddle around and create myself a timeline like in the Calendar iOS app with the hours on the left and a divider per hour plus a red line where the current time is at.
Like this
I've seem to accomplish the first (hour and divider), but I am stuck on drawing the current time and position it correctly based on time and spacing.
Like this
Is anyone able to help me or guide me to the right direction? Thank you in advance!
My current code:
struct ContentView: View {
let dateFormatter = DateFormatter()
init(){
dateFormatter.dateFormat = "HH:mm"
}
var sortedTimeFrom: [Date] = {
let today = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: Date())!
var sortedTime: [Date] = []
var calender = Calendar(identifier: .iso8601)
calender.locale = Locale(identifier: "en_US_POSIX")
let currentHour = calender.component(.hour, from: today)
(0...(24)).forEach {
guard let newDate = calender.date(byAdding: .hour, value: $0, to: today),
calender.component(.hour, from: newDate) <= 23 else {
return
}
//convert date into desired format
sortedTime.append(newDate)
}
return sortedTime
}()
var body: some View {
ScrollView(.vertical)
{
ForEach(sortedTimeFrom, id: \.self) { date in
VStack(alignment: .leading){
HStack {
Spacer()
Text(date, formatter: dateFormatter)
VStack{
Color.gray.frame(height: 1 / UIScreen.main.scale)
}
}
}.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
}
You'll have to check every row for when the hour is equal to now, then calculate the offset based on the minutes & height of each row:
struct ContentView: View {
let dateFormatter = DateFormatter()
init(){
dateFormatter.dateFormat = "HH:mm"
}
var sortedTimeFrom: [Date] = {
let today = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: Date())!
var sortedTime: [Date] = []
var calender = Calendar(identifier: .iso8601)
calender.locale = Locale(identifier: "en_US_POSIX")
let currentHour = calender.component(.hour, from: today)
(0...(24)).forEach {
guard let newDate = calender.date(byAdding: .hour, value: $0, to: today),
calender.component(.hour, from: newDate) <= 23 else {
return
}
//convert date into desired format
sortedTime.append(newDate)
}
return sortedTime
}()
var currentDate: (Int, Double) {
let calendar = Calendar(identifier: .iso8601)
let hour = calendar.component(.hour, from: Date())
let minutes: Double = Double(calendar.component(.month, from: Date()))/60
return (hour, minutes)
}
var body: some View {
ScrollView(.vertical) {
VStack(spacing: 0) {
ForEach(sortedTimeFrom, id: \.self) { date in
VStack(alignment: .leading){
HStack {
Spacer()
Text(date, formatter: dateFormatter)
VStack{
Color.gray.frame(height: 1 / UIScreen.main.scale)
}
}
}.frame(maxWidth: .infinity, alignment: .leading)
.frame(height: 30)
.overlay(
VStack {
if Calendar(identifier: .iso8601).component(.hour, from: date) == currentDate.0 {
HStack {
Spacer()
Text(Date(), formatter: dateFormatter)
.foregroundColor(.red)
Color.red
.frame(height: 1 / UIScreen.main.scale)
}.offset(y: 30 * currentDate.1)
}
}
)
}
}
}
}
}
I am trying to create a UI like the below. I have a startDate and endDate (e.g. 1:55am and 11:35am). How can I proportionally (using Geometry reader) find each hour in between two dates and plot them as is done here? I am thinking at some point I need to use seconds or timeIntervalSince perhaps, but can't quite get my head around how best to go about it?
my code so far which gets the hours correctly, but I need to space them out proportionally according to their times:
What my code looks like:
struct AsleepTimeView: View {
#EnvironmentObject var dataStore: DataStore
static let sleepTimeFormat: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .none
formatter.timeStyle = .short
return formatter
}()
var body: some View {
Color.clear.overlay(
GeometryReader { geometry in
if let mostRecentSleepDay = dataStore.pastSevenSleepDays?.last, let firstSleepSpan = mostRecentSleepDay.sleepSpans.first, let lastSleepSpan = mostRecentSleepDay.sleepSpans.last {
VStack(alignment: .center) {
HStack {
Text("\(firstSleepSpan.startDate, formatter: Self.sleepTimeFormat) ")
.font(.footnote)
Spacer()
Text("\(lastSleepSpan.endDate, formatter: Self.sleepTimeFormat)")
.font(.footnote)
}
HStack(spacing: 3) {
ForEach(dataStore.sleepOrAwakeSpans) { sleepOrAwakeSpan in
RoundedRectangle(cornerRadius: 5)
.frame(width: getWidthForRoundedRectangle(proxy: geometry, spacing: 3, seconds: sleepOrAwakeSpan.seconds, sleepOrAwakeSpans: athlyticDataStore.sleepOrAwakeSpans), height: 10)
.foregroundColor(sleepOrAwakeSpan.asleep == false ? TrackerConstants.scaleLevel5Color : TrackerConstants.scaleLevel8Color)
}
}
HStack {
ForEach(getHoursBetweenTwoDates(startDate: firstSleepSpan.startDate, endDate: lastSleepSpan.endDate).map { Calendar.current.component(.hour, from: $0) }, id: \.self) { hour in
HStack {
Text("\(hour)")
.font(.footnote)
Spacer()
}
}
}
HStack {
HStack {
Circle()
.foregroundColor(TrackerConstants.scaleLevel8Color)
.frame(width: 10, height: 10)
Text("Asleep")
.font(.footnote)
}
HStack {
Circle()
.foregroundColor(TrackerConstants.scaleLevel5Color)
.frame(width: 10, height: 10)
Text("Awake")
.font(.footnote)
}
Spacer()
}
}
}
}) // end of overlay
}
//helper
private func getWidthForRoundedRectangle(proxy: GeometryProxy, spacing: Int, seconds: TimeInterval, sleepOrAwakeSpans: [SleepOrAwakeSpan]) -> CGFloat {
let totalSpace = (sleepOrAwakeSpans.count - 1) * spacing
let totalSleepTime = sleepOrAwakeSpans.map { $0.endTime.timeIntervalSince($0.startTime) }.reduce(0, +)
guard totalSleepTime > 0 else { return 0}
let width = (proxy.size.width - CGFloat(totalSpace)) * CGFloat(seconds / totalSleepTime)
return width
}
func datesRange(from: Date, to: Date, component: Calendar.Component) -> [Date] {
// in case of the "from" date is more than "to" date,
// it should returns an empty array:
if from > to { return [Date]() }
var tempDate = from
var array = [tempDate]
while tempDate < to {
tempDate = Calendar.current.date(byAdding: component, value: 1, to: tempDate)!
array.append(tempDate)
}
return array
}
func getHoursBetweenTwoDates(startDate: Date, endDate: Date) -> [Date] {
var finalArrayOfHours = [Date]()
guard endDate > startDate else { return finalArrayOfHours }
let arrayOfHours = datesRange(from: startDate, to: endDate, component: .hour)
for date in arrayOfHours {
let hour = date.nearestHour()
finalArrayOfHours.append(hour)
}
return finalArrayOfHours
}
}
extension Date {
func nearestHour() -> Date {
return Date(timeIntervalSinceReferenceDate:
(timeIntervalSinceReferenceDate / 3600.0).rounded(.toNearestOrEven) * 3600.0)
}
}
There are a whole suite of methods in the Calendar class to help with what you're trying to do.
Off the top of my head, Id say you'd do something like the following:
In a loop, use date(byAdding:to:wrappingComponents:) to add an hour at a time to your startDate. If the result is ≤ endDate, use component(_:from:) to get the hour of the newly calculated date.
I built a simple horizontal calendar with SwiftUI and it works fine. But I want that it automatically scroll to the current date at app launch. How can I do this? I assume I should use GeometryReader to get a position of frame with current date and set an offset based on it but ScrollView doesn't have a content offset modifier. I know that in iOS 14 we now have ScrollViewReader but what about iOS 13?
struct MainCalendar: View {
#Environment(\.calendar) var calendar
#State private var month = Date()
#State private var selectedDate: Date = Date()
var body: some View {
VStack(spacing: 30) {
MonthAndYearView(date: $month)
WeekDaysView(date: month)
}
.padding(.vertical)
}
}
struct MonthAndYearView: View {
#Environment(\.calendar) var calendar
private let formatter = DateFormatter.monthAndYear
#Binding var date: Date
var body: some View {
HStack(spacing: 20) {
Button(action: {
self.date = calendar.date(byAdding: .month, value: -1, to: date)!
}, label: {
Image(systemName: "chevron.left")
})
Text(formatter.string(from: date))
.font(.system(size: 18, weight: .semibold))
Button(action: {
self.date = calendar.date(byAdding: .month, value: 1, to: date)!
}, label: {
Image(systemName: "chevron.right")
})
}
}
}
struct WeekDaysView: View {
#Environment(\.calendar) var calenar
let date: Date
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 30) {
ForEach(days, id: \.self) { day in
VStack(spacing: 20) {
Text(daySymbol(date: day))
.font(.system(size: 14, weight: .regular))
if calenar.isDateInToday(day) {
Text("\(dayNumber(date: day))")
.foregroundColor(Color.white)
.background(Circle().foregroundColor(.blue)
.frame(width: 40, height: 40))
} else {
Text("\(dayNumber(date: day))")
}
}
}
}
.padding([.horizontal])
.padding(.bottom, 15)
}
}
private func dayNumber(date: Date) -> String {
let formatter = DateFormatter.dayNumber
let dayNumber = formatter.string(from: date)
return dayNumber
}
private var days: [Date] {
guard let interval = calenar.dateInterval(of: .month, for: date) else { return [] }
return calenar.generateDates(inside: interval, matching: DateComponents(hour: 0, minute: 0, second: 0))
}
private func daySymbol(date: Date) -> String {
let dayFormatter = DateFormatter.weekDay
let weekDay = dayFormatter.string(from: date)
return weekDay
}
}
This library https://github.com/Amzd/ScrollViewProxy is solved my problem.
struct WeekDaysView: View {
let date: Date
#Environment(\.calendar) var calendar
#State private var scrollTarget: Int? = nil
var body: some View {
ScrollViewReader { proxy in
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 30) {
ForEach(Array(zip(days.indices, days)), id: \.0) { index, day in
VStack(spacing: 20) {
Text(daySymbol(date: day))
.font(.system(size: 14, weight: .regular))
.scrollId(index)
if calendar.isDateInToday(day) {
Text("\(dayNumber(date: day))")
.foregroundColor(Color.white)
.background(Circle().foregroundColor(.blue)
.frame(width: 40, height: 40))
.onAppear {
scrollTarget = index
}
} else {
Text("\(dayNumber(date: day))")
}
}
}
}
.padding([.horizontal])
.padding(.bottom, 15)
}
.onAppear {
DispatchQueue.main.async {
withAnimation {
proxy.scrollTo(scrollTarget, alignment: .center, animated: true)
}
}
}
}
}
The expected result is the main menu to handoff to the other .swift file / view controller. I do not want a back button or any other functions only top hand off to a separate part of the app.
This is the Main Screen currently.
import Foundation
import SwiftUI
struct FileView: View {
//Telling aray i want items 175 wide at least
let layout = [
GridItem(.adaptive(minimum: 175))
]
#State var date = Date()
var body: some View {
///Top Bar layout
ZStack {
HStack {
Button(action: {
}, label: {
Image("Home")
.resizable()
.frame(width: CGFloat(30), height: CGFloat(30), alignment: .leading)
}).padding(.leading, CGFloat(20))
.padding(.top, 15)
Spacer()
}
HStack {
Text("DAILYMENU")
.frame(alignment: .center)
.font(.title3)
.foregroundColor(Color("Menu"))
.multilineTextAlignment(.center)
.padding(.top, 15)
}
}
///Setup DAILYMENU FEED LINE
HStack{
Text("DAILYMENU")
.padding(.leading, 20.0)
.padding(.top, 100.0)
.font(.largeTitle)
.foregroundColor(Color("Menu"))
Spacer()
}
///Setup Welcome messages
HStack{
Text(greeting())
.padding(.bottom, 20)
.padding(.leading, 20.0)
.font(.subheadline)
.foregroundColor(Color("Menu"))
Spacer()
Text("...")
.foregroundColor(/*#START_MENU_TOKEN#*/.blue/*#END_MENU_TOKEN#*/)
.font(.largeTitle)
.padding(.trailing, 20.0)
.padding(.bottom, 20)
}
/// Setup navigation home
ScrollView {
LazyVGrid(columns: layout, spacing: 5) {
Button(action: {
print("Button action")
}) {
Image("DAILYMENU")
}
.padding(.vertical, 2.5)
.padding(.horizontal, 2.5)
Button(action: {
print("Button action")
}) {
Image("DAILYMENU")
}
.padding(.vertical, 2.5)
.padding(.horizontal, 2.5)
Button(action: {
print("Button action")
}) {
Image("DAILYMENU")
}
.padding(.vertical, 2.5)
.padding(.horizontal, 2.5)
Button(action: {
print("Button action")
}) {
Image("DAILYMENU")
}
Button(action: {
print("Button action")
}) {
Image("STOREMENU")
}
.padding(.vertical, 2.5)
.padding(.horizontal, 2.5)
Button(action: {
print("Button action")
}) {
Image("DAILYMENU")
}
//Add correct images Direction and Contact us
Button(action: {
print("Button action")
}) {
Image("DAILYMENU")
}
Button(action: {
print("Button action")
}) {
Image("DAILYMENU")
}
.padding(.vertical, 2.5)
.padding(.horizontal, 2.5)
}
}
.padding(.horizontal, 2.5)
.padding(.vertical, 2.5)
}
var timeFormat: DateFormatter{
let formatter = DateFormatter(); formatter.dateFormat = "hh:mm:ss a"
return formatter
}
func timeString(date: Date) -> String {
let time = timeFormat.string(from: date)
return time
}
var updateTimer: Timer{
Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {_ in self.date = Date() })
}
func greeting() -> String{
var greet = ""
greet = ""
//sleep is important
let sleepMidNight = Calendar.current.date(bySettingHour: 1, minute: 01, second: 00, of: date)!
let sleepNightEnd = Calendar.current.date(bySettingHour: 4, minute: 00, second: 00, of: date)!
//good morning
let morningStart = Calendar.current.date(bySettingHour: 9, minute: 00, second: 0, of: date)
let morningEnd = Calendar.current.date(bySettingHour: 11, minute: 00, second: 00, of: date)!
//good afternoon
let noonStart = Calendar.current.date(bySettingHour: 11, minute: 01, second: 00, of: date)!
let noonEnd = Calendar.current.date(bySettingHour: 17, minute: 00, second: 00, of: date)!
//good evening
let eveStart = Calendar.current.date(bySettingHour: 17, minute: 01, second: 00, of: date)!
let eveEnd = Calendar.current.date(bySettingHour: 22, minute: 00, second: 00, of: date)!
//up late
let nightStart = Calendar.current.date(bySettingHour: 22, minute: 01, second: 00, of: date)!
let midNight24 = Calendar.current.date(bySettingHour: 01, minute: 00, second: 00, of: date)!
//early riser
let earlyRiser = Calendar.current.date(bySettingHour: 01, minute: 01, second: 00, of: date)
let earlyRiserEnd = Calendar.current.date(bySettingHour: 06, minute: 00, second: 00, of: date)
//way to start your day
let wayToStart = Calendar.current.date(bySettingHour: 06, minute: 01, second: 00, of: date)
let wayToStartEnd = Calendar.current.date(bySettingHour: 09, minute: 59, second: 59, of: date)
if((date >= sleepMidNight)
&& (sleepNightEnd >= date)){
greet = "Sleep is important!"
}else if ((date >= morningStart!) && (morningEnd >= date)){
greet = "Good Morning!"
//would not run without ! after morningStart don't know why. Doesn't need it for others?
}else if ((date >= noonStart) && (noonEnd >= date)){
greet = "Good Afternoon!"
}else if ((date >= eveStart) && (eveEnd >= date)){
greet = "Good Evening!"
}else if ((date >= nightStart) && (midNight24 >= date)){
greet = "You're up late!"
}else if ((date >= earlyRiser!) && (earlyRiserEnd! >= date)){
greet = "Early Riser!"
// had to do the ! on both Riser and End
}else if ((date >= wayToStart!) && (wayToStartEnd! >= date)){
greet = "What a way to start your day"
// had to do the ! on both here too
}
return greet
}
}
struct File_Previews: PreviewProvider {
static var previews: some View {
FileView()
}
}
This is the second view the lets say first button needs to link to.
import SwiftUI
import Combine
struct VideoList: View {
#ObservedObject private(set) var viewModel: ViewModel
#State private var isRefreshing = false
var body: some View {
NavigationView {
List(viewModel.videos.sorted { $0.id > $1.id}, id: \.id) { video in
NavigationLink(
destination: VideoDetails(viewModel: VideoDetails.ViewModel(video: video))) {
VideoRow(video: video)
}
}
.onPullToRefresh(isRefreshing: $isRefreshing, perform: {
self.viewModel.fetchVideos()
})
.onReceive(viewModel.$videos, perform: { _ in
self.isRefreshing = false
})
.navigationBarTitle(viewModel.navigationBarTitle)
}
.onAppear(perform: viewModel.fetchVideos)
}
}
#if DEBUG
struct VideoList_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
VideoList(viewModel: .init())
}
}
}
#endif
UIKit made this easy as you only needed to setup a UB outlet and link it to the view... swiftUI seems to make it impossible to do this.i keep seeing examples to where I have to implement it in the same .swift however that breaks the view model connection in the correct .swift file which means the code becomes messy and unsorted.
thus either I'm implementing this wrong or something is causing conflicts with this style of implemention (example provided by #pawello2222).
struct ContentView: View {
#State private var isLinkActive = false
var body: some View {
NavigationView {
VStack {
Button("Go to...") {
// activate link programatically
self.isLinkActive = true
}
}
// hide navigation bar
.navigationBarTitle("", displayMode: .inline)
.navigationBarHidden(true)
.background(
NavigationLink(destination: SecondView(), isActive: $isLinkActive) {
EmptyView()
}
.hidden()
)
}
}
}
struct SecondView: View {
#Environment(\.presentationMode) private var presentationMode
var body: some View {
Button("Go back") {
self.presentationMode.wrappedValue.dismiss()
}
.navigationBarTitle("", displayMode: .inline)
.navigationBarHidden(true)
}
}
I have a simple View with a custom month selector. Each time you click chevron left or right,
Inside DateView I had two private vars: startDateOfMonth2: Date and endDateOfMonth2: Date. But they were only available inside DateView.
QUESTION
How to make those two variables available in other views?
I have tried to add them as #Published vars, but I am getting an error:
Property wrapper cannot be applied to a computed property
I have found just a few similar questions, but I cannot manage to use answers from them with my code.
import SwiftUI
class SelectedDate: ObservableObject {
#Published var selectedMonth: Date = Date()
#Published var startDateOfMonth2: Date {
let components = Calendar.current.dateComponents([.year, .month], from: selectedMonth)
let startOfMonth = Calendar.current.date(from: components)!
return startOfMonth
}
#Published var endDateOfMonth2: Date {
var components = Calendar.current.dateComponents([.year, .month], from: selectedMonth)
components.month = (components.month ?? 0) + 1
let endOfMonth = Calendar.current.date(from: components)!
return endOfMonth
}
}
struct DateView: View {
#EnvironmentObject var selectedDate: SelectedDate
static let dateFormat: DateFormatter = {
let formatter = DateFormatter()
formatter.setLocalizedDateFormatFromTemplate("yyyy MMMM")
return formatter
}()
var body: some View {
HStack {
Image(systemName: "chevron.left")
.frame(width: 50, height: 50)
.contentShape(Rectangle())
.onTapGesture {
self.changeMonthBy(-1)
}
Spacer()
Text("\(selectedDate.selectedMonth, formatter: Self.dateFormat)")
Spacer()
Image(systemName: "chevron.right")
.frame(width: 50, height: 50)
.contentShape(Rectangle())
.onTapGesture {
self.changeMonthBy(1)
}
}
.padding(EdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5))
.background(Color.yellow)
}
func changeMonthBy(_ months: Int) {
if let selectedMonth = Calendar.current.date(byAdding: .month, value: months, to: selectedDate.selectedMonth) {
self.selectedDate.selectedMonth = selectedMonth
}
}
}
No need to declare your computed values as Published, as they are depending on a Published value. When the published value changed, they get recalculated.
class SelectedDate: ObservableObject {
#Published var selectedMonth: Date = Date()
var startDateOfMonth2: Date {
let components = Calendar.current.dateComponents([.year, .month], from: self.selectedMonth)
let startOfMonth = Calendar.current.date(from: components)!
print(startOfMonth)
return startOfMonth
}
var endDateOfMonth2: Date {
var components = Calendar.current.dateComponents([.year, .month], from: self.selectedMonth)
components.month = (components.month ?? 0) + 1
let endOfMonth = Calendar.current.date(from: components)!
print(endOfMonth)
return endOfMonth
}
}
When you print endDateOfMonth2 on click, you will see that it is changing.
computed property is function substantially。
it can't stored value,it just computed value from other variable or property in getter and update value back to things it computed in setter。
think about your property‘s character carefully。
mostly,you can directly readwrite computed property。
just remember it is function core and property skin