To receive different typed completions in one macOS SwiftUI screen, give each component a generic adapter or typed closure. Swift does not let the screen conform to the same protocol twice with different associated types.

This tutorial builds a small desktop preview whose buttons apply a synthetic title and column count. The task is to keep each component’s completion contract checked by the compiler while allowing one parent view to receive both values.

Why can’t one type adopt both completion contracts?

A protocol’s associated type belongs to its conformance. If a completion protocol declares an associated type named Value, a conforming type must provide a consistent choice for that type. Adding two overloaded completion methods does not create two independent conformances. Swift’s language guide explains how associated types acquire their concrete types when a protocol is adopted. Generics — Associated Types.

For example, a screen cannot adopt one completion protocol once with Value equal to TitleChoice and again with Value equal to LayoutChoice. Moving the declarations into separate extensions does not change that restriction. SE-0143 explicitly preserves the ban on repeated conformance, including conditional conformances whose conditions are disjoint. Conditional conformances.

Primary associated types do not remove this boundary. The syntax CompletionHandling<TitleChoice> expresses a constraint on an associated type; it does not create a separate protocol identity for every argument. SE-0346 introduced this constraint syntax in Swift 5.7. Lightweight same-type requirements for primary associated types.

The supported design used below moves conformance onto a generic adapter. There is one conformance declaration, and the application creates differently specialized adapter values for the two components. The parent owns the resulting state without adopting the completion protocol.

Which completion design fits your component?

Use the following decision aid before changing the interface. These are design recommendations derived from the documented type rules, rather than additional Swift language requirements.

Component requirement Suggested design What remains explicit
One callback, with an interface you control A typed closure The callback’s input type
An existing protocol-based API A generic conforming adapter The protocol and its associated type
One event channel carrying several named alternatives An enum with associated values Every event case the receiver handles
Several unrelated operations with different meanings Separate named protocols Each operation’s distinct requirements

For this example, assume the reusable component already expects a protocol-based handler. A generic adapter preserves that boundary and keeps the screen’s handling code next to its state updates. If the component only needs a callback and has no protocol requirement, a closure is the smaller design.

A generic completion method would express a different promise. A method such as complete<T>(_ value: T) must work for every type permitted by its constraints. It does not express the narrower requirement that each component delivers one particular result type. Swift’s generics documentation describes how generic functions operate over the types selected at their call sites. Generics — Generic Functions.

Build the typed completion adapter

Use a macOS SwiftUI application target with a Swift 5.7-or-later compiler and a macOS 12-or-later deployment target for this example. The compiler requirement comes from the primary associated type declaration. The deployment target is a conservative baseline for this tutorial, not a claim that every API shown first appeared in macOS 12.

The three files below together form the complete example. Add them to the same application target, replacing the generated app entry point and content view so the target has only one @main declaration. No packages, assets, sample files, or external inputs are needed.

Create CompletionContracts.swift with these declarations:

import SwiftUI

struct TitleChoice {
    let text: String
}

struct LayoutChoice {
    let columns: Int
}

@MainActor
protocol CompletionHandling<Value> {
    associatedtype Value
    func complete(_ value: Value)
}

@MainActor
struct CompletionAdapter<Output>: CompletionHandling {
    typealias Value = Output

    private let receive: @MainActor (Output) -> Void

    init(_ receive: @escaping @MainActor (Output) -> Void) {
        self.receive = receive
    }

    func complete(_ value: Output) {
        receive(value)
    }
}

@MainActor
struct CompletionButton<Value, Handler: CompletionHandling>: View
where Handler.Value == Value {
    let title: String
    let value: Value
    let handler: Handler

    var body: some View {
        Button(action: {
            handler.complete(value)
        }) {
            Text(title)
        }
    }
}

The constraint Handler.Value == Value connects the component’s output to the handler’s accepted input. A button carrying a TitleChoice therefore requires a handler whose associated type is TitleChoice. The explicit type alias makes the adapter’s associated type easy to inspect.

The adapter stores its closure for later invocation, so its initializer marks that parameter @escaping. Swift’s closures documentation requires this annotation when a closure can outlive the function call that receives it. Closures — Escaping Closures.

The protocol, adapter, and view use @MainActor, and the stored callback preserves that isolation in its function type. This makes the intended UI execution context part of the contract. SE-0316 documents both global actor isolation and global actor qualified function types. Global actors.

