---
title: "CMMotionActivityManager: walking, running, driving or still"
canonical: "https://aifitnessapi.com/phone-sensors/cmmotionactivitymanager"
cluster: "Phone Sensors"
primary_query: "CMMotionActivityManager"
last_reviewed: "2026-10-03"
description: "How CMMotionActivityManager reports walking, running, cycling, driving and stationary, why the flags overlap, what confidence means, and the 7-day limit."
publisher: "AIFitnessAPI — independent, not sponsored"
cite_as: "\"CMMotionActivityManager: walking, running, driving or still\", AIFitnessAPI, https://aifitnessapi.com/phone-sensors/cmmotionactivitymanager"
---

# CMMotionActivityManager: walking, running, driving or still

> 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.

- Canonical: https://aifitnessapi.com/phone-sensors/cmmotionactivitymanager
- Last reviewed: 2026-10-03
- Publisher: AIFitnessAPI (https://aifitnessapi.com) — independent, not sponsored
- Cite as: "CMMotionActivityManager: walking, running, driving or still", AIFitnessAPI, https://aifitnessapi.com/phone-sensors/cmmotionactivitymanager

---

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](https://developer.apple.com/documentation/coremotion/cmmotionactivitymanager) and [CMMotionActivity](https://developer.apple.com/documentation/coremotion/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:

| Property | Apple's description | Since (iOS / watchOS) |
| --- | --- | --- |
| `stationary` | whether the device is stationary | 7.0 / 2.0 |
| `walking` | whether the device is on a walking person | 7.0 / 2.0 |
| `running` | whether the device is on a running person | 7.0 / 2.0 |
| `automotive` | whether the device is in an automobile | 7.0 / 2.0 |
| `cycling` | whether the device is in a bicycle | 8.0 / 2.0 |
| `unknown` | whether the type of motion is unknown | 7.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."

```swift
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](/phone-sensors/cmheadphonemotionmanager).

The Android equivalent is the [Activity Recognition Transition API](/phone-sensors/android-activity-recognition-transition-api). For steps rather than activity type, see [CMPedometer](/phone-sensors/cmpedometer).

## FAQ

### 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.

[Permalink](https://aifitnessapi.com/phone-sensors/cmmotionactivitymanager#faq-1)

### 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.

[Permalink](https://aifitnessapi.com/phone-sensors/cmmotionactivitymanager#faq-2)

### 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.

[Permalink](https://aifitnessapi.com/phone-sensors/cmmotionactivitymanager#faq-3)

### 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.

[Permalink](https://aifitnessapi.com/phone-sensors/cmmotionactivitymanager#faq-4)
