SwiftUI Pie Charts
SwiftUI Charts support creating pie charts since iOS 17. To create a pie chart, use a `Chart` view and place `SectorMark`s inside.
With the parameter `innerRadius` a ring chart can be created. Data labels can be inserted with the `.annotation` modifier.
import SwiftUI
import Charts
struct SamplePieChartView: View {
struct Record: Identifiable {
let id: UUID = UUID()
var name: String
var value: Double
}
@State private var earnings: [Record] = []
private let demoEarnings: [Record] = [
Record(name: "Tax Return", value: 50),
Record(name: "Interest", value: 70),
Record(name: "Payment", value: 1000)
]
@State private var expenses: [Record] = []
private let demoExpenses: [Record] = [
Record(name: "Groceries", value: 80),
Record(name: "Insurance", value: 100),
Record(name: "Bicycle repair", value: 20)
]
var body: some View {
Text("SwiftUI Pie Chart")
.font(.largeTitle)
VStack(spacing: 16) {
VStack {
let total = earnings.reduce(0) { $0 + $1.value }
HStack {
Text("Earnings")
Text(total, format: .currency(code: Locale.current.currency?.identifier ?? "$"))
}
.font(.title2)
if #available(iOS 17.0, *) {
Chart(earnings) { record in
SectorMark(angle: .value(
Text(verbatim: record.name),
record.value / total))
.foregroundStyle(by: .value(
Text(verbatim: record.name),
record.name
))
}
} else {
Text("iOS 17.0 required")
.foregroundStyle(.secondary)
}
}
.padding(12)
.background {
Color.white
}
.clipShape(RoundedRectangle(cornerRadius: 16))
VStack {
let total = expenses.reduce(0) { $0 + $1.value }
HStack {
Text("Expenses")
Text(total, format: .currency(code: Locale.current.currency?.identifier ?? "$"))
}
.font(.title2)
if #available(iOS 17.0, *) {
Chart(expenses) { record in
SectorMark(angle: .value(
Text(verbatim: record.name),
record.value / total), innerRadius: .ratio(0.5), angularInset: 4)
.annotation(position: .overlay, alignment: .center) {
VStack(spacing: 0) {
Text("\(record.name)")
Text(record.value, format: .currency(code: Locale.current.currency?.identifier ?? "$"))
}
.padding(4)
.clipShape(RoundedRectangle(cornerRadius: 8))
.background {
Color.white
.clipShape(RoundedRectangle(cornerRadius: 8))
.shadow(radius: 4)
}
}
.foregroundStyle(by: .value(
Text(verbatim: record.name),
record.name
))
}
} else {
Text("iOS 17.0 required")
.foregroundStyle(.secondary)
}
}
.padding(12)
.background {
Color.white
}
.clipShape(RoundedRectangle(cornerRadius: 16))
}
.padding()
.onTapGesture {
loadData()
}
.background {
Color(white: 0.9)
}
.onTapGesture { // for testing
loadData()
}
.onAppear {
loadData()
}
}
private func loadData() {
earnings = []
expenses = []
Task {
for earning in demoEarnings {
try? await Task.sleep(for: .milliseconds(800))
withAnimation(.spring(duration: 0.8, bounce: 0.3)) {
earnings.append(earning)
}
}
for expense in demoExpenses {
try? await Task.sleep(for: .milliseconds(800))
withAnimation(.spring(duration: 0.8, bounce: 0.3)) {
expenses.append(expense)
}
}
}
}
}
#Preview {
SamplePieChartView()
}