Is this the place to ask for clarification on previously written lines of code? I apologize if not. I'm trying to understand some Swift code for using LineCharts. Code was written for line charts using iOS16's Swift Charts. My understanding is this code will create a list of dates and numbers to plot. But how do I call it and print out the values? Having no luck printing out data with either playground or within SwiftUI.
I'd like to somehow print out the array of data (days and numbers) that were generated before attempting to plot them.
struct HeartRate: Hashable {
var day: String
var value: Int = .random(in: 60..<150)
}
extension HeartRate {
static var data: [HeartRate] {
let calendar = Calendar(identifier: .gregorian)
let days = calendar.shortWeekdaySymbols
return days.map { day in
HeartRate(day: day)
}
}
}
struct NewLineChartView: View {
var dataPoints: [HeartRate]
var body: some View {
Chart(dataPoints, id: \.self) { rate in
LineMark(x: .value("Day", rate.day),
y: .value("Heart rate", rate.value))
.foregroundStyle(.red)
.symbol(Circle().strokeBorder(lineWidth: 1.5))
}
}
}
Do you want to print to the console? If so, you could capture and print the values(result) before charting the same values:
extension HeartRate {
static var data: [HeartRate] {
let calendar = Calendar(identifier: .gregorian)
let days = calendar.shortWeekdaySymbols
let result = days.map { day in
HeartRate(day: day)
}
print(result)
return result
}
}
Related
I'm trying to get multiple types of health data all with the same timeframe. My problem relies on the way that I should handle all the data that I get from those queries. Currently my model is as follows:
struct DailyData {
var steps: Int?
var distance: Int?
var calories: Int?
var exercise: Int?
}
class UserData {
let healthManager = HealthKitManager.shared
var dailyData = [DailyData]?
.
.
.
}
if I'm not mistaken, I can only query only one HKQuantityIdentifier at a time, so that means I need to call my getData() function from my HealthKitManager.swift once for every HKQuantityType that I have in my model:
func getData(type: HKQuantityTypeIdentifier, unit: HKUnit, days: Int, completed: #escaping (Result<[Int], Error>) -> Void) {
let calendar = NSCalendar.current
let interval = NSDateComponents()
interval.day = 1
let quantityType = HKQuantityType.quantityType(forIdentifier: type)!
var anchorComponents = calendar.dateComponents([.day, .month, .year], from: NSDate() as Date)
anchorComponents.hour = 0
let anchorDate = calendar.date(from: anchorComponents)
// Define 1-day intervals starting from 0:00
let query = HKStatisticsCollectionQuery(quantityType: quantityType,
quantitySamplePredicate: nil,
options: .cumulativeSum,
anchorDate: anchorDate!,
intervalComponents: interval as DateComponents)
query.initialResultsHandler = {query, results, error in
if let error = error {
completed(.failure(error))
return
}
let endDate = NSDate()
let startDate = calendar.date(byAdding: .day, value: -(days - 1), to: endDate as Date, wrappingComponents: false)
var completeDataArray: [Int] = []
if let myResults = results{
myResults.enumerateStatistics(from: startDate!, to: endDate as Date) { statistics, stop in
if let quantity = statistics.sumQuantity(){
let dayData = quantity.doubleValue(for: unit)
completeDataArray.append(Int(dayData))
}
}
}
completed(.success(completeDataArray))
}
healthStore.execute(query)
}
My problem is that I can't find a way to correctly set the received data into my model. Could someone point me in the right direction? I believe my model could be wrong, because as for what I have gather online, it's impossible to query multiple HKQuantityTypes in one query. Meaning that I would definitely have to set my model one [Int] of a HKtype at a time.
But with what I currently have, that would mean that when the first query returns I have to create multiple DailyData objects with almost all of the variables nil except the one I'm setting. Then, when the other queries return, I should do some array checking of dailyData matching the .count values with the one I got from the query, and that just feels wrong in my opinion. Not very elegant.
I tried another approach. Instead of having an array of a custom type, having a custom type that inside has an array of ints for every HKType that I need. But this has another problem: How could I "Keep in sync" the data from every HKType? having different arrays for every type would be, in my opinion, difficult to handle for example in a tableView. How could I set number of rows in section? Which type should I prefer? Also I would be accessing data over array indexes which may lead to bugs difficult to solve.
struct WeekData {
var steps: [Int]?
var distance: [Int]?
var calories: [Int]?
var exercise: [Int]?
}
class UserData {
let healthManager = HealthKitManager.shared
var weekData = WeekData()
.
.
.
}
I need to create a loop which will show pennies each day and total for the months end. I couldn't create the loop as the playground keeps on multiplying infinitely.
Great question! There are definitely a lot of ways to solve this problem, but if I understand your question - this is how I would solve it.
Step 1 - Date Extension
First, I would start by making an extension to Date.
extension Date {
/// The month component of the provided date.
var month: Int {
return Calendar.current.component(.month, from: self)
}
/// Exactly one day before the provided date.
var prevDay: Date {
return Calendar.current.date(byAdding: .day, value: -1, to: self)!
}
/// Exactly one day after the provided date.
var nextDay: Date {
return Calendar.current.date(byAdding: .day, value: 1, to: self)!
}
}
These variables will make some of the later work, easier.
Step 2 - Variables
Then we can setup the variables.
First, we get the start date - I am just instantiating a new Date object which defaults to right now.
Along with that, we will define a variable where we can keep track of where we are in the iteration.
Next, we need somewhere to keep track of the values over time.
Lastly, a value to hold the starting value.
let startDate = Date() // Today, now.
var iterDate = startDate
var vals: [Date: Int] = [:]
let startingValue: Int = 1
Step 3 - The Loop
Now, the fun part - the loop. This part will be documented in the code.
// Execute the loop until the end of the start date's month.
while iterDate.month == startDate.month {
// First, check if this is the first iteration -
if vals.count == 0 {
// If so, there is nothing to double, so we just set the starting value.
vals[iterDate] = startingValue
} else {
// If there are already values - get the previous days value, double it, and save.
if let val = vals[iterDate.prevDay] {
vals[iterDate] = val * 2
}
}
// Lastly, move to the next day.
iterDate = iterDate.nextDay
}
Step 4 - Final Value
Now that we have a dictionary of all of the values, as they grow, we can get the month-end value. First, sort the dictionary - then get the value. Getting the value this way means that you don't need to know the date.
let sortedVals = vals.sorted(by: { $0.0 < $1.0 })
if let monthEnd = sortedVals.last {
let monthEndVal = monthEnd.1
// Use the value, here.
}
There it is - hope that solves the problem!
Full Code
import Foundation
extension Date {
/// The month component of the provided date.
var month: Int {
return Calendar.current.component(.month, from: self)
}
/// Exactly one day before the provided date.
var prevDay: Date {
return Calendar.current.date(byAdding: .day, value: -1, to: self)!
}
/// Exactly one day after the provided date.
var nextDay: Date {
return Calendar.current.date(byAdding: .day, value: 1, to: self)!
}
}
let startDate = Date() // Today, now.
var iterDate = startDate
var vals: [Date: Int] = [:]
let startingValue: Int = 1
// Execute the loop until the end of the start date's month.
while iterDate.month == startDate.month {
// First, check if this is the first iteration -
if vals.count == 0 {
// If so, there is nothing to double, so we just set the starting value.
vals[iterDate] = startingValue
} else {
// If there are already values - get the previous days value, double it, and save.
if let val = vals[iterDate.prevDay] {
vals[iterDate] = val * 2
}
}
// Lastly, move to the next day.
iterDate = iterDate.nextDay
}
let sortedVals = vals.sorted(by: { $0.0 < $1.0 })
if let monthEnd = sortedVals.last {
let monthEndVal = monthEnd.1
// Use the value, here.
}
Realm doesn't support DateInterval to be store into the database. For now our team do the following:
private let _intervalBegins = List<Date>()
private let _intervalEnds = List<Date>()
var dateIntervals: [DateInterval] {
get {
var intervals = [DateInterval]()
for (i, startDate) in _intervalBegins.enumerated() {
let endDate = _intervalEnds[i]
intervals.append(DateInterval(start: startDate, end: endDate))
}
return intervals
}
set {
_intervalBegins.removeAll()
_intervalBegins.append(objectsIn: newValue.compactMap{ $0.start })
_intervalEnds.removeAll()
_intervalEnds.append(objectsIn: newValue.compactMap{ $0.end })
}
}
Is there a more "proper" way to do this? Maybe to store both the start and end dates into one property/database column? And get those value directly without "parsing" them with another variable as we do now.
Thanks!
As you notice, Realm doesn't support DateInterval, but Realm is able to save your custom objects. In this case you can create your own RealmDateInterval (or so) and create initializer, that allows you to create object from DateInterval:
dynamic var start: Date = Date()
dynamic var end: Date = Date()
convenience init(dateInterval: DateInterval) {
self.init()
self.start = dateInterval.start
self.end = dateInterval.end
}
Next thing, when you retrieve RealmDateInterval from Realm you really want DateInterval instead. Here you can create a bridge function, that can convert RealmDateInterval to DateInterval or create a protocol with convert func and
adopt it to RealmDateInterval (i.e. clearly show everybody RealmDateInterval has specific functionality).
protocol DateIntervalConvertible {
func toDateInterval() -> DateInterval
}
extension RealmDateInterval: DateIntervalConvertible {
func toDateInterval() -> DateInterval {
return DateInterval(start: start, end: end)
}
}
My idea is to make an ios-app that displays today's lunch at my work. I'm not sure how to approach this. My own thought was to have a UIDatePicker (default set to current day) and have functions that will respond to different dates. This is some code just to illustrate the ideas in my head.
var dateFromPicker = UIDatePicker.date
#IBOutlet weak var lunchLabel: UILabel!
func februaryFirst {
let dateFebruaryFirst = ...
if dateFromPicker = dateFebruaryFirst {
lunchLabel.text = ("Fish'n chips")
}
}
func februarySecond {
let dateFebruarySecond = ...
if dateFromPicker = dateFebruarySecond {
lunchLabel.text = ("Noodlesoup")
}
}
You probably want to use DateComponents to check which day/month a certain date is. For example:
func februarySecond {
// Get the day and month of the given date
let dateFromPickerComponents = Calendar.current.dateComponents([.day, .month], from: dateFromPicker)
// Check whether the day and month match Feb 2
if dateFromPickerComponents.day == 2 && dateFromPickerComponents.month == 2 {
lunchLabel.text = ("Noodlesoup")
}
}
You could use a switch statement which supports multiple values:
let dateFromPicker = UIDatePicker.date
let components = Calendar.current.dateComponents([.month, .day], from: dateFromPicker)
switch (components.month!, components.day!) { // first month then day
case (1,14): print("suprise")
case (2,1): print("Fish'n chips")
case (2,2): print("Noodlesoup")
default: print("fast day")
}
I'm trying to build an array of data from 3000 consecutive days but the code will only extract data from one single date 3000 times.
When I print the date that I keep increasing (theincreasingnumberofdays) I can see that it is adding one day to the date every time it loops, but when I read out the Array after the loop it's all the same data 3000 times.
class day
{
var week: Int?
var date: Int?
var month: Int?
var year: Int?
var weekday: Int?
}
#IBAction func pressbuttontodefinearray(sender: AnyObject) {
print("button has been pressed")
if (theArrayContainingAllTheUserData.count > 1) {
print("array contains something allready and we don't do anything")
return
}
print("array is empty and we're filling it right now")
var ScoopDay = day()
var numberofloops = 0
while theArrayContainingAllTheUserData.count < 3000
{
var theincreasingnumberofdays: NSDate {
return NSCalendar.current.date(byAdding: .day, value: numberofloops, to: NSDate() as Date)! as NSDate
}
print(theincreasingnumberofdays)
let myCalendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian)!
// var thenextdate = calendar.date(byAdding: .day, value: numberofloops, to: date as Date)
numberofloops=numberofloops+1
ScoopDay.week=Int(myCalendar.component(.weekday, from: theincreasingnumberofdays as Date!))
ScoopDay.date=Int(myCalendar.component(.day, from: theincreasingnumberofdays as Date!))
ScoopDay.month=Int(myCalendar.component(.month, from: theincreasingnumberofdays as Date!))
ScoopDay.year=Int(myCalendar.component(.year, from: theincreasingnumberofdays as Date!))
ScoopDay.weekday=Int(myCalendar.component(.weekday, from: theincreasingnumberofdays as Date!))
theArrayContainingAllTheUserData.append(ScoopDay)
}
print("we're done with this looping business. Let's print it")
var placeinarray = 0
while placeinarray < 2998
{
print("Here is", placeinarray, theArrayContainingAllTheUserData[placeinarray].date, theArrayContainingAllTheUserData[placeinarray].month)
placeinarray=placeinarray+1
}
return
}
The problem is that there is one day object, named ScoopDay, and you are adding that one object to the array 3000 times. So the array ends up with 3000 references to that one single object, which contains the last values you assigned to it.
You can fix this by moving the line
var ScoopDay = day()
inside the loop. That way you will create 3000 different day objects, each with different contents.
A Swift style tip: capitalize the first letter of class names, and lowercase the first letter of variable names, so:
class Day
and
var scoopDay = Day()