Skip to content
AF
Phone Sensors

CMMotionActivityManager: walking, running, driving or still

Last verified October 3, 2026 · 4 min read

CMMotionActivityManager is the Core Motion class that tells your app whether the device is on someone walking, running, cycling, in a vehicle or stationary, delivered as CMMotionActivity objects with a confidence of low, medium or high. It is available from iOS 7.0 and watchOS 2.0, with the cycling flag from iOS 8.0. Live changes come from startActivityUpdates(to:withHandler:), which reports the current state and then only changes, and history comes from queryActivityStarting(from:to:to:withHandler:), which Apple limits to the last seven days and warns can lag by up to several minutes. Apple also says the motion flags are not mutually exclusive, so a single update can be both automotive and stationary.

Activity classification is the cheapest context signal a fitness app can get: no GPS, no camera, no wearable. Core Motion keeps the classification running on its own; your app reads it. Everything below comes from Apple's CMMotionActivityManager and CMMotionActivity documentation, read 2026-10-03.

What an update contains#

Each CMMotionActivity carries a startDate ("The time at which the change in motion occurred"), a confidence, and six Booleans:

PropertyApple's descriptionSince (iOS / watchOS)
stationarywhether the device is stationary7.0 / 2.0
walkingwhether the device is on a walking person7.0 / 2.0
runningwhether the device is on a running person7.0 / 2.0
automotivewhether the device is in an automobile7.0 / 2.0
cyclingwhether the device is in a bicycle8.0 / 2.0
unknownwhether the type of motion is unknown7.0 / 2.0

The flags overlap, and can all be false#

This is the line that breaks naive switch statements. Apple: "The motion-related properties of this class aren't mutually exclusive. In other words, it's possible for more than one of the motion-related properties to contain the value true." The example is a car at a red light, which reports both automotive and stationary. The reverse also happens: "It's also possible for all of the properties to be set to false when the device is in motion but the movement doesn't correlate to walking, running, cycling, or automotive travel." And unknown has its own meaning: it is true "when there is no way to estimate the current type of motion", for example when "the device was turned on recently and not enough motion data had been gathered".

So model the result as a set, not an enum, and decide your own precedence. Ours, as judgement: for a fitness app, running beats walking, any of the three beats stationary, and automotive overrides everything because a phone in a car should never log a workout.

Confidence has three levels and no numbers#

CMMotionActivityConfidence is .low ("Confidence is low."), .medium ("Confidence is good.") or .high ("Confidence is high."). Apple publishes no threshold, no accuracy figure and no definition of what moves a reading between levels. Treat it as an ordering you can filter on, not a probability.

Live updates: current state, then changes#

startActivityUpdates(to:withHandler:) first reports "the current motion in effect for the device", then runs the handler "only when the motion data changes". Delivery is best effort and stops while you are suspended: "If updates arrived while your app was suspended, the last update is delivered to your app when it resumes execution. To get all of the updates that occurred while your app was suspended, use the queryActivityStarting(from:to:to:withHandler:) method." Calling it again with a new block replaces the old one, and "Updates stop altogether when the motion activity manager object itself is deallocated", so keep a strong reference.

History: seven days, delayed#

queryActivityStarting(from:to:to:withHandler:) returns an array ordered by time. Two sentences from Apple matter: "A delay of up to several minutes in reported activities is expected," and "The system stores only the last seven days worth of activity data at most." An empty range is not an empty array: "If there are no samples for the specified range of time, an error object with the code CMErrorUnknown is passed to the handler block."

import CoreMotion

let activityManager = CMMotionActivityManager()   // keep a strong reference

func minutesOnFoot(since start: Date, completion: @escaping (TimeInterval) -> Void) {
    guard CMMotionActivityManager.isActivityAvailable() else { return completion(0) }
    activityManager.queryActivityStarting(from: start, to: Date(), to: .main) { activities, error in
        guard let activities, error == nil else { return completion(0) }  // CMErrorUnknown = no samples
        var total: TimeInterval = 0
        for (current, next) in zip(activities, activities.dropFirst()) {
            let onFoot = (current.walking || current.running) && !current.automotive
            if onFoot && current.confidence != .low {
                total += next.startDate.timeIntervalSince(current.startDate)
            }
        }
        completion(total)
    }
}

Each activity marks a change, so a segment lasts until the next one's startDate; that is how the loop measures duration.

Permission and availability#

The same rule as the pedometer: "To use this API, you must include the NSMotionUsageDescription key in your app's Info.plist file", and "If you don't include a usage description string, your app crashes when you call this API." Check isActivityAvailable() first; Apple notes "Motion data is not available on all iOS devices." authorizationStatus() arrived in iOS 11.0 and watchOS 4.0.

Pitfalls#

  • Switching on a single flag. Several can be true at once, and all can be false.
  • Reading an error as "no data". CMErrorUnknown is what an empty range returns.
  • Scoring a workout the moment it ends. History can lag by several minutes. Query a little later, or re-query.
  • Letting the manager deallocate. Updates stop with it.
  • Expecting macOS. CMMotionActivity lists macOS 15.0, but Apple's CMMotionActivityManager page lists no macOS version. On macOS the documented source is the headphone activity manager (macOS 15.0), covered on the CMHeadphoneMotionManager page.

The Android equivalent is the Activity Recognition Transition API. For steps rather than activity type, see CMPedometer.

Frequently asked questions

Can a CMMotionActivity have more than one activity flag set?
Yes. Apple's CMMotionActivity documentation says the motion-related properties aren't mutually exclusive, and gives the example of a car stopped at a red light, which reports both automotive and stationary as true. Apple also says all of them can be false when the device is moving in a way that doesn't match walking, running, cycling or driving. Model the result as a set of flags and choose your own precedence; a switch over one value will misread real updates.
Why does queryActivityStarting return an error instead of an empty array?
Because that is how Apple reports an empty range. The documentation for queryActivityStarting(from:to:to:withHandler:) says that if there are no samples for the specified range of time, an error object with the code CMErrorUnknown is passed to the handler. Treat that code as no activity recorded. Remember the other two limits on the same page: the system keeps at most the last seven days of activity, and reported activities can lag by up to several minutes.
Does CMMotionActivityManager deliver activity changes while my app is suspended?
No. Apple says the handler passed to startActivityUpdates(to:withHandler:) runs on a best-effort basis and updates are not delivered while your app is suspended. When the app resumes, only the last update that arrived meanwhile is delivered. To recover everything that happened in between, Apple points you to queryActivityStarting(from:to:to:withHandler:), which returns the full ordered list for a time range within the last seven days.
What do the CMMotionActivityConfidence levels actually mean?
Apple documents three values and very little else: low is described as confidence is low, medium as confidence is good, and high as confidence is high. There is no published threshold, probability or accuracy figure behind them. Use confidence as an ordering, for example ignoring low-confidence segments when you total active minutes, and validate the filter against your own recorded sessions rather than assuming a number.

All 40 questions in Phone Sensors →

Keep reading

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