<!--
{
  "documentType" : "article",
  "framework" : "MediaIntelligence",
  "identifier" : "/documentation/MediaIntelligence/detecting-and-grouping-faces-in-images",
  "metadataVersion" : "0.1.0",
  "role" : "article",
  "title" : "Detecting and grouping faces in images"
}
-->

# Detecting and grouping faces in images

Organize photos by person using on-device face detection.

## Overview

Apps that work with photo collections often need to identify who appears in each image and group photos by person. To simplify this task, [`FaceGroupAnalyzer`](/documentation/MediaIntelligence/FaceGroupAnalyzer) provides on-device face detection along with automatic grouping by person. Because all processing happens on device, face data remains private and no network connection is required.

The analyzer stores all face data in a working directory you provide. The framework exclusively manages the contents of this directory, so your app interacts with the stored data only through the [`FaceGroupAnalyzer`](/documentation/MediaIntelligence/FaceGroupAnalyzer) API. This data persists between app launches, so the analyzer resumes where it left off. The typical workflow has three phases: add images to detect faces, group the detected faces by person, and retrieve the results.

## Create a face group analyzer

To set up the analyzer, create a writable directory that the analyzer uses to store its face data and metadata, then pass it to [`init(workingDirectory:)`](/documentation/MediaIntelligence/FaceGroupAnalyzer/init(workingDirectory:)):

```swift
let workingDirectory = URL.documentsDirectory
    .appending(path: "FaceGroupData", directoryHint: .isDirectory)

// Create the directory if it doesn't already exist.
try FileManager.default.createDirectory(
    at: workingDirectory, 
    withIntermediateDirectories: true
)

let analyzer = try FaceGroupAnalyzer(workingDirectory: workingDirectory)
```

After initializing the analyzer, it’s ready to accept images. If the directory already contains data from a previous session, the analyzer loads it automatically.

## Add image assets

Create [`MediaIntelligenceImageAsset`](/documentation/MediaIntelligence/MediaIntelligenceImageAsset) values to identify each image you want the analyzer to process. Each asset takes a unique identifier you assign and a [`MediaIntelligenceImageAsset.Kind`](/documentation/MediaIntelligence/MediaIntelligenceImageAsset/Kind-swift.enum) value that tells the framework how to access the image data. Use the same identifier consistently for each image, because the framework uses it to match new submissions against existing data.

Call [`insertOrUpdateAssets(_:)`](/documentation/MediaIntelligence/FaceGroupAnalyzer/insertOrUpdateAssets(_:)) to detect faces and persist the results. The method returns an async sequence of asset identifier and [`FaceGroupAnalyzer.Face`](/documentation/MediaIntelligence/FaceGroupAnalyzer/Face) array pairs, one for each image. This example creates assets from an array of image URLs and iterates through the detection results:

```swift
let assets = imageURLs.map { url in
    MediaIntelligenceImageAsset(
        id: MediaIntelligenceImageAsset.ID(url.lastPathComponent),
        kind: .url(url)
    )
}

let results = try await analyzer.insertOrUpdateAssets(assets)
for try await (assetID, faces) in results {
    for face in faces {
        print("Found face \(face.id) at \(face.bounds) in asset \(face.assetID).")
    }
}
```

Each detected face provides valid values for its [`id`](/documentation/MediaIntelligence/FaceGroupAnalyzer/Face/id-swift.property), [`bounds`](/documentation/MediaIntelligence/FaceGroupAnalyzer/Face/bounds), and [`assetID`](/documentation/MediaIntelligence/FaceGroupAnalyzer/Face/assetID) properties, but its [`entityID`](/documentation/MediaIntelligence/FaceGroupAnalyzer/Face/entityID) property is `nil`. The analyzer assigns this value when you group faces by person in the next step.

## Group faces by person

The [`update(subprogress:)`](/documentation/MediaIntelligence/FaceGroupAnalyzer/update(subprogress:)) method groups detected faces by person. Check the analyzer’s [`state`](/documentation/MediaIntelligence/FaceGroupAnalyzer/state-swift.property) property to determine whether you need to run grouping. The property returns one of three values:

- [`FaceGroupAnalyzer.State.ready`](/documentation/MediaIntelligence/FaceGroupAnalyzer/State-swift.enum/ready): All faces have current group assignments.
- [`FaceGroupAnalyzer.State.stale`](/documentation/MediaIntelligence/FaceGroupAnalyzer/State-swift.enum/stale): You added, updated, or removed faces and need to run grouping.
- [`FaceGroupAnalyzer.State.updating`](/documentation/MediaIntelligence/FaceGroupAnalyzer/State-swift.enum/updating): The grouping algorithm is running.

