iOS 17 Updates: Enhancing Swift Charts

Hey Guys! Welcome to another blog on the latest iOS 17 updates for Swift charts. In the ever-evolving world of app development, data visualization plays a crucial role in conveying information effectively and engaging users. With the advent of iOS 17, Apple has introduced a range of exciting enhancements to the Swift charts framework, empowering developers to create stunning and interactive visual representations of data like never before.

lyvennitha sasikumar
9 min readJul 12, 2023

Introduction

In this blog, we will take a deep dive into the new features and improvements introduced in iOS 17, focusing specifically on Swift charts. Whether you’re a seasoned iOS developer or just starting your journey, this guide will equip you with the knowledge and insights needed to leverage the power of data visualization in your applications.

From customizable chart styles to dynamic animations and enhanced interactivity, iOS 17 brings a wealth of possibilities for creating visually captivating charts that seamlessly integrate with your app’s user interface. We’ll explore the new functionalities, demonstrate their usage through code examples, and discuss best practices for incorporating these updates into your projects.

Swift Chart

To effectively utilize the Swift charts framework in iOS 17, it’s essential to structure your data appropriately. Here’s an example of a model class for your chart data.

struct ChartData {
var name: String
var sales: Double
}

In the above code, we define a ChartData struct that represents a single data point on the chart. It contains two properties: name, which represents the label or category for the data point, and sales, which represents the corresponding sales value.

To populate your chart with data, you can create an array of ChartData instances like this.

let data: [ChartData] = [
.init(name: "Jan", sales: 28.5),
.init(name: "Feb", sales: 35.5),
.init(name: "Mar", sales: 45.8),
.init(name: "Apr", sales: 1.8),
.init(name: "May", sales: 10.0),
.init(name: "Jun", sales: 15.8),
.init(name: "Jul", sales: 11.8),
.init(name: "Aug", sales: 18.8)
]

In the above code snippet, we create an array called data that holds multiple ChartData instances. Each instance represents a specific month and its corresponding sales value. You can modify this example to include your actual data points and their respective values.

we will explore the implementation of four popular chart types using Swift Charts: pie chart, bar chart, line chart, and progress bar chart. Along the way, we’ll dive into the innovative Section Mark and see how it can elevate your charting capabilities. Let’s get started!

In the latest iOS 17 update for Swift charts, a new feature called SectorMark has been introduced, allowing you to create visually stunning pie charts. With SectorMark, you can effortlessly represent data as slices within a circular chart. Let's take a closer look at how to utilize this exciting enhancement.

 Chart (data, id: \.name) { element in
SectorMark(
angle: .value("Sales", element.sales),
innerRadius: .ratio(0.618),
angularInset: 1.5
)
.cornerRadius(5)
.foregroundStyle(by: .value("Name", element.name))
}

The angle parameter in the init method of SectorMark allows you to define the angular position of the sector mark within the full circle. It accepts an instance of PlottableValue<Angle> as its value, which can be of type .value or .range.

Here’s a breakdown of the SectorMark parameter and its options:

  • .value: With .value, you can proportionally map the angle within the full circle based on a specific value. This allows the size of the sector to dynamically adjust relative to other data points.
  • .range: Alternatively, you can use .range to explicitly define the start and end angles for the sector. This allows for more precise control over the angular position of the mark.

The Angle type in the PlottableValue<Angle> constraint represents the type of the angle value or range. It should conform to the Plottable protocol, indicating that it can be plotted on a chart.

By utilizing the angle parameter and its options, you can effectively map angular positions to create sector marks that accurately represent your data within the pie chart.

This code have empty mid space in pie chart.

To add a background to a view containing a chart using the chartBackground modifier in Swift Charts, you can use the following code as an example:

chartBackground { chartProxy in
GeometryReader { geometry in
let frame = geometry[chartProxy.plotAreaFrame]
VStack {
Text("Most Sold Style")
.font(.callout)
.foregroundStyle(.secondary)
Text("March")
.font(.title2.bold())
.foregroundColor(.primary)
}
.position(x: frame.midX, y: frame.midY)
}
}

In the above code, we wrap the view containing the chart with the chartBackground modifier. Inside the chartBackground closure, we have access to the ChartProxy object, which allows us to interact with the chart.

Within the closure, we use a GeometryReader to get the frame of the plot area (chartProxy.plotAreaFrame). This frame represents the available space for the chart within the view.

We then create a VStack containing two Text views, representing the background content. You can customize the content as needed for your specific use case.

Finally, we position the VStack in the center of the plot area by setting its position to frame.midX and frame.midY.

By using the chartBackground modifier and customizing the background content, you can enhance the visual appeal of your chart and provide additional context or information to the users.

Adjust the content and styling according to your requirements.

Before that we will create view for different styling option using switch design with pie chart selection as default.

First of all, we will create viewModel to handle this.

class ChartsViewModel: ObservableObject{
@Published var graphType: GraphType = GraphType()
}

