SwiftUICharts not changing color on bar chart - swiftui

I am trying to build a watchOS app that has charting and I can't use Swifts built in charts because I need to support down to version 7 and swift charts are only available for watchOS 9+. So instead I am using a library I found here...
https://github.com/willdale/SwiftUICharts
It has some sample and examples, trying to follow them I was able to get the chart to show up and customize it some, but I can't get the bar chart items to change their color. It seems simple enough, but for whatever reason I can't get it to actually change the color.
I'll provide a simple sample that should help show what's going on.
struct ChartView: View {
var items: [TimeEntries] = [
TimeEntry(dateString: "01/23/2023", entry: 97, timestamp: Date().millisecondsSince1970)]
var body: some View {
let chartData = makeData(items)
BarChart(chartData: chartData)
.touchOverlay(chartData: chartData)
.padding()
}
private func makeData(_ items: [TimeEntries]) -> BarChartData {
var data: [BarChartDataPoint] = [BarChartDataPoint]()
for item in items {
let stat = BarChartDataPoint(
value: Double(item.entry),
xAxisLabel: "Wed",
date: Date(milliseconds: entry.timestamp),
colour: ColourStyle(colour: Color.purple)
)
data.append(stat)
}
let dataSet = BarDataSet(dataPoints: data)
return BarChartData(dataSets: dataSet)
}
}
That should give me an entry on my bar chart with purple filling, I simplified this for sake of ease of posting, my real data has 7 points in it.
However, what actually happens is I have a single red bar on my chart. I am not using red anywhere in the app at all, but it won't take the color that I specify in the colour property of the BarChartDataPoint.
I know it's based on a library, but hopefully someone here will have used this library and will know what I have done wrong. I'll attach a screenshot of the chart so you can see. Thank you.

Related

How to set the background of a SwiftUI accessoryCorner watch widget?

I want to setup a very simple accessoryCorner SwiftUI widget, but it displays ugly.
This is my code:
struct WidgetView: View {
#Environment(\.widgetFamily) var widgetFamily
var body: some View {
switch widgetFamily {
case .accessoryCorner:
Image(systemName: "cart")
.widgetLabel {
Text("Label")
}
default:
Text("?")
}
}
}
This yields the following watch face:
For some reason, the image (the cart) is displayed in white color on a nearly white background, i.e. it cannot be seen.
I tried various methods to set a better background, e.g. ZStack with AccessoryWidgetBackground(), background(Color.clear), etc., but none worked.
How to display the image without a background, like the day (DI) in the left upper corner?
I contacted Apple and got the following answer:
We have reviewed your request and have concluded that there is no
supported way to achieve the desired functionality given the currently
shipping system configurations.

How can I update/save a Property Change for Child Item from a Hierarchical List View of Items

