Skip to content

Mediator ​

Mediator owns communication among components that would otherwise reference each other directly. The components report events to the mediator; it decides what changes next.

When it helps in iOS ​

A search screen has a query field, filter chips, and a results list. If each component updates the others, dependencies become tangled. A view model can mediate those interactions.

Original example: search screen state ​

swift
import Foundation

struct SearchItem {
    let title: String
    let category: String
}

@MainActor
final class SearchMediator {
    private let allItems: [SearchItem]
    private(set) var visibleItems: [SearchItem] = []
    private var query = ""
    private var category: String?

    init(items: [SearchItem]) {
        allItems = items
        recompute()
    }

    func queryChanged(to value: String) {
        query = value
        recompute()
    }

    func categorySelected(_ value: String?) {
        category = value
        recompute()
    }

    private func recompute() {
        visibleItems = allItems.filter { item in
            (category == nil || item.category == category) &&
            (query.isEmpty || item.title.localizedCaseInsensitiveContains(query))
        }
    }
}

The query field and chips call methods on SearchMediator; the list renders visibleItems. No component needs a reference to another component.

Production decisions ​

Publish visibleItems through @Published or Observation for SwiftUI, or notify a UIKit controller to apply a diffable snapshot. Debounce remote searches and cancel stale requests. A mediator should coordinate a bounded feature, not turn into a global event bus.

Why the components do not talk directly ​

The query field and category chips both affect the results list. If the field owned a reference to the chips and the chips owned a reference to the list, every new interaction would add another connection. SearchMediator instead owns the current query, selected category, and derived visibleItems. Each input component reports only its event. recompute() is the single place where those inputs are combined, which prevents two components from applying different filtering rules.

From example to an iOS search feature ​

A SwiftUI screen can bind text changes and chip taps to mediator methods, then render visibleItems from an observable wrapper. In UIKit, the controller forwards UI callbacks and applies a diffable snapshot after recomputation. For remote search, the mediator should issue a debounced request and cancel the previous request when the query changes. Display results only if they correspond to the latest query; otherwise an older response may overwrite a newer one.

Failure modes and verification ​

Test query-only, category-only, combined, and clearing either input. Keep the mediator scoped to one screen or flow. A global mediator with knowledge of unrelated features becomes a service locator or event bus. If there is only one control and one result, direct state binding may be simpler. Mediator coordinates peers; Facade simplifies calls to subsystems for a client.

All patterns

Released under the MIT License.