struct GraphType: Equatable{
var isBarChart: Bool = false
var isProgressChart: Bool = false
var isPieChart: Bool = true
var isLineChart: Bool = false
}

The GraphType struct holds boolean properties representing different types of charts: isBarChart, isProgressChart, isPieChart, and isLineChart.

The view code allows the user to switch between different chart types using toggles. The @Published property wrapper on the graphType in the ChartsViewModel class ensures that changes to the selected graph type trigger UI updates.

Let’s break down the implementation:

ChartsViewModel:

  • The graphType property of the ChartsViewModel class is marked with the @Published property wrapper, making it observable and triggering updates to any subscribed views when its value changes.
  VStack{
Text("Monthly Progress")
.font(.title)
.bold()

HStack{
VStack{
Toggle(isOn: $viewModel.graphType.isBarChart){
Text("Bars")
.foregroundColor(Color.black)
}
.onChange(of: viewModel.graphType.isBarChart) { oldValue, newValue in
if newValue{
viewModel.graphType.isPieChart = false
viewModel.graphType.isLineChart = false
viewModel.graphType.isProgressChart = false
}
}
Toggle(isOn: $viewModel.graphType.isPieChart){
Text("Pie")
.foregroundColor(Color.black)
}
.onChange(of: viewModel.graphType.isPieChart) { oldValue, newValue in
if newValue{
viewModel.graphType.isBarChart = false
viewModel.graphType.isLineChart = false
viewModel.graphType.isProgressChart = false
}
}
}
VStack{
Toggle(isOn: $viewModel.graphType.isProgressChart){
Text("Progress")
.foregroundColor(Color.black)
}
.onChange(of: viewModel.graphType.isProgressChart) { oldValue, newValue in
if newValue{
viewModel.graphType.isBarChart = false
viewModel.graphType.isLineChart = false
viewModel.graphType.isPieChart = false
}
}
Toggle(isOn: $viewModel.graphType.isLineChart){
Text("Line")
.foregroundColor(Color.black)
}
.onChange(of: viewModel.graphType.isLineChart) { oldValue, newValue in
if newValue{
viewModel.graphType.isBarChart = false
viewModel.graphType.isPieChart = false
viewModel.graphType.isProgressChart = false
}
}
}
}
.padding()
}
}
  • The VStack wraps the UI elements responsible for displaying and toggling the chart types.
  • Each Toggle represents a specific chart type and is bound to the corresponding boolean property in the viewModel.graphType.
  • The onChange modifier is used to observe changes in the boolean properties and update the other chart type properties accordingly. When a chart type is selected, the other chart type properties are set to false, ensuring only one chart type is active at a time.
  • The chart type options are divided into two VStacks to create a visually appealing layout.

By using the ChartsViewModel class and toggles to switch between chart types, you can dynamically update the displayed chart based on user selection. This approach provides flexibility and allows users to interactively explore different types of charts within your application.

The code you provided demonstrates the usage of the BarMark in Swift Charts for creating bar charts. Let's break it down and explain each component.

BarMark(
x: .value("Month", element.name),
y: .value("Sales", element.sales)
)
.foregroundStyle(by: .value("Type", element.name))
  • BarMark: The BarMark represents an individual bar in the bar chart. It is used to define the position and dimensions of the bar.
  • x: .value("Month", element.name): The x property defines the position of the bar on the x-axis. In this case, it uses .value to associate the label "Month" with the element.name, which represents the month. This specifies the category or group that the bar belongs to.
  • y: .value("Sales", element.sales): The y property determines the height or value of the bar on the y-axis. It uses .value to associate the label "Sales" with the element.sales, which represents the sales value for that particular month.
  • .foregroundStyle(by: .value("Type", element.name)): The .foregroundStyle modifier allows you to set the styling of the bar. In this example, it uses .value to associate the label "Type" with the element.name, which can represent different types of bars. You can customize the appearance, such as color or other visual properties, based on the specific type of bar.

By utilizing the BarMark and customizing its properties, you can dynamically generate bars in your bar chart, associating them with relevant data points and applying different visual styles based on the specific type of bar.

BarMark(
x: .value("Sales", element.sales),
stacking: .normalized
)
.foregroundStyle(by: .value("Type", element.name))
  • BarMark: The BarMark is used to represent an individual bar in the progress bar chart. It defines the position and dimensions of the bar.
  • x: .value("Sales", element.sales): The x property specifies the value or length of the bar. In the case of a progress bar, the "Sales" label is associated with the element.sales value, which represents the progress or completion status. The length of the bar corresponds to the progress value.
  • stacking: .normalized: The stacking property determines how bars are stacked within the progress bar chart. Setting it to .normalized ensures that each bar's length is normalized to the overall length of the progress bar, resulting in a proportional representation of the progress.
  • .foregroundStyle(by: .value("Type", element.name)): The .foregroundStyle modifier is used to set the styling of the bar. In this example, the "Type" label is associated with the element.name, which can represent different types of bars. You can customize the appearance, such as color or other visual properties, based on the specific type of bar.

