A fluent API to integrate with Strava on iOS apps written in Swift.
This document explains how to use StravaZpot in your Swift iOS app. For additional questions, you may want to check Strava official documentation here.
Strava uses OAuth 2.0 to authenticate users, and then request them to grant permission to your app. To get a Client ID and Secret from Strava for your app, follow this link.
Once you have your app credentials, you can create an AuthenticationViewController
and set the necessary fields with the following code:
let authenticationViewController = AuthenticationViewController()
authenticationViewController.url = url // URL
authenticationViewController.redirectURL = redirectURL // String
authenticationViewController.delegate = delegate // AuthenticationDelegate
authenticationViewController.title = "Login to Strava"
The url
that you need to send has some special arguments. StravaZpot provides an easy way to build it:
let url = StravaLogin(clientID: <YOUR_CLIENT_ID>,
redirectURI: <YOUR_REDIRECT_URL>,
approvalPrompt: ApprovalPrompt.force,
accessScope: AccessScope.Write).makeURL()
You need to notice several things with this call:
<YOUR_CLIENT_ID>
must be replaced with the Client ID provided by Strava when you registered your application.<YOUR_REDIRECT_URL>
must be in the domain you specified when you registered your app in Strava.ApprovalPrompt
enum to get more options for this parameter.AccessScope
enum to get more options for this parameter.The delegate
you pass to the AuthenticationViewController
must implement the protocol AuthenticationDelegate
, which consists of a single method:
func authenticationViewController(_ authenticationViewController: AuthenticationViewController, didFinishWithCode code: String) {
// Use code to obtain token
}
Every Strava API call needs a token to prove the user is authenticated and the app has permission to access the API. After you have obtained the code from user login, you need to exchange it with Strava to get a token. You can do it with the following code:
let client = HTTPClientBuilder.authenticationClient(debug: true)
AuthenticationAPI(client: client)
.getToken(forApp: AppCredentials(clientID: <YOUR_CLIENT_ID>,
clientSecret: <YOUR_CLIENT_SECRET>),
withCode: self.code)
.execute { loginResult : StravaResult<LoginResult> in
// Check if result is successful or not
}
Notice that in this call you must provide the Client ID and Secret provided by Strava when you registered your application, and the code obtained during the login process. Also, the execution of the previous code involves a network request; you are responsible for calling this code in a suitable thread, outside the UI thread. Otherwise, you will get an exception.
If the previous request is successful, you will get a LoginResult
, which has a Token
that you can use in your subsequent API calls, and an Athlete
instance, representing the authenticated user.
All responses from Strava API calls are wrapped in StravaResult<T>
, which is an enum that indicates if the result was successful or not. The successful case contains an instance of the object that Strava returns. The failure case contains an Error
explaining the reason for the failure.
Before introducing the AthleteAPI, we have to talk about HTTPClient
. HTTPClient
is a protocol required by all the APIs in StravaZpot to configure the way it is going to interact with Strava. You can create an HTTPClient
as soon as you obtain a token and reuse it during your app lifecycle. To create an instance of this:
let client = HTTPClientBuilder.client(withToken: <YOUR_TOKEN>, debug: true)
You must provide the token obtained during the authentication process. The debug
argument will print useful information about the API calls.
Once you have the HTTPClient
, you can proceed to use all the APIs.
let athleteAPI = AthleteAPI(client: client)
athleteAPI.retrieveCurrentAthlete()
.execute { result : StravaResult<Athlete> in ... }
athleteAPI.retrieveAthlete(withID: 227615)
.execute { result : StravaResult<Athlete> in ... }
ahtleteAPI.updateCurrentAthlete(withCity: "Irvine",
withState: "California",
withCountry: "USA",
withSex: .male,
withWeight: 85.6)
.execute { result : StravaResult<Athlete> in ... }
athleteAPI.getAthleteZones()
.execute { result : StravaResult<Zones> in ... }
atheleteAPI.getTotalsAndStats(withID: 227615)
.execute { result : StravaResult<Stats> in ... }
athleteAPI.listAthleteKOMS(withID: 227615)
.of(page: 2, itemsPerPage: 10)
.execute { result : StravaResult<EquatableArray<SegmentEffort>> in ... }
let friendAPI = FriendAPI(client: client)
friendAPI.listMyFriends()
.of(page: 2, itemsPerPage: 10)
.execute{ result : StravaResult<EquatableArray<Athlete>> in ... }
friendAPI.listAthleteFriends(withID: 227615)
.of(page: 2, itemsPerPage: 10)
.execute{ result : StravaResult<EquatableArray<Athlete>> in ... }
friendAPI.listMyFollowers()
.of(page: 2, itemsPerPage: 10)
.execute{ result : StravaResult<EquatableArray<Athlete>> in ... }
friendAPI.listAthleteFollowers(withID: 227615)
.of(page: 2, itemsPerPage: 10)
.execute{ result : StravaResult<EquatableArray<Athlete>> in ... }
friendAPI.listBothFollowing(withID: 227615)
.of(page: 2, itemsPerPage: 10)
.execute{ result : StravaResult<EquatableArray<Athlete>> in ... }
let activityAPI = ActivityAPI(client: client)
activityAPI.createActivity(withName: "Rowing session",
withType: .rowing,
withStartDate: Date(day: 20, month: 1, year: 2016, hour: 12, minute: 35, second: 46),
withElapsedTime: Time(seconds: 6345),
withDescription: "Relaxing training",
withDistance: Distance(meters: 1234),
isPrivate: false,
withTrainer: true,
withCommute: false)
.execute { result : StravaResult<Activity> in ... }
activityAPI.retrieveActivity(withID: 321934,
includeAllEfforts: true)
.execute{ result : StravaResult<Activity> in ... }
activityAPI.updateActivity(withID: 321934,
withName: "Rowing session",
withType: .rowing,
isPrivate: true,
withTrainer: false,
withCommute: true,
withGearID: "b123456",
withDescription: "Best training ever!")
.execute{ result : StravaResult<Activity> in ... }
activityAPI.deleteActivity(withID: 321934)
.execute{ result : StravaResult<Bool> in ... }
activityAPI.listActivities(before: 1000, after: 2000)
.of(page: 2, itemsPerPage: 10)
.execute{ result : StravaResult<EquatableArray<Activity>> in ... }
activityAPI.listFriendsActivities(before: 1000)
.of(page: 2, itemsPerPage: 10)
.execute{ result : StravaResult<EquatableArray<Activity>> in ... }
activityAPI.listRelatedActivities(toActivityWithID: 321934)
.of(page: 2, itemsPerPage: 10)
.execute{ result : StravaResult<EquatableArray<Activity>> in ... }
activityAPI.listActivityZones(withID: 321934)
.execute{ result : StravaResult<EquatableArray<ActivityZone>> in ... }
activityAPI.listActivityLaps(withID: 321934)
.execute{ result : StravaResult<EquatableArray<ActivityLap> in ... }
let commentAPI = CommentAPI(client: client)
commentAPI.listActivityComments(withID: 123)
.of(page: 2, itemsPerPage: 10)
.execute{ result : StravaResult<EquatableArray<Comment>> in ... }
let kudosAPI = KudosAPI(client: client)
kudosAPI.listActivityKudos(withID: 321934)
.of(page: 2, itemsPerPage: 10)
.execute{ result : StravaResult<EquatableArray<Athlete>> in ... }
let photoAPI = PhotoAPI(client: client)
photoAPI.listActivityPhotos(withID: 81121657)
.execute{ result : StravaResult<EquatableArray<Photo>> in ... }
let clubAPI = ClubAPI(client: client)
clubAPI.retrieveClub(withID: 1)
.execute{ result : StravaResult<Club> in ... }
clubAPI.listClubAnnouncements(withID: 109984)
.execute{ result : StravaResult<EquatableArray<Announcement>> in ... }
clubAPI.listClubGroupEvents(withID: 1)
.execute{ result : StravaResult<EquatableArray<Event>> in ... }
clubAPI.listAthleteClubs()
.execute{ result : StravaResult<EquatableArray<Club>> in ... }
clubAPI.listClubMembers(withID: 123)
.execute{ result : StravaResult<EquatableArray<Athlete>> in ... }
clubAPI.listClubAdmins(withID: 123)
.execute{ result : StravaResult<EquatableArray<Athlete>> in ... }
clubAPI.listClubActivities(withID: 123, before: 9999)
.of(page: 2, itemsPerPage: 10)
.execute{ result : StravaResult<EquatableArray<Activity>> in ... }
clubAPI.joinClub(withID: 123)
.execute{ result : StravaResult<JoinResult> in ... }
clubAPI.leaveClub(withID: 123)
.execute{ result : StravaResult<LeaveResult> in ... }
let gearAPI = GearAPI(client: client)
gearAPI.getGear(withID: "b105763")
.execute{ result : StravaResult<Gear> in ... }
let routeAPI = RouteAPI(client: client)
routeAPI.retrieveRoute(withID: 1263727)
.execute{ result : StravaResult<Route> in ... }
routeAPI.listAthleteRoutes(withID: 1234)
.execute{ result : StravaResult<EquatableArray<Route>> in ... }
let segmentAPI = SegmentAPI(client: client)
segmentAPI.retrieveSegment(withID: 229781)
.execute{ result : StravaResult<Segment> in ... }
segmentAPI.listMyStarredSegments()
.execute{ result : StravaResult<EquatableArray<Segment>> in ... }
segmentAPI.listAthleteStarredSegments(withID: 1234)
.execute{ result : StravaResult<EquatableArray<Segment>> in ... }
segmentAPI.starSegment(withID: 229781)
.execute{ result : StravaResult<Segment> in ... }
segmentAPI.unstarSegment(withID: 229781)
.execute{ result : StravaResult<Segment> in ... }
segmentAPI.listSegmentEfforts(withID: 229781,
forAthleteWithID: 1234,
withStartDate: Date(day: 1, month: 1, year: 2015, hour: 0, minute: 0, second: 1),
withEndDate: Date(day: 31, month: 12, year: 2015, hour: 23, minute: 59, second: 59))
.of(page: 2, itemsPerPage: 10)
.execute{ result : StravaResult<EquatableArray<SegmentEffort>> in ... }
segmentAPI.retrieveSegmentLeaderboard(withID: 229781,
withGender: .female,
withAgeGroup: .age_25_34,
withWeightClass: .kg_65_74,
following: true,
withClubID: 321,
inDateRange: .thisMonth,
withContextEntries: 4)
.of(page: 2, itemsPerPage: 10)
.execute{ result : StravaResult<Leaderboard> in ... }
segmentAPI.explore(inRegion: Bounds(southWest: Coordinates(latitude: 15, longitude: -24),
northEast: Coordinates(latitude: -32, longitude: 40)),
withActivityType: .running,
withMinCategory: 3,
withMaxCategory: 7)
.execute{ result : StravaResult<ExploreResult> in ... }
let segmentEffortAPI = SegmentEffortAPI(client: client)
segmentEffortAPI.retrieveSegmentEffort(withID: 269990681)
.execute{ result : StravaResult<SegmentEffort> in ... }
let streamAPI = StreamAPI(client: client)
streamAPI.listActivityStreams(withID: 112233,
forTypes: .altitude, .time, .temperature,
withResolution: .low,
withSeriesType: .time)
.execute{ result : StravaResult<EquatableArray<Stream>> in ... }
streamAPI.listSegmentEffortStreams(withID: 112233,
forTypes: .altitude, .time, .temperature,
withResolution: .low,
withSeriesType: .time)
.execute{ result : StravaResult<EquatableArray<Stream>> in ... }
streamAPI.listSegmentStreams(withID: 112233,
forTypes: .altitude, .time, .temperature,
withResolution: .low,
withSeriesType: .time)
.execute{ result : StravaResult<EquatableArray<Stream>> in ... }
streamAPI.listRouteStreams(withID: 112233)
.execute{ result : StravaResult<EquatableArray<Stream>> in ... }
let uploadAPI = UploadAPI(client: client)
Strava allows you to upload files with formats GPX, FIT or TCX. We recommend to use TCXZpot in order to generate TCX files that can be uploaded to Strava.
uploadAPI.upload(file : URL(string: "file://path_to_file")!,
withFilename : "filename.fit",
withDataType : .fit,
withActivityType : .ride,
withName : "A complete ride around the city",
withDescription : "No description",
isPrivate : false,
hasTrainer : false,
isCommute : false,
withExternalID : "test.fit")
.execute{ result : StravaResult<UploadStatus> in ... }
uploadAPI.checkUploadStatus(withId: 16486788)
.execute{ result : StravaResult<UploadStatus> in ... }
You can get StravaZpot from CocoaPods. Just add this line to your Podfile:
pod 'StravaZpot-Swift', '~> 1.0.5'
Copyright 2017 SweetZpot AS
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.