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.
Related
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
}
I try to create reusable function. It select data only for current week. But I need use this function few times in my app, so I've decided to do it generic.
I have two structs
struct BodyWeightCalendarModel {
let weight: Float
let date: Date
}
struct RetrievedWorkoutsByExercise {
let exerciseTitle: String
let sets: Int
let maxWeight: Int
let maxReps: Int
let date: Date
let volume: Int
}
func loopForWeek<T> (data: [T], completionHandler: ([T]) -> Void) {
let arrayForPeriod2: [T] = []
for value in data where value.date >= (calendar.currentWeekBoundary()?.startOfWeek)! && value.date <= (calendar.currentWeekBoundary()?.endOfWeek)! {
arrayForPeriod.append(value)
}
completionHandler(arrayForPeriod2)
}
How to get access to data values? I can't get access through "value.data".
So I want to use this function for different struct (but all this structs needs to have field "date").
As mentioned in other answers using a protocol with date property makes most sense in your case. However theoretically you could also use keypaths to achieve that. You could make a function that takes any instances and gets dates from them in a similar way as below:
func printDates(data: [Any], keyPath:AnyKeyPath) {
for value in data {
if let date = value[keyPath: keyPath] as? Date {
print("date = \(date)")
}
}
}
and then pass for example your BodyWeightCalendarModel instances as below:
let one = BodyWeightCalendarModel(date: Date(), weight: 1)
let two = BodyWeightCalendarModel(date: Date(), weight: 2)
printDates(data: [one, two], keyPath: \BodyWeightCalendarModel.date)
But still a protocol makes more sense in your case.
The reason you can't access value.date is that your function knows nothing about T. Your function declares T but it doesn't constrain it. To the compiler, T can be anything.
You need to create a protocol that tells your function what to expect, and make your structs conform to it:
protocol Timed { // You might find a better name
var date: Date { get }
}
struct BodyWeightCalendarModel: Timed {
let date: Date
...
}
struct RetrievedWorkoutsByExercise: Timed {
let date: Date
...
}
Now you can constrain T to Timed and your function will know that value has a date property.
func loopForWeek<T: Timed> (data: [T], completionHandler: ([T]) -> Void) {
What you're trying to do is close. A Generic value doesn't necessarily have a value on it called date so this won't work. What you can do instead is create a protocol that has a variable of date on it. With that protocol you can do something neat with an extension.
protocol Dated {
var date: Date { get set }
}
extension Dated {
func loopForWeek() -> [Dated] {
let arrayForPeriod2: [Dated] = []
for value in date >= (calendar.currentWeekBoundary()?.startOfWeek)! && value.date <= (calendar.currentWeekBoundary()?.endOfWeek)! {
arrayForPeriod.append(value)
}
return arrayForPeriod2
}
}
Then we can make your two structs use that protocol like:
struct BodyWeightCalendarModel: Dated {
let date: Date
}
struct RetrievedWorkoutsByExercise: Dated {
let date: Date
}
And they can call the function like:
let workouts = RetrievedWorkoutsByExercise()
let result = workouts.loopForWeek()
I am making an apple watch app where it gets the accelerometer data from a date to another date. By using this code:
#IBAction func start() {
WKInterfaceDevice.currentDevice().playHaptic(WKHapticType.Start)
let startDate = NSDate()
NSUserDefaults.standardUserDefaults().setObject(startDate, forKey: "StartDate")
}
#IBAction func stop() {
WKInterfaceDevice.currentDevice().playHaptic(WKHapticType.Stop)
let endDate = NSDate()
let startDate = NSUserDefaults.standardUserDefaults().objectForKey("StartDate") as! NSDate
printData(startDate, endDate: endDate)
}
Then when I got to export the data using the function printData() which this is the code for that function
func printData(startDate: NSDate, endDate: NSDate) {
let recorder = CMSensorRecorder()
let list: CMSensorDataList = recorder.accelerometerDataFromDate(startDate, toDate: endDate)!
for (index, data) in list.enumerate() {
print(index, data)
}
}
For the enumeration I have an extension and here is the code for the extension:
extension CMSensorDataList: SequenceType {
public func generate() -> NSFastGenerator {
return NSFastGenerator(self)
}
}
When I press the stop button the app crashes. I got all of my code from this question(Swift watchOS 2 - CMSensorDataList) but it doesn't work on my device. Does anyone know what is happening?
You need an entry for NSMotionUsageDescription in your Info.plist file. If you crash while attached to Xcode, it should give you this information.
I was trying to make a table show dates from a calendar in the cells but when I wrote the array it gave me the error of 'Unresolved Identifier for "Date"'
Here is the code:
import UIKit
class DatesTableViewController: UITableViewController {
// MARK: Properties
var dates = [Date]()
override func viewDidLoad() {
super.viewDidLoad()
func loadSampleDates() {
let date1 = Date(print(CALENDAR_CLOCK))!
let date2 = Date(print(CALENDAR_CLOCK))!
let date3 = Date(print(CALENDAR_CLOCK))!
let date4 = Date(print(CALENDAR_CLOCK))!
dates += [date1, date2, date3, date4]
}
The file is in the correct group, it is pointing to an existing table and cell, I am just unsure what else to check for.
I will preface this by saying I am newer to Swift so I might just be simply missing an obvious mistake, but based on the iOS Developer site this seems to be the correct way to write this code.
Date is an unresolved identifier (unless it's a separate class you wrote yourself) — you probably want to use NSDate:
import UIKit
class DatesTableViewController: UITableViewController {
// MARK: Properties
dynamic var dates = [NSDate]()
override func viewDidLoad() {
super.viewDidLoad()
func loadSampleDates() {
let date1 = NSDate(print(CALENDAR_CLOCK))
let date2 = NSDate(print(CALENDAR_CLOCK))
let date3 = NSDate(print(CALENDAR_CLOCK))
let date4 = NSDate(print(CALENDAR_CLOCK))
dates += [date1, date2, date3, date4]
}
}}
Note that the NSDate print function will always return a result — it's not optional, so there's no need to use an ! to force-unwrap it.
In QueryHK I run a HealthKit query for steps and the corresponding date. I return the values in a completion handler. In ViewController I declare the completion. My problem is that the method only returns the last value from the iteration sample in samples.
Question: I want all of the data returned in the completion, not just the last value.. How can I return all the data from the query in an NSArray ?
QueryHK.swift:
import UIKit
import HealthKit
class QueryHK: NSObject {
var steps = Double()
var date = NSDate()
func performHKQuery (completion: (steps: Double, date: NSDate) -> Void){
let healthKitManager = HealthKitManager.sharedInstance
let stepsSample = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
let stepsUnit = HKUnit.countUnit()
let sampleQuery = HKSampleQuery(
sampleType: stepsSample,
predicate: nil,
limit: 0,
sortDescriptors: nil)
{
(sampleQuery, samples, error) in
for sample in samples as [HKQuantitySample]
{
self.steps = sample.quantity.doubleValueForUnit(stepsUnit)
self.date = sample.startDate
}
// Calling the completion handler with the results here
completion(steps: self.steps, date: self.date)
}
healthKitManager.healthStore.executeQuery(sampleQuery)
}
}
ViewController:
import UIKit
class ViewController: UIViewController {
var dt = NSDate()
var stp = Double()
var query = QueryHK()
override func viewDidLoad() {
super.viewDidLoad()
printStepsAndDate()
}
func printStepsAndDate() {
query.performHKQuery() {
(steps, date) in
self.stp = steps
self.dt = date
println(self.stp)
println(self.dt)
}
}
}
Have your completion handler receive an array of steps/date pairs:
completion: ([(steps: Double, date: NSDate)]) -> Void
(you could pass two arrays, one of steps and one of dates, but I feel like it’s clearer to pass an array of pairs since the two are tied together)
Then build an array of pairs of step counts and dates:
if let samples = samples as? [HKQuantitySample] {
let steps = samples.map { (sample: HKQuantitySample)->(steps: Double, date: NSDate) in
let stepCount = sample.quantity.doubleValueForUnit(stepsUnit)
let date = sample.startDate
return (steps: stepCount, date: date)
}
completion(steps)
}
If you want the query class to retain this information as well, make the member variable an array of the same type and store the result in that as well as pass it to the callback.