StravaActivity
Represents a single activity from the Strava API, containing core metrics and metadata.
Unique Strava activity identifier
User-provided name of the activity
Distance covered in meters
Time spent moving in seconds (excludes pauses)API key: moving_time
Total elapsed time in seconds (includes pauses)API key: elapsed_time
Activity type identifier (e.g., “Run”, “Ride”)
More specific sport type (e.g., “Trail_Run”, “Mountain_Bike_Ride”)API key: sport_type
Activity start time in UTCAPI key: start_date
Activity start time in local timezoneAPI key: start_date_local
Total elevation gain in metersAPI key: total_elevation_gain
Number of kudos receivedAPI key: kudos_count
Strava’s relative effort score (optional, only for premium users)API key: suffer_score
Example
let activity = StravaActivity(
id: 123456789,
name: "Morning Run",
distance: 10000.0,
movingTime: 3600,
elapsedTime: 3720,
type: "Run",
sportType: "Trail_Run",
startDate: Date(),
startDateLocal: Date(),
totalElevationGain: 150.0,
kudosCount: 12,
sufferScore: 85
)
Decoding from API
The model uses custom CodingKeys to map snake_case API fields to camelCase properties:
let jsonData = """
{
"id": 123456789,
"name": "Afternoon Ride",
"distance": 32000.0,
"moving_time": 5400,
"elapsed_time": 5520,
"type": "Ride",
"sport_type": "Gravel_Ride",
"start_date": "2024-01-15T14:30:00Z",
"start_date_local": "2024-01-15T09:30:00-05:00",
"total_elevation_gain": 450.0,
"kudos_count": 24,
"suffer_score": 142
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let activity = try decoder.decode(StravaActivity.self, from: jsonData)
print(activity.name) // "Afternoon Ride"
print(activity.movingTime) // 5400
Common Calculations
extension StravaActivity {
/// Distance in miles
var miles: Double {
distance / 1609.34
}
/// Distance in kilometers
var kilometers: Double {
distance / 1000.0
}
/// Average pace in seconds per mile
var paceSecondsPerMile: Double? {
guard miles > 0 else { return nil }
return Double(movingTime) / miles
}
/// Average speed in miles per hour
var speedMph: Double? {
guard movingTime > 0 else { return nil }
return miles / (Double(movingTime) / 3600.0)
}
/// Moving time formatted as "HH:MM:SS"
var movingTimeFormatted: String {
let hours = movingTime / 3600
let minutes = (movingTime % 3600) / 60
let seconds = movingTime % 60
return String(format: "%02d:%02d:%02d", hours, minutes, seconds)
}
/// Elevation gain in feet
var elevationGainFeet: Double {
totalElevationGain * 3.28084
}
/// Pause time (elapsed - moving)
var pauseTime: Int {
elapsedTime - movingTime
}
/// Match against ActivityType enum
func matchesActivityType(_ activityType: ActivityType) -> Bool {
activityType.matches(type: type, sportType: sportType)
}
}
// Usage
let activity = StravaActivity(/* ... */)
print("Distance: \(String(format: "%.2f", activity.miles)) mi")
print("Pace: \(formatPace(activity.paceSecondsPerMile))")
print("Moving: \(activity.movingTimeFormatted)")
print("Elevation: \(Int(activity.elevationGainFeet)) ft")
Filtering Activities
let activities: [StravaActivity] = loadActivities()
// Filter by activity type
let runs = activities.filter { activity in
ActivityType.run.matches(type: activity.type, sportType: activity.sportType)
}
// Filter by date range
let calendar = Calendar.current
let startOfYear = calendar.date(from: DateComponents(year: 2024, month: 1, day: 1))!
let thisYear = activities.filter { $0.startDateLocal >= startOfYear }
// Filter by distance
let longRuns = activities.filter { $0.distance >= 16093.4 } // 10+ miles
// Calculate totals
let totalMiles = activities.reduce(0.0) { $0 + ($1.distance / 1609.34) }
let totalKudos = activities.reduce(0) { $0 + $1.kudosCount }
let totalElevation = activities.reduce(0.0) { $0 + $1.totalElevationGain }
Codable - Can be encoded/decoded to JSON
Identifiable - Has an id property for SwiftUI lists
Sendable - Safe to share across concurrency domains