See the following app screens:
Content View Screen:
Content View with hierarchical list children rows disclosed:
Parent Row Detail View:
Child Row Detail View:
Referencing the above views, here are the steps I do and the resulting problem I’m trying to solve:
Launch the app.
From the Functions (Content View) presented at launch, see that there is one item listed in a list view (1.0 Move Vessel)
Click the yellow (my app accent color) disclosure arrow at the right of the list item.
Two subordinate child list rows appear under the parent list item, 1.1 Move Position and 1.2 Hold Position.
When I tap the parent item (1.0 Move Vessel) in the hierarchy list, I'm successfully able to navigate to a detail view for that tapped item.
Edit the description of the 1.0 Move Vessel item (defaults to test) of the tapped item properties in the detail view using a TextEditor view.
Click yellow Save button at top left of detail view. The app navigates back to the parent Functions (Content View).
Click on the parent 1.0 Move Vessel row again.
See that description was successfully saved and now displayed from the change made in Step 5 and 6.
Repeat steps 5 through 8 again for 1.1 Move Position list row.
See that the edit/change made to the description was not saved and the default test1 description is displayed instead (not what is wanted).
Repeat steps 5 through 8 again for 1.2 Hold Position list row.
See that the edit/change made to the description was not saved and the default test2 description is displayed instead (not what is wanted).
I think I may have a problem in my save code logic and I'm trying to investigate.
Here are the swift files for the Detail View, the View Model, and the Model (I’ve not included the content view code because that code is working ok with the detail view. Again, I think the problem is in my save button and function call code for updating the view model.
NOTE: sorry that I can’t seem to figure out how to get all the code for a file contiguous in the code view. I seem to have some closing braces that don’t appear in the code view. I think you can still follow the code.
struct FunctionDetailView: View {
#State var vesselFunction: VesselFunction
#State var vesselFunctionDescription: String
#Environment(\.presentationMode) var presentationMode
#EnvironmentObject var functionViewModel : FunctionViewModel
var body: some View {
NavigationView {
Form {
Text("Enter description below")
TextEditor(text: $vesselFunctionDescription)
.frame(height: 200)
.toolbar {
Button {
//print(vesselFunction)
vesselFunction.funcDescription = vesselFunctionDescription
//print(vesselFunction)
functionViewModel.updateVesselFunction(vesselFunction: vesselFunction)
//print(vesselFunction)
presentationMode.wrappedValue.dismiss()
} label: {
Text("Save")
}
}
}
.padding()
.navigationTitle(vesselFunction.name)
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct FunctionDetailView_Previews: PreviewProvider {
static var previews: some View {
FunctionDetailView(vesselFunction: VesselFunction(id: UUID(), name: "x.x Verb Noun", funcDescription: "Description", children: nil), vesselFunctionDescription: "placeholder")
.environmentObject(FunctionViewModel())
.preferredColorScheme(.dark)
}
}
FunctionViewModel.swift
#MainActor class FunctionViewModel: ObservableObject {
#Published private(set) var decomp : [VesselFunction] = [
VesselFunction(id: UUID(), name: "1.0 Move Vessel", funcDescription: "test", children: [
VesselFunction(id: UUID(), name: "1.1 Move Position", funcDescription: "test1", children: nil),
VesselFunction(id: UUID(), name: "1.2 Hold Position", funcDescription: "test2", children: nil)
])
]
func updateVesselFunction(vesselFunction: VesselFunction) {
/*
if let index = decomp.firstIndex(where: { (existingVesselFunction) -> Bool in
return existingVesselFunction.id == vesselFunction.id
}) {
//run this code
}
*/
// cleaner version of above
if let index = decomp.firstIndex(where: { $0.id == vesselFunction.id }) {
decomp[index] = vesselFunction.updateCompletion()
}
/*
else {
for item in decomp {
if item.children != nil {
if let index = item.children?.firstIndex(where: { $0.id == vesselFunction.id }) {
item.children![index] = vesselFunction.updateCompletion()
}
}
}
} */
}
}
FunctionModel.swift
struct VesselFunction: Identifiable {
let id : UUID
let name : String
var funcDescription : String
var children : [VesselFunction]?
init(id: UUID, name: String, funcDescription: String, children: [VesselFunction]?) {
self.id = id
self.name = name
self.funcDescription = funcDescription
self.children = children
}
func updateCompletion() -> VesselFunction {
return VesselFunction(id: id, name: name, funcDescription: funcDescription, children: children)
}
}
As you can see from the else and for-in loop code commented out at the bottom of the FunctionViewModel code, I was trying to see if I needed to do something like this code to access the children VesselFunction array entries of the decomp published property. With the if let index code that is not commented out, the save function works but only for the top-level decomp array VesselFunction elements, not the nested children arrays elements.
Any help would be appreciated so all decomp array elements, both parent and nested children, can be updated when the TextEditor field is changed and the Save button is pressed in the FunctionDetailView.
NOTE: I am only showing a 1 level deep nested array of children for the decomp property. I actually want to have multiple (at least 3) level of children arrays, so if you have any ideas how to make an updateVesselFunction function work for multiple children array elements, I would appreciate it.
In the main View use ForEach($model.items) { $item in so you get a write access to the model item. In the detail View change the #State to #Binding.
The issue isn't so much your code right now, as it is the architecture of the program. You really need to reorganize the app with MVVM concepts in mind. If you are not sure of them, study Apple’s SwiftUI Tutorials & Stanford’s CS193P. Without a proper architecture, you have gotten lost down a rabbit hole to the extent that I gave up trying to fix the code.
Also, given the structure of your data, I would give serious consideration to using Core Data to model it. Your VesselFunction struct contains an array of VesselFunction, and that it much better modeled as a relationship, rather than having a struct hold an array of the same struct which can hold an array of the same struct. It is a nightmare to deal with as a struct, instead of as a Core Data class.
I would also consider make your FunctionDetailView just display data, and have a separate editing view. This will keep your view separate and easier to manage.
Lastly, you have a lot of redundancy in your naming conventions. Theoretically, you could be trying to access a piece of data at functionViewModel.funcDescription (Not to mention: functionViewModel.children[index].children[subIndex].children[subSubIndex].funcDescription); this can get a bit unwieldy. The further you go down, the worse it will get.

In SwiftUI on Apple Watch, what is the best way to update a String that describes the interval since a Date with Always On State?

I'm building an Apple Watch app that uses SwiftUI.
New data periodically comes into the app from an outside source, and I keep track of when that last happened with a #Published Date (called lastUpdatedDate) inside an ObservableObject (store).
In the app, I want to use a SwiftUI Text struct to indicate to the user how long ago the data was updated.
I'm weighing different options, and I'm wondering what the best practice would be for something like this.
Solution #1 - Text.DateStyle
A very simple method I tried was using Text.DateStyle.relative:
Text(store.lastUpdatedDate, style: .relative)
This worked the way I wanted to some degree, because it kept the text up-to-date with the relative interval since the date had happened, but the text is not customizable. I do not want it to show the number of seconds, just minutes or hours.
Solution #2 - Computed Property
Inside the View where I want to display the Text, I have access to the ObservableObject, and added a computed property that uses a function that converts the Date to a String for the relative date (for example, 5 minutes ago) using an extension on Date with a RelativeDateTimeFormatter
Computed property:
private var lastUpdatedText: String {
store.lastUpdatedDate.timeAgoDisplay()
}
Date extension:
extension Date {
func timeAgoDisplay() -> String {
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .full
return formatter.localizedString(for: self, relativeTo: Date())
}
}
This seems like the best solution I've found so far, because it automatically updates every time the app is opened. The downside, however, is that it only seems to update when the app comes to the foreground (when another app or the Clock Face was previously active). This is probably fine, but it could be better on Series 5, Series 6, and Series 7, which have alway-on displays. On watchOS 8, with the Always On State, this text remains on the screen when the wrist is dropped, and when the wrist is raised again. The text can become outdated, because these events do not cause the lastUpdatedText computed property to be updated again.
Update: This is actually not a very good solution. Through more testing, I found out that it is a fluke that it was updating every time the app opened. It was only re-computing the property on app open because other items in the view were getting refreshed, and on its own it would not have actually re-computed every time the app was opened.
Other Possible Solutions
I've considered adding a second #Published variable in the ObservableObject that's a String containing the relative time interval. The downside of this is I would have to manually initialize and update that variable. If I do that, I could manually update the text based on lifecycle functions in ExtensionDelegate, like applicationWillEnterForeground(), but that only handles when the app first comes to the foreground (which is the same update frequency as the computed property). I haven't found any way to detect when the wrist is dropped or raised again, so it seems the only way I could keep it up to date is to set up one or more published Timers, and update the #Published String every minute, disabling and setting up the timer(s) every time the app goes into the background and returns to the foreground. Does that seem like a good solution? Is there a better solution I'm overlooking?
Solution
Based on the accepted answer, I was able to use a TimelineView to update periodically.
Even though I am only using minute granularity for the text that's displayed, I wanted the text to update to the second when the data was actually refreshed. To accomplish that part, I started by adding a new extension for Date:
extension Date {
func withSameSeconds(asDate date: Date) -> Date? {
let calendar = Calendar.current
var components = calendar.dateComponents([.era, .year, .month, .day, .hour, .minute, .second], from: self)
let secondsDateComponents = calendar.dateComponents([.era, .year, .month, .day, .hour, .minute, .second], from: date)
components.second = secondsDateComponents.second
return calendar.date(from: components)
}
}
Then, I created my view used the TimelineView to update the text every minute after the data is refreshed:
struct LastUpdatedTextView: View {
#ObservedObject var store: DataStore
var body: some View {
if let nowWithSameSeconds = Date().withSameSeconds(asDate: store.lastUpdatedDate) {
TimelineView(PeriodicTimelineSchedule(from: nowWithSameSeconds,
by: 60))
{ context in
Text(store.lastUpdatedText(forDate: lastUpdatedDate))
}
} else {
EmptyView()
}
}
}
Apple addresses this in Build A Workout App from WWDC21
They use TimelineView and context.cadence == .live to tell their formatter to not show milliseconds when the watch is in Alway on.
You could use that code to determine what to show.
struct TimerView: View {
var date: Date
var showSubseconds: Bool
var fontWeight: Font.Weight = .bold
var body: some View {
if #available(watchOSApplicationExtension 8.0, watchOS 8.0, iOS 15.0, *) {
//The code from here is mostly from https://developer.apple.com/wwdc21/10009
TimelineView(MetricsTimelineSchedule(from: date)) { context in
ElapsedTimeView(elapsedTime: -date.timeIntervalSinceNow, showSubseconds: context.cadence == .live)
}
} else {
Text(date,style: .timer)
.fontWeight(fontWeight)
.clipped()
}
}
}
#available(watchOSApplicationExtension 8.0, watchOS 8.0, iOS 15.0,*)
private struct MetricsTimelineSchedule: TimelineSchedule {
var startDate: Date
init(from startDate: Date) {
self.startDate = startDate
}
func entries(from startDate: Date, mode: TimelineScheduleMode) -> PeriodicTimelineSchedule.Entries {
PeriodicTimelineSchedule(from: self.startDate, by: (mode == .lowFrequency ? 1.0 : 1.0 / 30.0))
.entries(from: startDate, mode: mode)
}
}
struct ElapsedTimeView: View {
var elapsedTime: TimeInterval = 0
var showSubseconds: Bool = false
var fontWeight: Font.Weight = .bold
#State private var timeFormatter = ElapsedTimeFormatter(showSubseconds: false)
var body: some View {
Text(NSNumber(value: elapsedTime), formatter: timeFormatter)
.fontWeight(fontWeight)
.onChange(of: showSubseconds) {
timeFormatter.showSubseconds = $0
}
.onAppear(perform: {
timeFormatter = ElapsedTimeFormatter(showSubseconds: showSubseconds)
})
}
}

Nested ForEach (and List) in Views give unexpected results

Doing a ForEach within another ForEach in a SwiftUI View produces unexpected results - almost like they are stepping on each other's counters. Not clear as to what's happening. I need to display multi branched arrays and have tried a number of variants but keep running into the same problem
I have a few projects where this has come up. Tries using ranges (0..
Is this just a beta issue or am I missing something? I've included an example project that demonstrated the problem.
using XCode 11 Beta (11M392r)
Thanks!
import SwiftUI
struct ContentView: View {
let topTier:[String] = ["Apple", "Banana", "Cherry"]
let nextTier:[String] = ["Abalone", "Brie", "Cheddar"]
var body: some View {
List {
ForEach (topTier.indices, id: \.self) { a in
Text(self.topTier[a])
ForEach (self.nextTier.indices, id: \.self) { b in
Text(self.nextTier[b]).padding(.leading, 20)
}
}
}
}
}
throws "Ambiguous reference to member 'indices'" on the fitst ForEach.
If the inner ForEach is commented it works displaying the fruits
If the outter ForEach is commented it works displaying the cheeses
I want it to display:
Apple
Abalone
Brie
Cheddar
Banana
Abalone
Brie
Cheddar
Cherry
Abalone
Brie
Cheddar
As with many of the errors that come out of the new #ViewBuilder syntax with SwiftUI during this beta cycle, the “Ambiguous reference…” message is a red herring. Your issue is that the ForEach closure expects a single view, not a view builder.
You can get your view working by simply wrapping the body of the ForEach closure in a Group, like so:
import SwiftUI
struct ContentView: View {
let topTier:[String] = ["Apple", "Banana", "Cherry"]
let nextTier:[String] = ["Abalone", "Brie", "Cheddar"]
var body: some View {
List {
ForEach (topTier.indices, id: \.self) { a in
Group {
Text(self.topTier[a])
ForEach (self.nextTier.indices, id: \.self) { b in
Text(self.nextTier[b]).padding(.leading, 20)
}
}
}
}
}
}
And voilà:
Not sure I completely understand your problem, but I had a similar, potentially related issue.
SwiftUI Nested ForEach causes unexpected ordering
I received an answer that specified that each of the cells need a different identifier, which could also be your problem.
Each time the nested ForEach is executed, the id's that are generated are the same, which results in there being lots of cells that have the same ID, and thus the unexpected reorderings etc. appear. In the linked post you can see how I solved this issue.
Summary: Each cell needs a unique ID, as you can see in this screenshot where each of the cells from section 7 and 8 have different ids.

How to change the Chart.js legend rectangles to squares

I am using the Chart.js library for creating charts and everything is working fine, but my client wants to have squares in the legend instead of rectangles (I know it's a small thing, but the client is the king). Of course I could make my own legend with some HTML and CSS, but I would prefer to keep using only the library for my charts.
I hope that someone has a solution for this.
You can generate a custom legend (HTML string) like this:
var chart = new Chart(ctx, {
type: 'line',
data: data,
options: {
legendCallback: function(chart) {
// Return the HTML string here.
}
}
});
Reference:
http://www.chartjs.org/docs/latest/configuration/legend.html