Apple documents Button as taking an action and a label. Here the action invokes the handler with the component’s already typed value; Text(title) supplies its visible label. Button.

Connect two result types to one SwiftUI screen

Create ContentView.swift. The title and layout results have separate state destinations, and each adapter closure updates only its matching destination.

import SwiftUI

@MainActor
struct ContentView: View {
    @State private var previewTitle = "Sample Board"
    @State private var columnCount = 1

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text("Component Preview")
                .font(.headline)

            Text(previewTitle)
            Text("Columns: \(columnCount)")

            HStack {
                CompletionButton(
                    title: "Apply title",
                    value: TitleChoice(text: "Pebble Board"),
                    handler: CompletionAdapter<TitleChoice> { choice in
                        previewTitle = choice.text
                    }
                )

                CompletionButton(
                    title: "Apply layout",
                    value: LayoutChoice(columns: 3),
                    handler: CompletionAdapter<LayoutChoice> { choice in
                        columnCount = choice.columns
                    }
                )
            }
        }
        .padding(24)
        .frame(minWidth: 420, minHeight: 180)
    }
}

Create TypedCompletionsApp.swift as the application’s sole entry point:

import SwiftUI

@main
@MainActor
struct TypedCompletionsApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

The parent keeps its initial values in private @State properties. Apple documents that SwiftUI manages this storage and updates dependent views when the values change. Its current documentation also notes that builds with Xcode 27 or later use the State macro instead; the declaration style shown here follows the earlier property-wrapper baseline. State.

WindowGroup hosts the screen’s view hierarchy. Apple documents independent state storage for each window created by the group, so this example’s state belongs to each preview window. WindowGroup.

There are two specialized adapter types: CompletionAdapter<TitleChoice> and CompletionAdapter<LayoutChoice>. Both come from the single generic adapter declaration. ContentView conforms only to View; it receives the two results through the closures it supplies.

Verify the behavior and diagnose mismatches

No Swift compiler is installed in the research environment, so these files were inspected but were not compiled or run. Build the application target on your Mac, then use the following checks to verify the example’s intended behavior.

  1. Launch the app. The preview should display Sample Board and Columns: 1.
  2. Click Apply title. The title should become Pebble Board, while the column count remains unchanged.
  3. Click Apply layout. The count should become Columns: 3, while the chosen title remains unchanged.
  4. Click either button again. It should apply the same synthetic value again.

To inspect the type boundary, temporarily change the first button’s handler specialization to CompletionAdapter<LayoutChoice> and give its closure an empty body. Leave that button’s value as TitleChoice. The component then requires incompatible types to be equal through Handler.Value == Value, so compilation should fail. Restore the original handler afterward.

If the compiler reports a repeated conformance, look for another declaration that makes the same nominal type adopt CompletionHandling. Retain the single generic adapter conformance and create another adapter value for the additional output. Two constrained extensions declaring the same conformance remain subject to Swift’s repeated-conformance restriction. Conditional conformances — Multiple conformances.

If the compiler rejects the protocol’s primary associated type syntax, check the compiler version. For an older compiler, the adapter design can retain an ordinary associated type declaration instead. This is a replacement fragment for the protocol declaration only; keep the other declarations unchanged:

@MainActor
protocol CompletionHandling {
    associatedtype Value
    func complete(_ value: Value)
}

That change removes this example’s Swift 5.7 syntax dependency. It does not remove the compiler requirement for @MainActor, introduced in Swift 5.5, or change the tutorial’s deployment target. The component already uses a generic where clause, so it needs no primary associated type syntax at its use site. Lightweight same-type requirements, Global actors.

What does this completion contract guarantee?

The sample guarantees a matching payload type at each component boundary. Its implementation calls the handler once per button activation, synchronously. It does not implement an asynchronous operation, cancellation, persistence, or a once-only completion lifecycle. Those would require additional behavior and an explicit contract.

The sample also uses known local values. If a future component accepts arbitrary column counts, validate that input before updating the preview and define how rejection is reported. A matching Swift type alone does not establish that an integer is a useful column count.

For this desktop task, keep one adapter per typed endpoint and let the parent own the resulting state. That gives the two components precise completion contracts through one reusable conformance declaration, with each result’s destination visible at the call site.

Sources