Content offset in ScrollView SwiftUI - ios

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

Related

Date-range-presets with SwiftUI's new MultiDatePicker

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

Wrong selection with LazyHGrid SwiftUI

Hi all I have a problem with selecting cells in a LazyHGrid using SwiftUI
In the TimePickerGridView.swift I create a horizontal list with times that the user can select. To manage cell selection I use #State private var selectedItem: Int = 0
Everything seems to work but I have some problems when I save the user selection to create a date which contains the selected times
When the user selects a cell, it immediately updates a date by setting the hours and minutes.
The date being modified is #Binding var date: Date, this date refers to a RiservationViewModel.swift which is located in the RiservationView structure
struct ReservationsView: View {
#StateObject var viewModels = ReservationViewModel()
var body: some View {
VStack {
TimePickerGridView(date: $viewModels.selectedDate)
}
}
}
Now the problem is that when the user selects the time and creates the date the LazyHGrid continuously loses the selection and has to select the same cell more than once to get the correct selection again ...
At this point the variable date is observed because it can change thanks to another view that contains a calendar where the user selects the day.
How can I solve this problem? where is my error?
private extension Date {
var hour: Int { Calendar.current.component(.hour, from: self) }
var minute: Int { Calendar.current.component(.minute, from: self) }
var nextReservationTime: TimePicker {
let nextHalfHour = self.minute < 30 ? self.hour : (self.hour + 1) % 24
let nextHalfMinute = self.minute < 30 ? 30 : 0
return TimePicker(hour: nextHalfHour, minute: nextHalfMinute)
}
}
struct TimePickerGridView: View {
#Binding var date: Date
#State private var selectedItem: Int = 0
#State private var showNoticeView: Bool = false
private var items: [TimePicker] = TimePicker.items
private func setTimeForDate(_ time: (Int, Int)) {
guard let newDate = Calendar.current.date(bySettingHour: time.0, minute: time.1, second: 0, of: date) else { return }
date = newDate
}
private var startIndex: Int {
// se trova un orario tra quelli della lista seleziona l'index appropriato
// altrimenti seleziona sempre l'index 0
items.firstIndex(of: date.nextReservationTime) ?? 0
}
init(date: Binding<Date>) {
// Date = ReservationViewModel.date
self._date = date
}
var body: some View {
VStack(spacing: 20) {
HStack {
Spacer()
TitleView("orario")
VStack {
Divider()
.frame(width: screen.width * 0.2)
}
Button(action: {}) {
Text("RESET")
.font(.subheadline.weight(.semibold))
.foregroundColor(date.isToday() ? .gray : .bronze)
.padding(.vertical, 5)
.padding(.horizontal)
.background(.thinMaterial)
.clipShape(Capsule())
}
Spacer()
}
ZStack {
ScrollView(.horizontal, showsIndicators: false) {
ScrollViewReader { reader in
LazyHGrid(rows: Array(repeating: GridItem(.fixed(60), spacing: 0), count: 2), spacing: 0) {
ForEach(items.indices) { item in
TimePickerGridCellView(date: $date, selectedItem: $selectedItem, picker: items[item], selection: selectedItem == items.indices[item])
.frame(width: (UIScreen.main.bounds.width - horizontalDefaultPadding * 2) / 4)
.onTapGesture {
setTimeForDate((items[item].hour, items[item].minute))
selectedItem = item
}
.onChange(of: date) { _ in
selectedItem = startIndex
}
.onAppear(perform: {
selectedItem = startIndex
})
}
}
.background(Divider())
}
}
.frame(height: showNoticeView ? 70 : 130)
}
}
}
}

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.

Linking a SwiftUI Button to a view controller in Xcode 12 / Swift 5

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

How to make a timer in SwiftUI keep firing when changing tab with tabview

I have a timer that fires every half second and that leads to the calling of a function that outputs a set of strings that are used to display a countdown to a specific date. It works when I create a new event and then switch over to the tab that contains the information for the countdown, but when I switch back to the add event tab and then back it stops counting down.
The timer is made using this:
let timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()
It runs later using this
ForEach(eventNames.indices, id: \.self) { index in
VStack{
Text("Your event " + "\(self.eventNames[index])" + " is in " + "\(self.string[index])")
.onReceive(self.timer) { input in
self.differenceDate(numbers: index)
}
}
}
And finally, it calls this function
func differenceDate(numbers: Int) {
self.formatter.unitsStyle = .full
self.formatter.allowedUnits = [.day, .hour, .minute, .second]
//self.formatter.maximumUnitCount = 2
self.now = Date();
if self.now > self.eventDates[numbers] {
self.eventNames[numbers] = "";
}
else {
self.string[numbers] = self.formatter.string(from: self.now, to: self.eventDates[numbers]) ?? ""
}
}
This is the full code
import SwiftUI
struct ContentView: View {
#State private var selection = 0
#State private var eventDates = [Date]()
#State private var eventNames = [String]()
#State private var currentName = "";
#State private var counter = 0;
#State private var placeholderText = "Event Name";
#State private var selectedDate = Date();
var numbers = 0;
let timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()
#State var now = Date();
#State var string = [String]();
var formatter = DateComponentsFormatter();
func differenceDate(numbers: Int) {
self.formatter.unitsStyle = .full
self.formatter.allowedUnits = [.day, .hour, .minute, .second]
//self.formatter.maximumUnitCount = 2
self.now = Date();
if self.now > self.eventDates[numbers] {
self.eventNames[numbers] = "";
}
else {
self.string[numbers] = self.formatter.string(from: self.now, to: self.eventDates[numbers]) ?? ""
}
}
var body: some View {
TabView(selection: $selection){
//Page 1
VStack{
Text("Add New Event")
.underline()
.font(.title)
.padding(15)
// .onReceive(self.timer) { input in
// self.differenceDate(numbers: index)
// //}
// }
// .minimumScaleFactor(0.1)
TextField("\(placeholderText)", text: $currentName)
.padding(10)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.gray, lineWidth: 1)
.padding(5)
)
Text("When is your event?")
DatePicker("Please enter a date", selection: $selectedDate, displayedComponents: .date)
.labelsHidden()
.scaledToFill()
Button(action: {
if self.currentName != "" {
self.eventNames.append(self.currentName)
self.eventDates.append(self.selectedDate)
self.string.append("")
self.currentName = "";
}
})
{
Text("Add Event")
.font(.headline)
.foregroundColor(.black)
}
.padding(25)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.gray, lineWidth: 3)
.padding(5)
)
}
//Tab 1
.tabItem {
VStack {
Image(systemName: "calendar")
Text("Add Event")
}
}
.tag(1)
//Page 2
VStack{
Text("Your Events").underline()
.font(.title)
.padding(15)
ForEach(eventNames.indices, id: \.self) { index in
VStack{
Text("Your event " + "\(self.eventNames[index])" + " is in " + "\(self.string[index])")
.onReceive(self.timer) { input in
self.differenceDate(numbers: index)
}
}
}
}
//Tab 2
.font(.title)
.tabItem {
VStack {
Image(systemName: "flame.fill")
Text("Countdowns")
}
}
.tag(0)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I was wondering if there was a workaround or how to keep the timer firing while the tab changes or pause it when the tab changes and then start it again when the tab is swapped back over.
It needs to attach the .onReceive to the TabView and it will be received on all tabs, like
TabView {
...
// << all tab items here
...
}
.onReceive(self.timer) { _ in
self.differenceDate()
}
and iterate indexes inside of handler
func differenceDate() {
for numbers in eventNames.indices {
// current body here
}
}

Resources