I'm working on a personal app and I want to update the code. Currently I'm using an external cocoapod's library named "Charts" to create my charts in my app (which is in Swift). However, I could notice that I can Implement SwiftUI's native "Charts" library and implement it in my code, but I'm not proficient on SwiftUI yet. I'm implementing different sources to achieve this (like this youtube tutorial and this other tutorial). Nevertheless, I would like to show 3 values per month like I show in the image called "01_3BarsPerMonth" (which was created using the "Charts" cocoapod library in the original project), but the closest thing I have achieved is as the one shown in the 2nd link I provided you (see image "02_CurrentResult") which is currently made with the SwiftUI native "Charts" library. I have searched other examples but they have more (and complex) code that for now, I don't need and I believe that with what I have in this moment, I might get the results I want.
In the test code I'm using now (a small practice app in order to get familiar with SwiftUI's "Charts" library), this is my ViewController class:
import SwiftUI
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
// MARK: - Setup view
private func setupView() {
view.backgroundColor = .lightGray
let horizontalConstraint: CGFloat = 10
//let controller = UIHostingController(rootView: SavingsHistory())
let controller = UIHostingController(rootView: OrdersHistory())
guard let savingsView = controller.view else { return }
savingsView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(savingsView)
NSLayoutConstraint.activate([
savingsView.topAnchor.constraint(equalTo: view.topAnchor),
savingsView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
savingsView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor, constant: horizontalConstraint),
savingsView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor, constant: -horizontalConstraint)
])
}
}
And this is the code of my struct:
import SwiftUI
import Charts
struct Order: Identifiable {
var id: String = UUID().uuidString
var amount: Int
var day: Int
}
struct OrdersHistory: View {
var ordersWeekOne: [Order] = [
Order(amount: 10, day: 1),
Order(amount: 7, day: 2),
Order(amount: 4, day: 3),
Order(amount: 13, day: 4),
Order(amount: 19, day: 5),
Order(amount: 6, day: 6),
Order(amount: 16, day: 7)
]
var ordersWeekTwo: [Order] = [
Order(amount: 20, day: 1),
Order(amount: 14, day: 2),
Order(amount: 8, day: 3),
Order(amount: 26, day: 4),
Order(amount: 27, day: 5),
Order(amount: 12, day: 6),
Order(amount: 32, day: 7)
]
var ordersWeekThree: [Order] = [
Order(amount: 5, day: 1),
Order(amount: 3, day: 2),
Order(amount: 2, day: 3),
Order(amount: 7, day: 4),
Order(amount: 10, day: 5),
Order(amount: 3, day: 6),
Order(amount: 8, day: 7)
]
var body: some View {
Chart {
ForEach(ordersWeekOne, id: \.id) { order in
LineMark(
x: PlottableValue.value("Week 1", order.day),
y: PlottableValue.value("Orders 1", order.amount),
series: .value("Week", "One")
)
.foregroundStyle(Color.red)
}
ForEach(ordersWeekTwo, id: \.id) { order in
LineMark(
x: PlottableValue.value("Week 2", order.day),
y: PlottableValue.value("Orders 2", order.amount)
,
series: .value("Week", "Two")
)
.foregroundStyle(Color.green)
}
ForEach(ordersWeekThree, id: \.id) { order in
LineMark(
x: PlottableValue.value("Week 3", order.day),
y: PlottableValue.value("Orders 3", order.amount)
,
series: .value("Week", "Three")
)
.foregroundStyle(Color.black)
}
}
}
}
I've tried modifying the example above but I always get an error complaint such as "[OrdersHistory] must conform to Identifiable" or something like that when I try to create an object of type [[OrdersHistory]].
What can I modify to get a result as in the first image (3 values for the month of January, for instance).
I appreciate your feedback. Kind regards.
I would start by refactoring your Order
type to include the week number
struct Order: Identifiable {
var id: String = UUID().uuidString
var week: Int
var amount: Int
var day: Int
}
Then you can have a single array as the data source
var orders: [Order] = [
Order(week: 1, amount: 10, day: 1),
Order(week: 1, amount: 7, day: 2),
...
Order(week: 3, amount: 3, day: 6),
Order(week: 3, amount: 8, day: 7)
]
Then we need to use strings for our labels/groups instead of Int
extension Order {
var weekLabel: String {
"\(week)"
}
var dayLabel: String {
Calendar.current.weekdaySymbols[day-1]
}
}
And now we can use those labels and the array for the chart
Chart {
ForEach(orders) { order in
BarMark(
x: .value("Day", order.dayLabel),
y: .value("Amount", order.amount)
)
.foregroundStyle(by: .value("Week", order.weekLabel))
.position(by: .value("Week", order.weekLabel))
}
}
If you want to work with the 3 different arrays you can join them into an array of arrays
let ordersWeek: [[Order]] = [
[
Order(amount: 10, day: 1),
Order(amount: 7, day: 2),
//...
], [
Order(amount: 20, day: 1),
Order(amount: 14, day: 2),
//...
], [
Order(amount: 5, day: 1),
Order(amount: 3, day: 2),
//...
]
]
And then use stacking as suggested by @Sweeper
Chart {
ForEach(ordersWeek.indices, id: \.self) { index in
ForEach(ordersWeek[index]) { order in
BarMark(
x: .value("Day", order.dayLabel),
y: .value("Amount", order.amount),
stacking: MarkStackingMethod.standard)
.foregroundStyle(by: .value("Week", "\(index + 1)"))
.position(by: .value("Week", "\(index)"))
}
}
}
An alternative way to creating the array of arrays from the original array properties is to use a computed property:
var ordersWeek: [[Order]] {
[ordersWeekOne, ordersWeekTwo, ordersWeekThree]
}