By using the BarMark with appropriate settings, you can create visually appealing progress bar charts that represent progress or completion status with different types of bars.

For line chart

LineMark(
x: .value("Month", element.name),
y: .value("Sales", element.sales)
)
  • LineMark: The LineMark represents a line segment in the line chart. It is used to define the position and shape of the line segment.
  • x: .value("Month", element.name): The x property specifies the position of the data point on the x-axis. In this case, it uses .value to associate the label "Month" with the element.name, which represents the month. This determines the horizontal position of the data point along the x-axis.
  • y: .value("Sales", element.sales): The y property determines the position of the data point on the y-axis. It uses .value to associate the label "Sales" with the element.sales, which represents the sales value for that particular month. This determines the vertical position of the data point along the y-axis.

The LineMark is used to connect multiple data points, resulting in a line chart where the data points are visually connected by line segments.

PointMark(
x: .value("Month", element.name),
y: .value("Sales", element.sales)
)
  • PointMark: The PointMark represents an individual data point in the line chart. It is used to define the position of the data point.
  • x: .value("Month", element.name): The x property specifies the horizontal position of the data point on the x-axis. It associates the label "Month" with the element.name, representing the month.
  • y: .value("Sales", element.sales): The y property determines the vertical position of the data point on the y-axis. It associates the label "Sales" with the element.sales, representing the sales value for that particular month.

The PointMark is used to plot individual data points on the line chart, indicating specific values along the x-axis and y-axis.

By utilizing both LineMark and PointMark elements, you can create a line chart with connected data points represented by line segments and individual data points plotted on the chart.

Final code looks like this

import SwiftUI
import Charts

struct ContentView: View {

@StateObject var viewModel = ChartsViewModel()

var body: some View {
VStack{

Text("Monthly Progress")
.font(.title)
.bold()

HStack{
VStack{
Toggle(isOn: $viewModel.graphType.isBarChart){
Text("Bars")
.foregroundColor(Color.black)
}
.onChange(of: viewModel.graphType.isBarChart) { oldValue, newValue in
if newValue{
viewModel.graphType.isPieChart = false
viewModel.graphType.isLineChart = false
viewModel.graphType.isProgressChart = false
}
}
Toggle(isOn: $viewModel.graphType.isPieChart){
Text("Pie")
.foregroundColor(Color.black)
}
.onChange(of: viewModel.graphType.isPieChart) { oldValue, newValue in
if newValue{
viewModel.graphType.isBarChart = false
viewModel.graphType.isLineChart = false
viewModel.graphType.isProgressChart = false
}
}
}
VStack{
Toggle(isOn: $viewModel.graphType.isProgressChart){
Text("Progress")
.foregroundColor(Color.black)
}
.onChange(of: viewModel.graphType.isProgressChart) { oldValue, newValue in
if newValue{
viewModel.graphType.isBarChart = false
viewModel.graphType.isLineChart = false
viewModel.graphType.isPieChart = false
}
}
Toggle(isOn: $viewModel.graphType.isLineChart){
Text("Line")
.foregroundColor(Color.black)
}
.onChange(of: viewModel.graphType.isLineChart) { oldValue, newValue in
if newValue{
viewModel.graphType.isBarChart = false
viewModel.graphType.isPieChart = false
viewModel.graphType.isProgressChart = false
}
}
}
}
.padding()
Chart (data, id: \.name) { element in
if viewModel.graphType.isPieChart{
SectorMark(
angle: .value("Sales", element.sales)
, innerRadius: .ratio(0.618), angularInset: 1.5
)
.cornerRadius (5)
.foregroundStyle(by: .value ("Name", element.name))
}

if viewModel.graphType.isBarChart{
BarMark(
x: .value("Month", element.name),
y: .value("Sales", element.sales)
)
.foregroundStyle(by: .value("Type", element.name))
}
if viewModel.graphType.isProgressChart{
BarMark(
x: .value("Sales", element.sales),
stacking: .normalized
)
.foregroundStyle(by: .value("Type", element.name))
}

if viewModel.graphType.isLineChart{
LineMark(
x: .value("Month", element.name),
y: .value("Sales", element.sales)
)

PointMark(
x: .value("Month", element.name),
y: .value("Sales", element.sales)
)
}

}
.frame(height: viewModel.graphType.isProgressChart ? 100: 380)
.chartBackground { chartProxy in
if viewModel.graphType.isPieChart{
GeometryReader { geometry in
let frame = geometry[chartProxy.plotAreaFrame]
VStack {
Text ("Most Sold Style")
.font (.callout)
.foregroundStyle(.secondary)
Text ("March")
.font(.title2.bold ())
.foregroundColor (.primary)
}
.position(x: frame.midX, y: frame.midY)
}
}
}
.padding()



}
.padding()
}
}

Enjoy reading my contents? Follow me fore more like this. As always I said,

HAPPY CODING!🏆

--

--