How do I list yesterday's date in SwiftUI? It probably is a simple answer but I'm just learning to code and for some reason I can't seem to find the solution anywhere. Is it because it is too easy?
struct DateShown: View {
let datechoice: Datechoice
var body: some View {
Text(currentDate(date: Date()))
.font(.headline)
.fontWeight(.bold)
.foregroundColor(.blue)
}
func currentDate(date: Date!) -> String {
let formatter = DateFormatter()
formatter.locale = .current
formatter.dateFormat = "MMMM d, yyyy"
return date == nil ? "" : formatter.string(from: date)
}
}
I would rather use View extensions, though you also need Date formatting so I went the easier way and extended your solution. If the number at line "dayComponent.day" is positive, you go futher in time. I tested under:
swift 5
xcode 11.3.1
iOS 13.3.1 non beta
func yesterDay() -> String {
var dayComponent = DateComponents()
dayComponent.day = -1
let calendar = Calendar.current
let nextDay = calendar.date(byAdding: dayComponent, to: Date())!
let formatter = DateFormatter()
formatter.locale = .current
formatter.dateFormat = "MMMM d, yyyy"
return formatter.string(from: nextDay). //Output is "March 6, 2020
}
Usage is the same as yours:
Text(yesterDay())
Related
My goal:
I want to be able to group CoreData Todo items by their dueDate ranges. ("Today", "Tomorrow", "Next 7 Days", Future")
What I attempted...
I tried using #SectionedFetchRequest but the sectionIdentifier is expecting a String. If it's stored in coreData as a Date() how do I convert it for use? I received many errors and suggestions that didn't help. This also doesn't solve for the date ranges like "Next 7 Days". Additionally I don't seem to even be accessing the entity's dueDate as it points to my ViewModel form instead.
#Environment(\.managedObjectContext) private var viewContext
//Old way of fetching Todos without the section fetch
//#FetchRequest(sortDescriptors: []) var todos: FetchedResults<Todo>
#SectionedFetchRequest<String, Todo>(
entity: Todo.entity(), sectionIdentifier: \Todo.dueDate,
SortDescriptors: [SortDescriptor(\.Todo.dueDate, order: .forward)]
) var todos: SectionedFetchResults<String, Todo>
Cannot convert value of type 'KeyPath<Todo, Date?>' to expected argument type 'KeyPath<Todo, String>'
Value of type 'NSObject' has no member 'Todo'
Ask
Is there another solution that would work better in my case than #SectionedFetchRequest? if not, I'd like to be shown how to group the data appropriately.
You can make your own sectionIdentifier in your entity extension that works with #SectionedFetchRequest
The return variable just has to return something your range has in common for it to work.
extension Todo{
///Return the string representation of the relative date for the supported range (year, month, and day)
///The ranges include today, tomorrow, overdue, within 7 days, and future
#objc
var dueDateRelative: String{
var result = ""
if self.dueDate != nil{
//Order matters here so you can avoid overlapping
if Calendar.current.isDateInToday(self.dueDate!){
result = "today"//You can localize here if you support it
}else if Calendar.current.isDateInTomorrow(self.dueDate!){
result = "tomorrow"//You can localize here if you support it
}else if Calendar.current.dateComponents([.day], from: Date(), to: self.dueDate!).day ?? 8 <= 0{
result = "overdue"//You can localize here if you support it
}else if Calendar.current.dateComponents([.day], from: Date(), to: self.dueDate!).day ?? 8 <= 7{
result = "within 7 days"//You can localize here if you support it
}else{
result = "future"//You can localize here if you support it
}
}else{
result = "unknown"//You can localize here if you support it
}
return result
}
}
Then use it with your #SectionedFetchRequest like this
#SectionedFetchRequest(entity: Todo.entity(), sectionIdentifier: \.dueDateRelative, sortDescriptors: [NSSortDescriptor(keyPath: \Todo.dueDate, ascending: true)], predicate: nil, animation: Animation.linear)
var sections: SectionedFetchResults<String, Todo>
Look at this question too
You can use Date too but you have to pick a date to be the section header. In this scenario you can use the upperBound date of your range, just the date not the time because the time could create other sections if they don't match.
extension Todo{
///Return the upperboud date of the available range (year, month, and day)
///The ranges include today, tomorrow, overdue, within 7 days, and future
#objc
var upperBoundDueDate: Date{
//The return value has to be identical for the sections to match
//So instead of returning the available date you return a date with only year, month and day
//We will comprare the result to today's components
let todayComp = Calendar.current.dateComponents([.year,.month,.day], from: Date())
var today = Calendar.current.date(from: todayComp) ?? Date()
if self.dueDate != nil{
//Use the methods available in calendar to identify the ranges
//Today
if Calendar.current.isDateInToday(self.dueDate!){
//The result variable is already setup to today
//result = result
}else if Calendar.current.isDateInTomorrow(self.dueDate!){
//Add one day to today
today = Calendar.current.date(byAdding: .day, value: 1, to: today)!
}else if Calendar.current.dateComponents([.day], from: today, to: self.dueDate!).day ?? 8 <= 0{
//Reduce one day to today to return yesterday
today = Calendar.current.date(byAdding: .day, value: -1, to: today)!
}else if Calendar.current.dateComponents([.day], from: today, to: self.dueDate!).day ?? 8 <= 7{
//Return the date in 7 days
today = Calendar.current.date(byAdding: .day, value: 7, to: today)!
}else{
today = Date.distantFuture
}
}else{
//This is something that needs to be handled. What do you want as the default if the date is nil
today = Date.distantPast
}
return today
}
}
And then the request will look like this...
#SectionedFetchRequest(entity: Todo.entity(), sectionIdentifier: \.upperBoundDueDate, sortDescriptors: [NSSortDescriptor(keyPath: \Todo.dueDate, ascending: true)], predicate: nil, animation: Animation.linear)
var sections: SectionedFetchResults<Date, Todo>
Based on the info you have provided you can test this code by pasting the extensions I have provided into a .swift file in your project and replacing your fetch request with the one you want to use
It is throwing the error because that is what you told it to do. #SectionedFetchRequest sends a tuple of the type of the section identifier and the entity to the SectionedFetchResults, so the SectionedFetchResults tuple you designate has to match. In your case, you wrote:
SectionedFetchResults<String, Todo>
but what you want to do is pass a date, so it should be:
SectionedFetchResults<Date, Todo>
lorem ipsum beat me to the second, and more important part of using a computed variable in the extension to supply the section identifier. Based on his answer, you should be back to:
SectionedFetchResults<String, Todo>
Please accept lorem ipsum's answer, but realize you need to handle this as well.
On to the sectioning by "Today", "Tomorrow", "Next 7 Days", etc.
My recommendation is to use a RelativeDateTimeFormatter and let Apple do most or all of the work. To create a computed variable to section with, you need to create an extension on Todo like this:
extension Todo {
#objc
public var sections: String {
// I used the base Xcode core data app which has timestamp as an optional.
// You can remove the unwrapping if your dates are not optional.
if let timestamp = timestamp {
// This sets up the RelativeDateTimeFormatter
let rdf = RelativeDateTimeFormatter()
// This gives the verbose response that you are looking for.
rdf.unitsStyle = .spellOut
// This gives the relative time in names like today".
rdf.dateTimeStyle = .named
// If you are happy with Apple's choices. uncomment the line below
// and remove everything else.
// return rdf.localizedString(for: timestamp, relativeTo: Date())
// You could also intercept Apple's labels for you own
switch rdf.localizedString(for: timestamp, relativeTo: Date()) {
case "now":
return "today"
case "in two days", "in three days", "in four days", "in five days", "in six days", "in seven days":
return "this week"
default:
return rdf.localizedString(for: timestamp, relativeTo: Date())
}
}
// This is only necessary with an optional date.
return "undated"
}
}
You MUST label the variable as #objc, or else Core Data will cause a crash. I think Core Data will be the last place that Obj C lives, but we can pretty easily interface Swift code with it like this.
Back in your view, your #SectionedFetchRequest looks like this:
#SectionedFetchRequest(
sectionIdentifier: \.sections,
sortDescriptors: [NSSortDescriptor(keyPath: \Todo.timestamp, ascending: true)],
animation: .default)
private var todos: SectionedFetchResults<String, Todo>
Then your list looks like this:
List {
ForEach(todos) { section in
Section(header: Text(section.id.capitalized)) {
ForEach(section) { todo in
...
}
}
}
}
You can use this method for achive that,
like this:
func formattedDate () -> String? {
let RFC3339DateFormatter = DateFormatter()
RFC3339DateFormatter.locale = Locale(identifier: "en_US_POSIX")
RFC3339DateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
RFC3339DateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
let date1 = RFC3339DateFormatter.date(from: date.formatted()) ?? Date()
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
// ES Spanish Locale (es_ES)
dateFormatter.locale = Locale.current//Locale(identifier: "es_ES")
return dateFormatter.string(from: date1) // Jan 2, 2001
}
I got a list of quotes I am trying to update every 24hrs, based on the calender.
This is what I tried so far, but I am getting error Cannot convert value of type 'Date?' to expected argument type 'TimeInterval' (aka 'Double')
let numberOfQuotes = 3
let quotes = ["quote 1", "quote 2", "quote 3"]
override func viewDidLoad() {
super.viewDidLoad()
let timer = Timer.scheduledTimer(timeInterval: TimeInterval(30),
target: self, selector: #selector(self.updateQuote), userInfo: nil, repeats: true)
}
#objc func updateQuote() {
let lastUpdate = UserDefaults.standard.object(forKey: "lastUpdate") as? Date
if lastUpdate != nil {
let date1:Date = Date() // Same you did before with timeNow variable
let date2: Date = Date(timeIntervalSince1970: lastUpdate ) // **Getting error on this line**
let calender:Calendar = Calendar.current
let components: DateComponents = calender.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date1, to: date2)
if components.day! >= 1 {
UserDefaults.standard.set(Date(), forKey: "lastUpdate")
textView.text = "Hello there"
}
} else { //firstTime running
UserDefaults.standard.set(Date(), forKey: "lastUpdate")
textView.text = quotes[randomInt(min: 0,max: numberOfQuotes)]
}
}
lastUpdate is already a Date, the initializer Date(timeIntervalSince1970: is wrong and not needed anyway.
It's highly recommended to use optional bindings, and don't annotate types the compiler can infer.
if let lastUpdate = UserDefaults.standard.object(forKey: "lastUpdate") as? Date {
let date1 = Date()
let calender = Calendar.current
let components = calender.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date1, to: lastUpdate)
...
let date2: Date = lastUpdate!
UPDATE:
let components: DateComponents = calender.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date2, to: date1)
Hi newBiew in JTAppleCalendar.
I follow this link for JTAppleCalendar.
https://www.youtube.com/watch?v=CQNotydm58s&index=6&list=PLpqJf39XekqyUG7dxcqGO0JNprryysv9Q
I have this Problem:
How to I show calendar when user click a button to change monthView to WeekView or from week View to month View
How to I change the calendar size programmatically for CalendarView and mainStack as they have constrains?
I believe I need to handle configureCalendar as below but how to change programmatically month view to week view vice versa.
I have a stack (Call it mainStack) which used to contain CalendarView
Layout for the Calendar view :
#IBOutlet weak var CalendarView : JTAppleCalendarView!<br/>
#IBOutlet weak var mainStack: UIStackView!<br/>
extension MyCalendar: JTAppleCalendarViewDataSource, JTAppleCalendarViewDelegate {
func configureCalendar( _ calendar:JTAppleCalendarView) -> ConfigurationParameters {
formatter.dateFormat = "yyyy MM dd"
formatter.timeZone = Calendar.current.timeZone
formatter.locale = Calendar.current.locale
let startDate = formatter.date(from: "2017 01 01")!
let endDate = formatter.date(from: "2027 12 31")!
//-- how to set these "
1) Full calendar view
let parameters = ConfigurationParameters(startDate : startDate, endDate: endDate)
return parameters
2) for week view
let parameters = ConfigurationParameters(startDate : startDate, endDate: endDate, numberOfRows:1)
return parameters
}
func calendar( _ calendar: JTAppleCalendarView, cellForItemAt date: Date, cellState: CellState, indexPath: IndexPath) -> JTAppleCell{
let cell = calendar.dequeueReusableJTAppleCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.dateLabel.text = cellState.text
configureCell(cell:cell, cellState: cellState)
return cell
}
//------------ selected item
func calendar( _ calendar: JTAppleCalendarView, didSelectDate date: Date, cell:JTAppleCell?, cellState:CellState){
configureCell(cell: cell, cellState: cellState)
}
func calendar( _ calendar: JTAppleCalendarView, didDeselectDate date: Date, cell:JTAppleCell?, cellState:CellState){
configureCell(cell: cell, cellState: cellState)
}
func calendar(_ calendar: JTAppleCalendarView, didScrollToDateSegmentWith visibleDates: DateSegmentInfo) {
setupCalendarView(dateSegment: visibleDates)
}
}
Please help.
Thanks
So the plan is:
You have a variable for the number of rows.
In a monthView mode, it has 6 rows.
In a weekView mode, it has 1 row.
So when you want to change the mode you change numberOfRows and reload calendarView and scroll to the current date.
Also when you have weekView, you should use a little bit different ConfigurationParameters.
That's how I do this:
#IBAction func monthWeekModeChanged(_ sender: UISegmentedControl) {
if sender.selectedSegmentIndex == 0 {
numberOfRows = 6
} else {
numberOfRows = 1
}
calendarView.reloadData()
calendarView.scrollToDate(Date(), animateScroll: false)
}
var numberOfRows = 6
extension CalendarViewController: JTAppleCalendarViewDataSource {
func configureCalendar(_ calendar: JTAppleCalendarView) -> ConfigurationParameters {
let startDate = viewModel.formatter.date(from: "01-Jan-2019")
let endDate = viewModel.formatter.date(from: "31-Dec-2020")
if numberOfRows == 6 {
return ConfigurationParameters(startDate: startDate!, endDate: endDate!, numberOfRows: numberOfRows, firstDayOfWeek: .monday)
} else {
return ConfigurationParameters(startDate: startDate!,
endDate: endDate!,
numberOfRows: 1,
generateInDates: .forFirstMonthOnly,
generateOutDates: .off, firstDayOfWeek: .monday,
hasStrictBoundaries: false)
}
}
}
The tutorial here states just how to do this.
I cannot paste the whole thing here since the instructions are long.
Also, at the bottom of the page is the complete code in a zip file that you can play around with.
My code below works that if you manually use to datePicker to match the date and time with the users date and time it will print cool. However the code does not work as a alarm. I can set the date picker ahead of the user time and when the user time matches the date pickers timer nothing prints. I just want to be able to select a date/time using date picker and when the users eventually matches the datePickers time just print cool.
import UIKit
var dateFormatter : DateFormatter!
let datePicker2 = UIDatePicker();
let date = Date()
class ViewController: UIViewController {
#IBOutlet var dateLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let datePicker : UIDatePicker = UIDatePicker(frame: CGRect(x: 0,y: 330,width: self.view.frame.size.width,height: 220))
datePicker.datePickerMode = UIDatePickerMode.dateAndTime
self.view.addSubview(datePicker)
datePicker.addTarget(self, action: #selector(ViewController.change(_:)), for: UIControlEvents.valueChanged)
dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd hh:mm"
}
#IBAction func change(_ sender : UIDatePicker)
{
let pickerString = dateFormatter.string(from: sender.date)
let nowString = dateFormatter.string(from: Date())
dateLabel.text = pickerString
if pickerString == nowString {
print("cool")
}
}}
This is happening because your if statement is only being called when the picker moves. You need a timer that fires every second looking to see if they match. To use the method below you just need to move the pickerString and nowString up to global variables. If you don't like that you can refactor and use as you want.
var pickerString: String?
var nowString: String?
override func viewDidLoad() {
super.viewDidLoad()
let _ = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (timer) in
if pickerString == nowString {
print("cool")
}
}
#buildSucceeded
How can events be added to an FSCalendar in swift 3?
Implement the appropriate methods in a class adopting FSCalendarDataSource.
var datesWithEvent = ["2015-10-03", "2015-10-06", "2015-10-12", "2015-10-25"]
var datesWithMultipleEvents = ["2015-10-08", "2015-10-16", "2015-10-20", "2015-10-28"]
fileprivate lazy var dateFormatter2: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int {
let dateString = self.dateFormatter2.string(from: date)
if self.datesWithEvent.contains(dateString) {
return 1
}
if self.datesWithMultipleEvents.contains(dateString) {
return 3
}
return 0
}
Based On FsCalendar Documentation