```html SwiftUI: How to Implement Focus Filter (iOS 17+, 2026)

How to Implement a Focus Filter in SwiftUI

iOS 17+ Xcode 16+ Advanced APIs: FocusFilterIntent, AppContext, SetFocusFilterIntent Updated: May 11, 2026
TL;DR

Conform a struct to FocusFilterIntent, declare @Parameter properties users configure per Focus mode, then call FocusFilterIntent.current in your app to read the active configuration and adjust behavior accordingly.

import AppIntents

struct MyFocusFilter: FocusFilterIntent {
    static var title: LocalizedStringResource = "My App Filter"
    static var description: LocalizedStringResource = "Customize My App for this Focus."

    @Parameter(title: "Show Urgent Only")
    var urgentOnly: Bool = false

    func perform() async throws -> some IntentResult {
        // Called when the user activates a Focus with this filter
        return .result()
    }
}

// Read the active filter anywhere in your app:
let filter = try? await MyFocusFilter.current

Full implementation

Focus Filters use the App Intents framework. You define a FocusFilterIntent subtype, expose @Parameter values users can configure per Focus mode in iOS Settings, then observe those values inside your SwiftUI views. The system calls perform() whenever a matching Focus activates, and you read the stored configuration via MyFocusFilter.current to drive your UI.

import SwiftUI
import AppIntents

// MARK: - Focus Filter Intent

struct TaskAppFocusFilter: FocusFilterIntent {
    static var title: LocalizedStringResource = "Task App Filter"
    static var description: LocalizedStringResource =
        "Configure Task App behavior for this Focus mode."

    /// Shown in iOS Settings › Focus › [Mode] › App Filters › Task App
    @Parameter(title: "Show Urgent Tasks Only", default: false)
    var urgentOnly: Bool

    @Parameter(title: "Selected Project", optionsProvider: ProjectOptionsProvider())
    var project: String?

    func perform() async throws -> some IntentResult {
        // Called by the system when this Focus activates.
        // Use UserDefaults / AppStorage to persist and notify the main app.
        UserDefaults.standard.set(urgentOnly, forKey: "focusUrgentOnly")
        UserDefaults.standard.set(project, forKey: "focusProject")
        return .result()
    }
}

// MARK: - Options Provider

struct ProjectOptionsProvider: DynamicOptionsProvider {
    func results() async throws -> [String] {
        // Replace with your real data source
        ["Inbox", "Work", "Personal", "Side Project"]
    }
}

// MARK: - App State Observable

@Observable
final class FocusFilterState {
    var urgentOnly: Bool = false
    var activeProject: String? = nil

    @MainActor
    func refresh() async {
        guard let filter = try? await TaskAppFocusFilter.current else {
            urgentOnly = false
            activeProject = nil
            return
        }
        urgentOnly = filter.urgentOnly
        activeProject = filter.project
    }
}

// MARK: - SwiftUI View

struct TaskListView: View {
    @State private var focusState = FocusFilterState()

    let allTasks: [TaskItem] = TaskItem.samples

    var filtered: [TaskItem] {
        allTasks
            .filter { !focusState.urgentOnly || $0.isUrgent }
            .filter { focusState.activeProject == nil || $0.project == focusState.activeProject }
    }

    var body: some View {
        NavigationStack {
            List(filtered) { task in
                TaskRow(task: task)
            }
            .navigationTitle(focusState.activeProject ?? "All Tasks")
            .toolbar {
                if focusState.urgentOnly {
                    ToolbarItem(placement: .topBarTrailing) {
                        Label("Focus Active", systemImage: "moon.fill")
                            .labelStyle(.iconOnly)
                            .foregroundStyle(.indigo)
                    }
                }
            }
        }
        .task {
            await focusState.refresh()
        }
        // Re-read when app returns to foreground (Focus may have changed)
        .onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
            Task { await focusState.refresh() }
        }
    }
}

// MARK: - Supporting Types

struct TaskItem: Identifiable {
    let id = UUID()
    let title: String
    let project: String
    let isUrgent: Bool

    static let samples = [
        TaskItem(title: "Submit PR", project: "Work", isUrgent: true),
        TaskItem(title: "Buy groceries", project: "Personal", isUrgent: false),
        TaskItem(title: "Fix crash bug", project: "Work", isUrgent: true),
        TaskItem(title: "Read chapter 3", project: "Personal", isUrgent: false),
        TaskItem(title: "Update README", project: "Side Project", isUrgent: false),
    ]
}

struct TaskRow: View {
    let task: TaskItem
    var body: some View {
        HStack {
            if task.isUrgent {
                Image(systemName: "exclamationmark.circle.fill")
                    .foregroundStyle(.red)
                    .accessibilityLabel("Urgent")
            }
            VStack(alignment: .leading) {
                Text(task.title).font(.body)
                Text(task.project).font(.caption).foregroundStyle(.secondary)
            }
        }
    }
}

// MARK: - Preview

#Preview {
    TaskListView()
}

How it works

  1. FocusFilterIntent conformanceTaskAppFocusFilter tells iOS that your app supports Focus Filters. iOS surfaces this in Settings › Focus › [Mode] › App Filters so users can configure it per Focus without touching your app.
  2. @Parameter declarationsurgentOnly: Bool and project: String? are the knobs users set in Settings. DynamicOptionsProvider on the project parameter lets you supply a live list of projects from your data store.
  3. perform() callback — iOS calls this on your intent when the matching Focus activates. Here we write the active configuration to UserDefaults so the main app can read it instantly, even if it was already in memory.
  4. FocusFilterIntent.current — Reading TaskAppFocusFilter.current inside FocusFilterState.refresh() fetches the intent instance that is currently active. If no Focus with your filter is active it throws, which we handle with try? and reset to defaults.
  5. Foreground re-read via UIApplication.didBecomeActiveNotification — The user may enable or disable a Focus while your app is backgrounded. Re-fetching .current when the app comes to the foreground keeps the UI in sync without any polling.

Variants

Suppress notifications inside the app during Focus

// Add a parameter for in-app notification suppression
@Parameter(title: "Mute In-App Banners", default: false)
var muteBanners: Bool

// In your notification presentation delegate:
func userNotificationCenter(
    _ center: UNUserNotificationCenter,
    willPresent notification: UNNotification,
    withCompletionHandler handler: @escaping (UNNotificationPresentationOptions) -> Void
) {
    Task {
        let mute = (try? await TaskAppFocusFilter.current)?.muteBanners ?? false
        handler(mute ? [] : [.banner, .sound, .badge])
    }
}

Tinting the UI to signal an active Focus

Inject focusState.urgentOnly into your environment and apply .tint(.indigo) conditionally at the root WindowGroup level. This gives users a subtle, app-wide signal that a Work Focus is constraining the view — without needing a dedicated banner or alert.

Common pitfalls

Prompt this with Claude Code

When using Soarias or Claude Code directly to implement this:

Implement a focus filter in SwiftUI for iOS 17+.
Use FocusFilterIntent, @Parameter, DynamicOptionsProvider,
and FocusFilterIntent.current.
Persist active filter config to a shared UserDefaults App Group.
Make it accessible (VoiceOver labels, ContentUnavailableView fallback).
Add a #Preview with realistic sample data.

Drop this prompt into Soarias during the Build phase after your screens are scaffolded — it wires Focus Filter support into your existing data layer without disrupting your view hierarchy.

Related

FAQ

Does this work on iOS 16?

Partially. FocusFilterIntent shipped in iOS 16, so the basic conformance compiles. However, DynamicOptionsProvider and the async .current property work reliably only on iOS 17+. If you target iOS 16, replace DynamicOptionsProvider with a static optionsProvider and read the filter synchronously from UserDefaults instead of calling .current.

How do I test a Focus Filter without toggling my iPhone's Focus mode constantly?

Write a unit test that instantiates TaskAppFocusFilter, sets its urgentOnly and project properties directly, calls perform() with await, then asserts the expected UserDefaults values. For manual testing, add a developer settings screen in #if DEBUG that lets you fake-write the same UserDefaults keys and call focusState.refresh().

What's the UIKit equivalent?

The FocusFilterIntent API is framework-agnostic — it lives in App Intents, not SwiftUI. In a UIKit app you adopt the same FocusFilterIntent conformance and read .current in applicationDidBecomeActive(_:), then update your UIViewController state accordingly. No SwiftUI required.

Last reviewed: 2026-05-11 by the Soarias team.

```