Overview
The ClimateChartView component displays historical temperature data for the current date across multiple decades, visualizing local climate trends. It uses iOS Charts framework to render a bar chart showing how temperatures have changed over time, with color-coded bars indicating warming or cooling trends.
Visual Display
The chart presents:
- Header with trend icon and description
- Temperature unit toggle (Celsius/Fahrenheit)
- Change summary showing temperature difference from baseline year
- Bar chart with data points for each decade
- Color-coded bars (blue for cooling, gray for neutral, orange/red for warming)
- Value labels above each bar
Component Definition
struct ClimateChartView: View {
let data: [ClimateDataPoint]
@Binding var useFahrenheit: Bool
let formatTemp: (Double) -> String
let formatDiff: (Double) -> String
var body: some View {
VStack(alignment: .leading, spacing: 12) {
// Header
HStack {
VStack(alignment: .leading, spacing: 4) {
HStack {
Image(systemName: "chart.line.uptrend.xyaxis")
.foregroundColor(.orange)
Text("Local Climate Trend")
.font(.headline)
}
Text("Temperature on this day across decades")
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
// Unit toggle
HStack(spacing: 8) {
Text("°C")
.font(.caption)
.foregroundColor(!useFahrenheit ? .primary : .secondary)
Toggle("", isOn: $useFahrenheit)
.labelsHidden()
Text("°F")
.font(.caption)
.foregroundColor(useFahrenheit ? .primary : .secondary)
}
}
}
.padding()
.background(Color.cardBackground)
.clipShape(RoundedRectangle(cornerRadius: 16))
}
}
Props / Parameters
data
[ClimateDataPoint]
required
Array of historical temperature data points. Each ClimateDataPoint contains:
year: The year (e.g., 1980, 1990, 2000)
temperature: Temperature in Celsius for that year
Binding to control whether temperatures are displayed in Fahrenheit or Celsius. User can toggle this with the built-in switch.
formatTemp
(Double) -> String
required
Closure that formats temperature values for display. Should handle both Celsius and Fahrenheit formatting.
formatDiff
(Double) -> String
required
Closure that formats temperature differences (e.g., “+2.1°F”). Used for the change summary.
Key Features
Temperature Unit Toggle
iOS-style toggle switch for Celsius/Fahrenheit:
HStack(spacing: 8) {
Text("°C")
.font(.caption)
.foregroundColor(!useFahrenheit ? .primary : .secondary)
Toggle("", isOn: $useFahrenheit)
.labelsHidden()
Text("°F")
.font(.caption)
.foregroundColor(useFahrenheit ? .primary : .secondary)
}
Change Summary
Displays temperature change from baseline year:
private var temperatureChange: Double {
guard let first = data.first, let last = data.last else { return 0 }
return last.temperature - first.temperature
}
HStack {
Text("Change since \(baselineYear)")
.font(.subheadline)
.foregroundColor(.secondary)
Spacer()
Text(formatDiff(temperatureChange))
.font(.title2)
.fontWeight(.bold)
.foregroundColor(temperatureChange > 0 ? .red : .blue)
}
Bar Chart (iOS 16+)
Uses Swift Charts framework for modern visualization:
if #available(iOS 16.0, *) {
Chart(data) { point in
BarMark(
x: .value("Year", String(point.year)),
y: .value("Temp", displayTemperature(point.temperature))
)
.foregroundStyle(barColor(for: point))
.cornerRadius(4)
.annotation(position: .top) {
Text(formatTemp(point.temperature))
.font(.caption2)
.foregroundColor(.secondary)
}
}
.chartYAxis {
AxisMarks(position: .leading)
}
.frame(height: 180)
}
Color Coding Logic
Bars are colored based on temperature change from baseline:
private func barColor(for point: ClimateDataPoint) -> Color {
guard let baseline = data.first else { return .gray }
let diff = point.temperature - baseline.temperature
if diff > 2 {
return .red
} else if diff > 0.5 {
return .orange
} else if diff < -0.5 {
return .blue
} else {
return .gray
}
}
- Red: +2°C or more warming
- Orange: +0.5°C to +2°C warming
- Gray: -0.5°C to +0.5°C (neutral)
- Blue: More than -0.5°C cooling
Fallback for iOS 15 and Earlier
Manual bar chart rendering for older iOS versions:
else {
// Fallback for older iOS
HStack(alignment: .bottom, spacing: 8) {
ForEach(data) { point in
VStack {
Text(formatTemp(point.temperature))
.font(.caption2)
RoundedRectangle(cornerRadius: 4)
.fill(barColor(for: point))
.frame(height: barHeight(for: point))
Text(String(point.year))
.font(.caption2)
}
}
}
.frame(height: 180)
}
Temperature Conversion
Automatic conversion based on unit preference:
private func displayTemperature(_ celsius: Double) -> Double {
useFahrenheit ? (celsius * 9 / 5) + 32 : celsius
}
Usage in App
@State private var useFahrenheit = true
ClimateChartView(
data: [
ClimateDataPoint(year: 1980, temperature: 18.5),
ClimateDataPoint(year: 1990, temperature: 19.2),
ClimateDataPoint(year: 2000, temperature: 19.8),
ClimateDataPoint(year: 2010, temperature: 20.1),
ClimateDataPoint(year: 2020, temperature: 20.8),
ClimateDataPoint(year: 2024, temperature: 21.2)
],
useFahrenheit: $useFahrenheit,
formatTemp: { temp in
if useFahrenheit {
return String(format: "%.1f°F", (temp * 9/5) + 32)
} else {
return String(format: "%.1f°C", temp)
}
},
formatDiff: { diff in
if useFahrenheit {
return String(format: "%+.1f°F", diff * 9/5)
} else {
return String(format: "%+.1f°C", diff)
}
}
)
Data Context
The chart visualizes how temperatures on the current calendar date (e.g., March 4th) have changed across different decades, helping users understand local climate trends at a glance.
Requirements
- iOS 16.0+ for Swift Charts support
- Falls back to custom rendering on iOS 15 and earlier
Source Location
BreezeApp/Views/Environmental/ClimateChartView.swift:4