CtrlK
BlogDocsLog inGet started
Tessl Logo

dpearson2699/swift-ios-skills

Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.

80

Quality

100%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Advisory

Suggest reviewing before use

Overview
Quality
Evals
Security
Files

focus-patterns.mdskills/focus-engine/references/

Focus Patterns Reference

Contents

  • FocusState patterns
  • Default focus
  • Focused values and scene values
  • Focusable custom views
  • Focus sections for directional movement (macOS/tvOS)
  • Focus restoration after presentations
  • UIKit focus guides
  • Common mistakes checklist

FocusState patterns

struct CheckoutForm: View {
    enum Field: Hashable { case address, city, postalCode }

    @State private var address = ""
    @State private var city = ""
    @State private var postalCode = ""
    @FocusState private var focusedField: Field?

    var body: some View {
        Form {
            TextField("Address", text: $address)
                .focused($focusedField, equals: .address)
                .onSubmit { focusedField = .city }

            TextField("City", text: $city)
                .focused($focusedField, equals: .city)
                .onSubmit { focusedField = .postalCode }

            TextField("Postal Code", text: $postalCode)
                .focused($focusedField, equals: .postalCode)
        }
        .onAppear { focusedField = .address }
    }
}

Use Bool focus state only for one-off cases. Prefer a Hashable enum when more than one control can be focused.

Default focus

struct CommandPaletteView: View {
    enum Target: Hashable { case search }
    @FocusState private var target: Target?

    var body: some View {
        VStack {
            TextField("Search commands", text: $query)
                .focused($target, equals: .search)
        }
        .defaultFocus($target, .search)
    }
}

Choose one unambiguous default target. Competing defaults make focus feel unstable.

Focused values and scene values

struct SelectedDocumentKey: FocusedValueKey {
    typealias Value = Binding<Document>
}

extension FocusedValues {
    var selectedDocument: Binding<Document>? {
        get { self[SelectedDocumentKey.self] }
        set { self[SelectedDocumentKey.self] = newValue }
    }
}

struct DocumentEditor: View {
    @Binding var document: Document

    var body: some View {
        TextEditor(text: $document.body)
            .focusedSceneValue(\.selectedDocument, $document)
    }
}

Use focused scene values when command menus, toolbar actions, or scene-level controls need access to the current focused content.

Focusable custom views

struct TVCardButton: View {
    let title: String
    let action: () -> Void
    @FocusState private var isFocused: Bool

    var body: some View {
        Button(action: action) {
            RoundedRectangle(cornerRadius: 16)
                .fill(isFocused ? Color.accentColor.opacity(0.2) : Color.secondary.opacity(0.12))
                .overlay { Text(title) }
        }
        .buttonStyle(.plain)
        .focusable(interactions: .activate)
        .focused($isFocused)
        .scaleEffect(isFocused ? 1.04 : 1.0)
        .animation(.snappy, value: isFocused)
    }
}

Prefer semantic controls like Button first. Add .focusable(interactions:) only when the custom control needs explicit focus participation. Do not make arbitrary gesture-only views the primary action target on tvOS or keyboard-driven interfaces; expose the action through a semantic control or a custom control with explicit focus and activation behavior.

Focus sections for directional movement

focusSection() is available on macOS 13+ and tvOS 15+.

struct LibraryView: View {
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Button("Recent") { }
                Button("Favorites") { }
                Button("Downloaded") { }
            }
            .focusSection()

            LazyVGrid(columns: columns) {
                ForEach(items) { item in
                    Button(item.title) { open(item) }
                }
            }
            .focusSection()
        }
    }
}

Use focusSection() on macOS or tvOS when the user should move through one group before jumping into another.

Focus restoration after presentations

struct SearchFiltersView: View {
    @State private var isPresentingFilters = false
    @FocusState private var isFiltersButtonFocused: Bool

    var body: some View {
        Button("Filters") {
            isPresentingFilters = true
        }
        .focused($isFiltersButtonFocused)
        .sheet(isPresented: $isPresentingFilters) {
            FiltersSheet()
                .onDisappear {
                    Task { @MainActor in
                        isFiltersButtonFocused = true
                    }
                }
        }
    }
}

Restore focus to the trigger or to the next logical destination after dismissing temporary UI.

UIKit focus guides

final class PlayerViewController: UIViewController {
    private let skipGuide = UIFocusGuide()
    @IBOutlet private weak var playButton: UIButton!
    @IBOutlet private weak var nextEpisodeButton: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addLayoutGuide(skipGuide)
        skipGuide.preferredFocusEnvironments = [nextEpisodeButton]

        NSLayoutConstraint.activate([
            skipGuide.leadingAnchor.constraint(equalTo: playButton.trailingAnchor),
            skipGuide.trailingAnchor.constraint(equalTo: nextEpisodeButton.leadingAnchor),
            skipGuide.topAnchor.constraint(equalTo: playButton.topAnchor),
            skipGuide.bottomAnchor.constraint(equalTo: playButton.bottomAnchor)
        ])
    }
}

A focus guide is an invisible layout guide. Constrain it like any other layout guide, then set preferredFocusEnvironments to the destination views.

Common mistakes checklist

  • Using shared model state to drive @FocusState
  • Forgetting to restore focus after dismissing transient UI
  • Making decorative containers focusable
  • Adding multiple defaults to the same focus region
  • Using UIKit focus guides for layouts SwiftUI can solve with focusSection()
  • Publishing scene-focused values that should only be view-local

skills

.mcp.json

README.md

tile.json