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() }
