SwiftUI - how to shuffle an array one time a day? - ios

What is the approach to shuffle an array of strings one time a day?
And not every time the app is relaunches.
struct View: View {
#ObservedObject var quotes = Quotes()
var body: some View {
List {
ForEach(quotes.shuffled()) { quote in
Text(quote.quotes)
}
}
}
}
When I try the shuffled() method every time the view is updated the quotes are shuffled again, also when relaunching the app, I want to shuffle the array only one time a day.

You need to store current date in memory like user defaults and check every time for new date like I have in code below. isNewDay() function checks if date is new and saves current date in user defaults. Condition isNewDay() ? quotes.shuffled() : quotes shuffles quotes only if date is new
struct View :View{
#ObservedObject var quotes = Quotes()
var body :some View{
List{
ForEach(isNewDay() ? quotes.shuffled() : quotes){ quote in
Text(quote.quotes)
}
}
}
func isNewDay()-> Bool{
let currentDate = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd/yyyy"
let currentDateString = dateFormatter.string(from: currentDate)
if let lastSaved = UserDefaults.standard.string(forKey: "lastDate"){// last saved date
if lastSaved == currentDateString{
return true
}else{
UserDefaults.standard.setValue(currentDateString, forKey: "lastDate")
return false
}
}else{
UserDefaults.standard.setValue(currentDateString, forKey: "lastDate")
return false
}
}
}

Related

How to change the backgroungColor cell in tableView with timeInterval using Swift

I'm trying to change the cell backgroungColor after 3.5 month. I have a textField where i put the date and after 3.5 month of that date I want to change the color of the cell in red.
I tried this where date1 is the date from textField and date2 is this from (isToday) where i have put 106 day = 3.5 month
let isToday= Date.now.addingTimeInterval(106)
func isSameDay(date1: Date, date2: Date) -> Bool {
let diff = Calendar.current.dateComponents([.day], from: date1, to: date2)
if diff.day == 0 {
return true
}
else {
return false
}
}
Inside cellForRowAt i have used like this
var documentSendDate = "05.08.2022"// this is example to be more understandable
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM/dd/yyyy"
let date = dateFormatter.date(from: documentSendDate)
if date != nil {
let isDayToday = isSameDay(date1: date!, date2: isToday) // here I call the function above
if isDayToday == true {
if customer.isToday == true {
cell.backgroundColor = .red
}
}
}
But I have this checkBox and when I check it or uncheck it change the color of the random cells. Can someone help me with this please?
Here is how i wanted to look.
UITableView is a recycle-list view, so it will reuse the cell UI instance to display data for the corresponding indexPath.
First, modify your code to add a new way of dateFormatter declaration.
// Use lazy var to reduce initialization cost
// Because initializing a new DateFormatter is not cheap, it can consume CPU time like initializing a new NumberFormatter
// lazy var will be only initialized once on the first call/use
lazy var dateFormatter = DateFormatter()
func viewDidLoad() {
super.viewDidLoad()
dateFormatter.dateFormat = "MMM/dd/yyyy"
}
It is a reusable UI, so UI won't hold the data or state. The cellForRowAt will be called multiple times when you scroll tableView or when tableView needs to re-layout,... to display the corresponding data-state for each indexPath.
That is why you must not initialize or do some big calculations/long waiting here. It will freeze/delay your UI (ref: DispatchQueue.main or MainQueue).
So inside your cellForRowAt function, you need to add logic for all cases if you use switch/if-else.
var documentSendDate = "05.08.2022"// this is example to be more understandable
// Here I combine all checks into one if-else
// Order of check is left-to-right.
// It is condition1 AND condition2 AND condition3 (swift syntax)
if let date = dateFormatter.date(from: documentSendDate),
let isDayToday = isSameDay(date1: date!, date2: isToday),
customer.isToday == true {
cell.backgroundColor = .red
} else {
cell.backgroundColor = .clear // or your desired color
}

How to load JSON Data into FSCalendar Swift 5