> Note: If the app exits while the analyzer is updating, the state returns to ``doc://com.apple.mediaintelligence/documentation/MediaIntelligence/FaceGroupAnalyzer/State-swift.enum/stale`` on the next launch. Previously stored face data is preserved, so calling ``doc://com.apple.mediaintelligence/documentation/MediaIntelligence/FaceGroupAnalyzer/update(subprogress:)`` again completes the grouping.

When the state is [`FaceGroupAnalyzer.State.stale`](/documentation/MediaIntelligence/FaceGroupAnalyzer/State-swift.enum/stale), call [`update(subprogress:)`](/documentation/MediaIntelligence/FaceGroupAnalyzer/update(subprogress:)) to run grouping:

```swift
if await analyzer.state == .stale {
    try await analyzer.update()
}
```

After the call to [`update(subprogress:)`](/documentation/MediaIntelligence/FaceGroupAnalyzer/update(subprogress:)) completes, every detected face receives an [`entityID`](/documentation/MediaIntelligence/FaceGroupAnalyzer/Face/entityID) that groups it with other faces of the same person. The analyzer’s state returns to [`FaceGroupAnalyzer.State.ready`](/documentation/MediaIntelligence/FaceGroupAnalyzer/State-swift.enum/ready).

## Retrieve face and entity data

After grouping completes, iterate all faces grouped by person using [`allFacesByEntityID`](/documentation/MediaIntelligence/FaceGroupAnalyzer/allFacesByEntityID). Each element pairs an entity identifier that represents a unique person with an array of every face the analyzer detected for that person across all images:

```swift
for try await (entityID, faces) in analyzer.allFacesByEntityID {
    // Use entityID and faces to build person-based features.
}
```

To narrow your query, call [`fetchFaces(for:)`](/documentation/MediaIntelligence/FaceGroupAnalyzer/fetchFaces(for:)) or [`fetchAssetIDs(for:)`](/documentation/MediaIntelligence/FaceGroupAnalyzer/fetchAssetIDs(for:)) with specific entity identifiers. These methods return synchronously from the local store, so they don’t require `await`.

## Identify faces without storing data

To recognize people in new images without adding them to the analyzer’s persistent store, use the [`identifyFaces(in:)`](/documentation/MediaIntelligence/FaceGroupAnalyzer/identifyFaces(in:)) method. It detects faces in the provided images and matches them against the existing stored data. Returned faces include an [`entityID`](/documentation/MediaIntelligence/FaceGroupAnalyzer/Face/entityID) when they match a known person, letting you recognize people in newly captured photos or tag previews before saving to the persistent store.

Call this method when the analyzer’s state is [`FaceGroupAnalyzer.State.ready`](/documentation/MediaIntelligence/FaceGroupAnalyzer/State-swift.enum/ready) or [`FaceGroupAnalyzer.State.stale`](/documentation/MediaIntelligence/FaceGroupAnalyzer/State-swift.enum/stale). For best accuracy, call [`update(subprogress:)`](/documentation/MediaIntelligence/FaceGroupAnalyzer/update(subprogress:)) first to ensure the stored data is fully grouped. When the state is stale, identification still works but produces less accurate results.

The following example identifies faces in a new image and filters for matches:

```swift
let asset = MediaIntelligenceImageAsset(
    id: MediaIntelligenceImageAsset.ID(newPhotoURL.lastPathComponent),
    kind: .url(newPhotoURL)
)

let results = try await analyzer.identifyFaces(in: [asset])
for try await (assetID, faces) in results {
    let matchedFaces = faces.filter { $0.entityID != nil }
    // The result contains faces that match known people in the stored data.
}
```

## Manage stored data

The analyzer provides several options for removing stored data, from deleting individual assets to purging the entire working directory:

```swift
// Remove specific assets and their face data.
try await analyzer.deleteAssets([assetID1, assetID2])

// Remove all assets from the analyzer.
try await analyzer.deleteAllAssets()

// Remove all data from the working directory permanently.
try await FaceGroupAnalyzer.purge(workingDirectory: workingDirectory)
```

> Important: Calling ``doc://com.apple.mediaintelligence/documentation/MediaIntelligence/FaceGroupAnalyzer/purge(workingDirectory:)`` permanently deletes all face data, group assignments, and metadata.

After deleting assets, the analyzer’s state becomes [`FaceGroupAnalyzer.State.stale`](/documentation/MediaIntelligence/FaceGroupAnalyzer/State-swift.enum/stale) if any faces remain. Existing group assignments for the remaining faces are still valid, but calling [`update(subprogress:)`](/documentation/MediaIntelligence/FaceGroupAnalyzer/update(subprogress:)) regroups them to account for the removed data.

---

Copyright &copy; 2026 Apple Inc. All rights reserved. | [Terms of Use](https://www.apple.com/legal/internet-services/terms/site.html) | [Privacy Policy](https://www.apple.com/privacy/privacy-policy)