Skip to content
AF
Phone Sensors

CMPedometer: steps, distance, floors and cadence from the phone itself

Last verified October 3, 2026 · 5 min read

CMPedometer is Core Motion's class for what Apple calls system-generated live walking data: step counts, estimated distance, floors ascended and descended, pace and cadence, available from iOS 8.0 and watchOS 2.0. It has two ways in: queryPedometerData(from:to:withHandler:) reads history, and Apple says only the past seven days worth of data is stored, while startUpdates(from:withHandler:) streams running totals from a start date you choose. Your Info.plist must carry NSMotionUsageDescription, because Apple states that without it your app crashes when you call this API. authorizationStatus(), added in iOS 11.0, tells you whether the user allowed motion access.

Covered here:HealthKit

An iPhone app that wants steps without asking for HealthKit and without a watch reaches for this class. Apple's overview describes it plainly: "The pedometer object manages a cache of historic data that you can query or you can ask for live updates as the data is processed." Which store you should read steps from across HealthKit, Health Connect and wearable clouds is a separate decision, and it is made on the step counting API page. This page covers the class itself, from Apple's CMPedometer documentation as read on 2026-10-03.

What one CMPedometerData carries#

Every handler receives a CMPedometerData. Apple says you never create one yourself; the pedometer packages each answer into one. The fields, their units in Apple's words, and when they are missing:

PropertyUnit (Apple)When it is nilSince (iOS / watchOS)
numberOfStepsstepsDeclared non-optional NSNumber8.0 / 2.0
distancemeters, "estimated""may be nil if distance estimation is not supported on the current device"8.0 / 2.0
floorsAscended, floorsDescendedapproximate floorsWhen floor counting is not supported on the device8.0 / 2.0 (floorsAscended)
currentPaceseconds per meterIn historical queries, before it is available, or on unsupported devices9.0 / 2.0
currentCadencesteps per secondIn historical queries, before it is available, or on unsupported devices9.0 / 2.0
averageActivePaceseconds per meter, active periods onlyIn a historical query with no movement, or on unsupported devices10.0 / 3.0

Two of those deserve a second look. Pace is seconds per meter, the inverse of a speed, so a UI showing minutes per kilometre has to convert. And Apple defines a floor loosely: "A single floor has a height of approximately three meters," counted "only the floors ascended while the user was walking or running up stairs".

Availability is per capability#

There is no single availability check for the whole class. The class exposes isStepCountingAvailable(), isDistanceAvailable(), isFloorCountingAvailable(), isPaceAvailable(), isCadenceAvailable() and isPedometerEventTrackingAvailable(). For floor counting and cadence Apple adds the same sentence: "This capability is not supported on all devices." Check the capability the feature needs, not just steps.

Live updates are cumulative#

startUpdates(from:withHandler:) is the call most people get wrong. Apple: "The data passed to your handler block represents the cumulative data starting at the specified start date and ending at the current time." Each callback is a running total from your start date, not the steps since the last callback. The start date can be in the past ("You can specify a date in the past to retrieve the data from that time until now"), which is how you show today's total the moment a screen opens.

The handler "is called repeatedly on a background thread as new data arrives", on a serial queue. Suspension pauses delivery: "When the app is suspended, the delivery of updates stops temporarily. Upon returning to foreground or background execution, the pedometer object begins updates again."

import CoreMotion

let pedometer = CMPedometer()

func startTodayCounter(update: @escaping (Int, Double?) -> Void) {
    guard CMPedometer.isStepCountingAvailable() else { return }
    if CMPedometer.authorizationStatus() == .denied { return }  // explain, link to Settings

    let midnight = Calendar.current.startOfDay(for: Date())
    pedometer.startUpdates(from: midnight) { data, error in
        guard let data, error == nil else { return }
        let steps = data.numberOfSteps.intValue       // running total since midnight
        let metres = data.distance?.doubleValue       // nil where distance is unsupported
        DispatchQueue.main.async { update(steps, metres) }  // handler is off the main thread
    }
}
// later: pedometer.stopUpdates()

History: seven days, then nothing#

