Changing chart data in IOS - ios

So I am having trouble with some charting in Swift. I am using the following charting pod: https://github.com/aunnnn/RHLinePlot
I am using UIKit so I inserted a swiftUI view into my view controller. The charts work fine but whenever I change to a different timeline some data seems to be stuck from other timeperiods. But only at the 0 X axis value. You can see it on the left side, sticking out from the vertical Y axis. The first picture is fine but then the second shows some of the first chart, then the third shows of both:
Seems like some data is stuck in the view but I use new datasets every time.
Code goes as follows:
func setSwiftUi() {
let formView = SwiftUIStockChart(data: self.chartDataSwiftUI) { [weak self] newData in
self?.dismiss(animated: true) {
}
}
let childView = UIHostingController(rootView: formView)
addChild(childView)
childView.view.frame = chartView.bounds
chartView.addSubview(childView.view)
childView.didMove(toParent: self)
self.chartView.setNeedsDisplay()
self.chartViewAndAxis.setNeedsDisplay()
}
struct SwiftUIStockChart: View {
#State var currentIndex: Int? = nil
#State private var data = SymbolInfoStruct()
private var completion: (SymbolInfoStruct) -> Void
init(data: SymbolInfoStruct, completion: #escaping (SymbolInfoStruct) -> Void) {
self._data = State(initialValue: data)
self.completion = completion
}
func valueStickLabel(value: SymbolInfoStruct) -> some View {
let currentIndex = self.currentIndex ?? (value.chartDates.count - 1)
let formatter = DateFormatter()
if value.dateRange == "day"{
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
let date = formatter.date(from: value.chartDates[currentIndex])!
formatter.dateFormat = "HH:mm"
return Text(formatter.string(from: date))
} else if value.dateRange == "week" {
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
let date = formatter.date(from: value.chartDates[currentIndex])!
formatter.dateFormat = "EE"
return Text(formatter.string(from: date))
} else if value.dateRange == "month" {
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
let date = formatter.date(from: value.chartDates[currentIndex])!
formatter.dateFormat = "MMM d"
return Text(formatter.string(from: date))
} else if value.dateRange == "year" {
formatter.dateFormat = "yyyy-MM-dd"
let date = formatter.date(from: value.chartDates[currentIndex])!
formatter.dateFormat = "MMMM"
return Text(formatter.string(from: date))
} else {
formatter.dateFormat = "yyyy-MM-dd"
let date = formatter.date(from: value.chartDates[currentIndex])!
formatter.dateFormat = "YYYY"
return Text(formatter.string(from: date))
}
}
var isLaserModeOn = false
var showGlowingIndicator: Bool {
switch data.interval {
case "day":
return true // simulate today's data
default:
return false
}
}
func buildMovingPriceLabel(plotData: [CGFloat]) -> some View {
var currentIndex = 0
currentIndex = self.currentIndex ?? (plotData.count - 1)
return HStack(alignment: .bottom, spacing: 1) {
Text(" $")
MovingNumbersView(
number: Double(plotData[currentIndex]),
numberOfDecimalPlaces: 2,
verticalDigitSpacing: 0,
animationDuration: 0.3,
fixedWidth: 100) { (digit) in
Text(digit)
}
.mask(LinearGradient(
gradient: Gradient(stops: [
Gradient.Stop(color: .clear, location: 0),
Gradient.Stop(color: .orange, location: 0.2),
Gradient.Stop(color: .orange, location: 0.8),
Gradient.Stop(color: .orange, location: 1.0)]),
startPoint: .top,
endPoint: .bottom))
}
.rhFont(style: .title3, weight: .medium)
.foregroundColor(.orange.opacity(0.9))
}
var body: some View {
VStack (alignment: .leading, spacing: -10){
RHInteractiveLinePlot(
values: data.chartDataPoints,
occupyingRelativeWidth: 0.9,
showGlowingIndicator: true,
lineSegmentStartingIndices: Array(stride(from: 0, to: data.chartDataPoints.count, by: 5)),
didSelectValueAtIndex: { index in
self.currentIndex = index
},
customLatestValueIndicator: {
},
valueStickLabel: { value in
valueStickLabel(value: data)
})
.foregroundColor(.orange.opacity(0.9))
.font(.headline)
.environment(\.rhLinePlotConfig, RHLinePlotConfig.default.custom(f: { (c) in
c.useLaserLightLinePlotStyle = isLaserModeOn
}))
buildMovingPriceLabel(plotData: data.chartDataPoints)
}
}
}
Any idea how to resolve this?

Related

How to create a timeline like the iOS calendar app?

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

how to edit an existing reminder in an array