I am trying to load my JSON Data into FSCalendar.
I understand from the documentation that when you use an array that can be show in the calendar with the dots. I am wanting to do that same thing but instead to have the dots appear from my dates listed in my JSON file. I have loaded the JSON file into my bundle as well as made a function to load the JSON data and I have created a structure.
My problem comes in when I am trying to use an array from the structure and then load that array into the "func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int {" function. I have been working on this for a while and could use some help. Thanks
JSON Data: - I titled the JSON file data.json
[
{
"event": "Christmas",
"date": [
"2021-01-02",
"2021-01-03",
"2021-01-04"
]
}
]
View Controller:
import UIKit
import FSCalendar
class ViewController: UIViewController, FSCalendarDelegate, FSCalendarDataSource, FSCalendarDelegateAppearance {
//MARK: - calendar variables
#IBOutlet var calendar: FSCalendar!
#IBOutlet weak var dateLabel: UILabel!
fileprivate let gregorian: Calendar = Calendar(identifier: .gregorian)
fileprivate lazy var dateFormatter1: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy/MM/dd"
return formatter
}()
fileprivate lazy var dateFormatter2: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
// These are just test data to see the dots on the calendar
var datesWithEvents = ["2021-01-15","2021-01-16","2021-01-17","2021-01-18"]
var datesWithMultipleEvents = ["2021-01-20","2021-01-21","2021-01-22","2021-01-23"]
//MARK: - JSON variable
// var result: Results?
let data = DataLoader().eventData
//MARK: - viewdidload
override func viewDidLoad() {
super.viewDidLoad()
// parseJSON()
let data = DataLoader().eventData
print(data)
calendar.delegate = self
calendar.dataSource = self
}
deinit {
print("\(#function)")
}
//MARK: - calendar functions
func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
let formatter = DateFormatter()
formatter.dateFormat = "EEEE MM-dd-YYYY"
let string = formatter.string(from: date)
print("\(string)")
}
func calendar(_ calendar: FSCalendar, appearance: FSCalendarAppearance, eventDefaultColorsFor date: Date) -> [UIColor]? {
let key = self.dateFormatter2.string(from: date)
// this is the test with the hard coded arrays, I think i need to
implement the JSON here but am not sure how
if self.datesWithMultipleEvents.contains(key) {
return [UIColor.magenta, appearance.eventDefaultColor, UIColor.red]
}
return nil
}
func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int {
let dateString = self.dateFormatter2.string(from: date)
if self.datesWithEvents.contains(dateString) {
return 1
}
if self.datesWithMultipleEvents.contains(dateString) {
return 3
}
return 0
}
Data Model:
struct EventsData: Codable {
var event: String
var date: [String]
}
JSON loader:
import Foundation
public class DataLoader {
#Published var eventData = [EventsData]()
init() {
load()
}
func load() {
if let fileLocation = Bundle.main.url(forResource: "data", withExtension: "json") {
//do catch incase of error
do {
let data = try Data(contentsOf: fileLocation)
let jsonDecoder = JSONDecoder()
let dataFromJson = try jsonDecoder.decode([EventsData].self, from: data)
self.eventData = dataFromJson
} catch {
print(error)
}
}
}
}
EDIT:
func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int {
let dateString = self.dateFormatter2.string(from: date)
if self.eventData.contains(where: //<#T##(EventsData) throws -> Bool#>) //How to get this to do the contains like in the next if statement
if self.datesWithEvents.contains(dateString) {
return 1
}
if self.datesWithMultipleEvents.contains(dateString) {
return 3
}
return 0
}
Check if you have this in your code:
// FSCalendarDataSource
func calendar(calendar: FSCalendar!, hasEventForDate date: NSDate!) -> Bool {
return shouldShowEventDot
}
Just convert your string date into Date with DateFormatter and then just go through your array and check if the date (from the calendar function) matches any of your dates in the event.
You have a number of problems.
First, you are trying to use SwiftUI constructs (#Published) in a Swift app. That won't work.
Second, you have your dates coming in as an array of strings. I suggest setting up your Codable protocol conformance using a custom Date decoding stategy.
This link shows how to set up your JSONDecoder to use a custom date decoding strategy that takes a custom DateFormatter.
Next, you need some way to update your UI when new data is read from your JSON input. SwiftUI has built-in mechanisms for automatically responding to state changes, but you're not using SwiftUI, so those don't apply.
You have a DataLoader class that loads JSON data. It currently reads hard-coded JSON from a file, synchronously. I assume that you want to load the data from the internet, which would be async. Thus your approach won't work.
I suggest you set up your DataLoader class to either have a delegate that it notifies when it has completed loading new data, or set it up to take requests with a completion handler.
If you don't know how to do either of those things then you need to stop and learn some fundamentals. Read up on Swift async networking, completion handlers, and the delegate design pattern before you continue.

iOS Swift function isDateInToday not working on some phones

Our app requires that the user carries out a daily task. If he/she completes the task, then at midnight a new task is released. If they don't, then come midnight they will continue seeing the previous task until they do.
I'm saving the current date in User Defaults and I'm using isDateInToday to heck if the date has changed:
func checkIfDayChanged() -> Bool {
if let date = UserDefaults.standard.object(forKey: "currentDate") as? Date {
var calendar = Calendar.current
calendar.timeZone = TimeZone.current
let isSameDay = calendar.isDateInToday(date)
if (isSameDay) {
return false
}
}
return true
}
In the majority of cases this works fine. However, there is a small group of users that are stuck with the same task even though they completed it the previous day. For some reason the app is not recognizing that the day is a new one.
Could this be a setting on the phone? Or do I need to change this code? Why wouldn't this be working in only a few devices?
UPDATE:
This is what happens when the daily task (a lesson) is completed:
func onComplete() {
let latestLesson = UserDefaults.standard.integer(forKey: "currentLesson")
if String(latestLesson) == lesson?.lessonNumber {
if (UserDefaults.standard.bool(forKey: "dayLessonCompleted") == false) {
UserDefaults.standard.set(true, forKey: "dayLessonCompleted")
lesson?.completed = true
}
}
}
Every day, the following function runs:
private func checkForMoreLessons() {
// Check the date
let newDay = checkIfDayChanged()
// If the day changed, check if user has watched the previous lesson
if newDay {
if (UserDefaults.standard.bool(forKey: "dayLessonCompleted") == true) {
// Save the new day
UserDefaults.standard.set(Date(), forKey:"currentDate")
// Add 1 to the current date
let currentLesson = UserDefaults.standard.integer(forKey: "currentLesson")
UserDefaults.standard.set(currentLesson + 1, forKey: "currentLesson")
UserDefaults.standard.set(false, forKey: "dayLessonCompleted")
}
}
}

Unwrap optionals of the type Date() from CoreData?

I have created a NSManagedObject sReminderDate: Date(). This is stored in the coreData. The value for this comes from the date and time set by the user from the datePicker.
When the viewcontroller is loaded, this value is compared with the currentDate and time and if the currentTime > sReminderDate then a label says "Time's Up". The issue is that when the user loads the viewcontroller for the first time, the time and date is not set the sReminderDate is 'null'. Hence, it's crashing my app because this is null and I am using it for comparison.
I am having a hard time unwrapping it with if-let and guard statements. Help would be appreciated!
let currentDate = NSDate() //Current date and time stored whenever viewcontroller is loaded
if ((currentDate as! Date ) >= (editnotes?.sSelectedDate)! && (editnotes?.sReminderState == true)) //Comparison -> sSelectedDate is the date stored in coreData by the user when scheduling task
{
editnotes?.sReminderDate = "Time is up"
reminderMsg.text = editnotes?.sReminderDate
}
Look what you wanna to achieve is to separate code when you don't have this values and when you got them and can compare, i.e. implement your logic
I'd make something like that:
guard let selectedDate = editnotes?.sSelectedDate,
let needsToRemind = editnotes?.sReminderState else {
// implement logic when you DO NOT have values
return
}
// Everything is cool and you have values => can compare
if selectedDate < Date() && needsToRemind {
editnotes?.sSelectedDate = "Time's up"
// Possibly editnotes?.sReminderDate better to unwrap somehow
// if-let, guard-let, ??
reminderMsg.text = editnotes?.sReminderDate
}
Code is much cleaner and more representative
You can use comma (,) as AND operator in swift.
let currentDate = Date()
if let editnotes = editnotes, currentDate >= editnotes.sSelectedDate!, editnotes.sReminderState == true {
editnotes?.sReminderDate = "Time is up"
reminderMsg.text = editnotes?.sReminderDate
}
Updated ans
let currentDate = Date()
if let selectedDate = editnotes?.sSelectedDate, let reminderState = editnotes?.sReminderState, currentDate >= selectedDate, reminderState == true {
editnotes?.sReminderDate = "Time is up" reminderMsg.text = editnotes?.sReminderDate
}

Changing one property in a variable triggers subcriptions to other properties. RxSwift

I have the following struct of properties for Chat
struct Chat {
var id = String()
var gender = String()
var date = Date()
init() {}
}
In a view controller, i declare an instance of Chat called observablechat, and then i used the flatmap operator to attempt and observe only changes in the date property of observablechat. However, if i change the gender property (as shown), the subscription gets triggered. I wonder why that is and how can i fix this code such that the subscription only looks at what happens to the date property and nothing else?
class ViewController: UIViewController {
var observablechat = Variable(Chat())
observablechat.asObservable()
.flatMap { (Chat) -> Observable<Date> in
return Observable.of(Chat.matchDate)
}.subscribe(onNext: { (r) in
print(r)
}).addDisposableTo(disposeBag)
self.observablechat.value.gender = "male"
//triggers subscription above.
}
}
First of all, why flatMap?
You need only distinctUntilChanged and map. DebugPrint will be triggered only twice: initial setup + date changed once. Check out the code below and feel free to ask questions.
import PlaygroundSupport
import RxSwift
struct Chat {
var id: String
var gender: String
var date: Date
init(id: String = "", gender: String = "", date: Date = Date(timeIntervalSince1970: 0)) {
self.id = id
self.gender = gender
self.date = date
}
}
final class YourClass {
lazy var variableChat: Variable<Chat> = Variable<Chat>(Chat())
var observableDate: Observable<Date> {
return variableChat
.asObservable()
.distinctUntilChanged({ (chatOld, chatNew) -> Bool in
return chatOld.date == chatNew.date
})
.map({ (chat) -> Date in
return chat.date
})
.shareReplay(1)
}
}
let value = YourClass()
value.observableDate
.subscribe(onNext: { (date) in
debugPrint(date.timeIntervalSince1970)
})
value.variableChat.value.gender = "male"
value.variableChat.value.date = Date()
value.variableChat.value.gender = "female"
value.variableChat.value.gender = "male"
value.variableChat.value.gender = "female"
P.S. the way to run RxSwift in playground: readme

Resources