queryPedometerData(from:to:withHandler:) returns one aggregate for the range, and its handler "is called once". The limit is in the discussion: "Only the past seven days worth of data is stored and available for you to retrieve. Specifying a start date that is more than seven days in the past returns only the available data." Apple also says it is safe to query while live updates are running. A weekly chart works. A monthly one needs your own storage, or HealthKit.

Pause and resume events#

From iOS 10.0 and watchOS 3.0, startEventUpdates(handler:) delivers CMPedometerEvent objects whose type is .pause ("The user's pedestrian activity stopped.") or .resume ("The user's pedestrian activity resumed."). That is the documented hook for auto-pausing a walking session. Whether it fires quickly enough for your UI is something to measure on a device; Apple publishes no latency.

Permission#

Apple's warning on the class page: "To use this API, you must include the NSMotionUsageDescription key in your app's Info.plist file and provide a usage description string for this key." The string "appears in the prompt that the user must accept the first time the system asks the user to access motion data for your app." The class page lists no method that requests permission on its own; the prompt arrives with first use. authorizationStatus() returns a CMAuthorizationStatus: .notDetermined ("The status has not yet been determined."), .restricted ("Access is denied due to system-wide restrictions."), .denied ("Access was denied by the user.") or .authorized.

Pitfalls#

  • Summing live callbacks. Each one is cumulative from your start date. Adding them inflates the count on every update.
  • Updating UI inside the handler. It runs on a background thread. Hop to the main queue.
  • Planning for a month of history. The store holds seven days. Persist what you need before it ages out.
  • Treating nil as zero. A nil distance or floorsAscended means the device cannot measure it, not that the user stood still.
  • Expecting cadence from a query. currentPace and currentCadence are nil in historical queries by Apple's definition. Use averageActivePace there.
  • Assuming lifts count. Floors exclude "floors ascended by elevator or other assisted means".
  • Expecting a merged phone and watch total. Apple's CMPedometer pages say nothing about combining devices. A de-duplicated daily total across sources is what HealthKit statistics queries are for; see the HealthKit integration guide.

The rest of the phone's motion stack is on CMMotionActivityManager for walking versus driving and CMAltimeter for elevation. The Android counterpart is the step counter sensor.

Frequently asked questions

How far back can CMPedometer queryPedometerData return steps?
Seven days. Apple's documentation for queryPedometerData(from:to:withHandler:) says only the past seven days worth of data is stored and available for you to retrieve, and that a start date more than seven days in the past returns only the available data. It does not fail; it quietly gives you less than you asked for. If your product shows monthly step history from Core Motion alone, you have to save each day's total yourself before it ages out, or read from HealthKit instead.
Why does my app crash the first time it calls CMPedometer?
Almost always a missing NSMotionUsageDescription key. Apple's CMPedometer page says you must include that key in Info.plist with a usage description string, and that if you don't, your app crashes when you call this API. The same string is what the user sees in the motion permission prompt the first time the system asks for access, so write it as an explanation of the feature rather than a placeholder.
Is CMPedometer startUpdates data cumulative or per interval?
Cumulative. Apple states that the data passed to your handler represents the cumulative data starting at the specified start date and ending at the current time. Every callback is a running total from the date you passed in, so display the latest value rather than adding callbacks together. Passing midnight as the start date gives you today's total immediately, including steps taken before your app launched.
Why is currentCadence nil in a CMPedometer historical query?
Because Apple defines it that way. The documentation for currentCadence and currentPace says the value is nil when you are performing a query for historical pedometer data, when the information is not yet available, or on devices that do not support it. Cadence and current pace only arrive during live updates. For a past range, averageActivePace is the field Apple sets, and it averages pace only over periods of activity.

All 40 questions in Phone Sensors →

Keep reading

Elsewhere on the site

Pages that share this one’s concepts and sources, from other sections.

Next steps

Was this page useful?

Independent comparison, last reviewed October 3, 2026. Pricing, rate limits, and feature availability change often — confirm current details in each provider’s official documentation before you commit. Product and company names are trademarks of their respective owners; AIFitnessAPI is not affiliated with, endorsed by, or sponsored by any product listed here.

← All phone sensors · by AIFitnessAPI