I have an array of reminders(reminder model) in a view model and want to be able to edit existing reminders specifically through and edit swipe action and then through the reminders detail screen. I tried adding a button with a sheet to my Homeview in a list and then tried updating the edited reminder in the reminders array to a property in my view model called existingRemindData by using an update function in the reminder model. this should work but the remind var created by the foreach loop in the home view doesn't keep its value when it is called in the sheet. In the home view under the edit swipe action when I assign homevm.existingRemindData = remind.data it is equal to whatever reminder I swipe on because I did a print statement to confirm but as soon as I try to use the remind var inside of the sheet for the edit action the remind var defaults to the first item in the reminder array in the view model which is obviously not right. how would I make it so it uses the correct reminder index value when trying to update the reminder or is there another way which I could implement this functionality. any help would be great and look in the code for clarification on what I talk about.
HomeView
'''
import SwiftUI
struct HomeView: View {
#StateObject private var homeVM = HomeViewModel()
#State var percent: Int = 1
#State var showDetailEditView = false
#State var showAddView = false
#State var dropDown = false
//#State var filter = false
var body: some View {
ZStack {
VStack {
List {
ForEach($homeVM.reminds) { $remind in
ReminderView(remind: $remind)
//.background(remind.theme.mainColor)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
.swipeActions(edge: .leading) {
Button(action: {
self.showDetailEditView.toggle()
homeVM.existingRemindData = remind.data
print(homeVM.existingRemindData.title)
}) {
Label("Edit", systemImage: "pencil")
}
}
.sheet(isPresented: $showDetailEditView) {
NavigationView {
ReminderEditView(data: $homeVM.existingRemindData)
.navigationTitle(homeVM.existingRemindData.title)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
self.showDetailEditView.toggle()
homeVM.existingRemindData = Reminder.Data()
}
}
ToolbarItem(placement: .confirmationAction) {
Button("Done") {
self.showDetailEditView.toggle()
print("\(remind.id) \(remind.title)")
print("\(homeVM.existingRemindData.id) \(homeVM.existingRemindData.title)")
remind.update(from: homeVM.existingRemindData)
homeVM.newRemindData = Reminder.Data()
}
}
}
.background(LinearGradient(gradient: Gradient(colors: [
Color(UIColor(red: 0.376, green: 0.627, blue: 0.420, alpha: 1)),
Color(UIColor(red: 0.722, green: 0.808, blue: 0.725, alpha: 1))
]), startPoint: .topLeading, endPoint: .bottomTrailing))
}
}
.swipeActions(allowsFullSwipe: true) {
Button (role: .destructive, action: {
homeVM.deleteReminder(remind: remind)
}) {
Label("Delete", systemImage: "trash.fill")
}
}
}
}
.onAppear(
perform: {
UITableView.appearance().backgroundColor = .clear
UITableViewCell.appearance().backgroundColor = .clear
})
'''
Reminder edit view
'''
import SwiftUI
extension Binding {
static func ??(lhs: Binding<Optional<Value>>, rhs: Value) -> Binding<Value> {
return Binding(get: {lhs.wrappedValue ?? rhs}, set: {lhs.wrappedValue = $0})
}
}
struct ReminderEditView: View {
#ObservedObject var editVM: EditViewModel
init(data: Binding<Reminder.Data>) {
editVM = EditViewModel(data: data)
}
var body: some View {
Form {
Section {
TextField("Title", text: $editVM.data.title)
TextField("Notes", text: $editVM.data.notes ?? "")
.frame(height: 100, alignment: .top)
}
Section {
Toggle(isOn: $editVM.data.hasDueDate, label: {
if editVM.data.hasDueDate {
VStack(alignment: .leading) {
Text("Date")
Text(editVM.data.hasDueDate ? editVM.data.formatDate(date: editVM.data.date!) : "\(editVM.data.formatDate(date: Date.now))")
.font(.caption)
.foregroundColor(.red)
}
} else {
Text("Date")
}
})
if editVM.data.hasDueDate {
DatePicker("Date", selection: $editVM.data.dueDate, in: Date()..., displayedComponents: .date)
.datePickerStyle(.graphical)
}
'''
Reminder model
'''
extension Reminder {
struct Data: Identifiable {
var title: String = ""
var notes: String?
var date: Date?
var time: Date?
var theme: Theme = .poppy
var iscomplete: Bool = false
var priority: RemindPriority = .None
let id: UUID = UUID()
var dueDate: Date {
get {
return date ?? Date()
}
set {
date = newValue
}
}
var dueTime: Date {
get {
return time ?? Date()
}
set {
time = newValue
}
}
func formatDate(date: Date) -> String {
let formatter = DateFormatter()
formatter.dateStyle = .full
formatter.timeStyle = .none
return formatter.string(from: date)
}
func formatTime(time: Date) -> String {
let formatter = DateFormatter()
formatter.dateStyle = .none
formatter.timeStyle = .short
return formatter.string(from: time)
}
var hasDueDate: Bool {
get {
date != nil
}
set {
if newValue == true {
date = Date()
}
else {
date = nil
hasDueTime = false
}
}
}
var hasDueTime: Bool {
get {
time != nil
}
set {
if newValue == true {
time = Date()
hasDueDate = true
}
else {
time = nil
}
}
}
}
var data: Data {
Data(title: title, notes: notes, date: date, time: time, theme: theme, iscomplete: iscomplete, priority: priority)
}
mutating func update(from data: Data) {
title = data.title
notes = data.notes
date = data.date
time = data.time
theme = data.theme
iscomplete = data.iscomplete
priority = data.priority
}
init(data: Data) {
title = data.title
notes = data.notes
date = data.date
time = data.time
theme = data.theme
iscomplete = data.iscomplete
priority = data.priority
id = data.id
}
}
'''
HomeViewModel(View model talked about)
'''
import Foundation
import SwiftUI
import Combine
class HomeViewModel: ObservableObject {
#Published var reminds: [Reminder] = Reminder.sampleReminders
#Published var newRemindData = Reminder.Data()
#Published var existingRemindData = Reminder.Data()
#Published var selectedRemind = Reminder(data: Reminder.Data())
#Published var compReminds: [Reminder] = []
private var cancellables = Set<AnyCancellable>()
/*init(reminds: [Reminder]) {
self.reminds = reminds
}*/
func newReminder() {
let newRemind = Reminder(data: newRemindData)
reminds.append(newRemind)
newRemindData = Reminder.Data()
}
func deleteReminder(remind: Reminder) {
Just(remind)
.delay(for: .seconds(0.25), scheduler: RunLoop.main)
.sink {remind in
if remind.iscomplete {
self.removeRemind(remind: remind)
}
if !remind.iscomplete {
self.removeRemind(remind: remind)
}
self.reminds.removeAll { $0.id == remind.id }
}
.store(in: &cancellables)
}
func appendRemind(complete: Reminder) {
compReminds.append(complete)
}
func removeRemind(remind: Reminder) {
compReminds.removeAll() { $0.id == remind.id }
}
func remindIndex() -> Int {
return reminds.firstIndex(where: {
$0.id == existingRemindData.id
}) ?? 1
}
We don't use view model objects in SwiftUI. Change EditViewModel class to be an EditConfig struct, declare it as #State var config: EditConfig? use it as the item in sheet(item:onDismiss:content:) instead of the bool version.
Also, your date formatting is not SwiftUI compatible, you won't benefit from the labels being updated automatically when the user changes their region settings. To fix that remove the formatDate code and instead supply the formatter to Text or simply use .date. If using a formatter object make sure you aren't initing a new one every time, e.g. store one inside an #State struct or a static var. in SwiftUI we must not init objects in a View's init and body, only value types.

Swift: Finding and plotting the hours in between two Dates

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.

SwiftUI: ObservableObject with a computed property

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

Start and End date of month in SwiftUI

How to get startDateOfMonth and endDateOfMonth based on selected date in SwiftUI?
I have found some answers for Swift (DateComponents), but couldn't make it work with SwiftUI.
Why I need this: I am going to use dynamic filters using predicate to filter all the data in the currently selected month (using custom control to switch months). But first I need to get the start and end dates per selected month.
EXAMPLE code:
ContentView.swift
import SwiftUI
struct ContentView: View {
#State var currentDate = Date()
// How to make startDateOfMonth and endDateOfMonth dependent on selected month?
#State private var startDateOfMonth = "1st January"
#State private var endDateOfMonth = "31st January"
var body: some View {
VStack {
DateView(date: $currentDate)
Text("\(currentDate)")
Text(startDateOfMonth)
Text(endDateOfMonth)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
DateView.swift
import SwiftUI
struct DateView: View {
static let dateFormat: DateFormatter = {
let formatter = DateFormatter()
formatter.setLocalizedDateFormatFromTemplate("yyyy MMMM")
return formatter
}()
#Binding var date : Date
var body: some View {
HStack {
Image(systemName: "chevron.left")
.padding()
.onTapGesture {
print("Month -1")
self.changeDateBy(-1)
}
Spacer()
Text("\(date, formatter: Self.dateFormat)")
Spacer()
Image(systemName: "chevron.right")
.padding()
.onTapGesture {
print("Month +1")
self.changeDateBy(1)
}
}
.padding(EdgeInsets(top: 5, leading: 10, bottom: 5, trailing: 10))
.background(Color.yellow)
}
func changeDateBy(_ months: Int) {
if let date = Calendar.current.date(byAdding: .month, value: months, to: date) {
self.date = date
}
}
}
struct DateView_Previews: PreviewProvider {
struct BindingTestHolder: View {
#State var testItem: Date = Date()
var body: some View {
DateView(date: $testItem)
}
}
static var previews: some View {
BindingTestHolder()
}
}
I managed to solve it by the following implementation of ContentView
#State var currentDate = Date()
private var startDateOfMonth: String {
let components = Calendar.current.dateComponents([.year, .month], from: currentDate)
let startOfMonth = Calendar.current.date(from: components)!
return format(date: startOfMonth)
}
private var endDateOfMonth: String {
var components = Calendar.current.dateComponents([.year, .month], from: currentDate)
components.month = (components.month ?? 0) + 1
components.hour = (components.hour ?? 0) - 1
let endOfMonth = Calendar.current.date(from: components)!
return format(date: endOfMonth)
}
var body: some View {
VStack {
DateView(date: $currentDate)
Text("\(currentDate)")
Text(startDateOfMonth)
Text(endDateOfMonth)
}
}
private func format(date: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
return dateFormatter.string(from: date)
}
Because currentDate is changed by DateView through Binding the body computed property will be invoked thus startDateOfMonth and endDateOfMonth computed properties will return the updated values.

Resources