How Decompose Integrates with SwiftUI for iOS Development

Decompose integrates with SwiftUI by exposing Kotlin Multiplatform components through observable Value properties bridged via a custom @StateValue property wrapper, while navigation is handled through a StackView adapter that synchronizes Decompose's ChildStack with SwiftUI's NavigationStack or a UIKit fallback.

Decompose is a Kotlin Multiplatform library that enforces separation between business-logic components and UI layers. When targeting iOS, the component logic compiles to a native framework while the user interface is built with SwiftUI. This article explains the exact integration patterns used in the arkivanov/decompose repository, including state observation, navigation bridging, and the required helper files.

State Propagation with Value and StateValue

Decompose components expose mutable state through Value<T> (or MutableValue<T>) interfaces defined in the Kotlin codebase. Since SwiftUI cannot directly observe Kotlin classes, the integration relies on a bridging mechanism.

The StateValue Property Wrapper

The sample application includes a StateValue.swift file that defines a property wrapper converting Decompose's Value into an ObservableObject that SwiftUI can observe:

@propertyWrapper struct StateValue<T : AnyObject>: DynamicProperty {
    @ObservedObject private var obj: ObservableValue<T>
    var wrappedValue: T { obj.value }
    init(_ value: Value<T>) { obj = ObservableValue(value) }
}

When you apply @StateValue to a property in a SwiftUI view, the view automatically re-renders whenever the underlying Kotlin Value changes. This pattern appears throughout the iOS sample code, particularly in CountersView.swift.

Decompose models navigation using ChildStack<Configuration, Child>, which maintains a stack of child components with associated configurations. SwiftUI's NavigationStack requires Identifiable data and specific binding patterns that differ from Decompose's API.

Adapting to SwiftUI NavigationStack

The StackView.swift helper bridges these APIs by reading the current ChildStack via @StateValue and constructing a NavigationStack whose path is bound to the stack items (excluding the root). According to the source implementation in StackView.swift, this adapter handles the synchronization between Decompose's navigation state and SwiftUI's declarative navigation interface.

UIKit Fallback for Older iOS Versions

For iOS versions prior to 16.1, StackView.swift provides a fallback implementation using a UIKit UINavigationController wrapped in UIViewControllerRepresentable. This ensures that Decompose navigation works across the supported iOS version range while still allowing SwiftUI developers to declare navigation declaratively.

Complete Implementation Example

A typical SwiftUI screen integrates both state observation and navigation. The following example from the sample project demonstrates a counter view that observes a Kotlin component:

struct CounterView: View {
    private let counter: Counter          // Kotlin component
    @StateValue private var state: CounterState

    init(_ counter: Counter) {
        self.counter = counter
        _state = StateValue(counter.state)   // subscribe to Decompose Value
    }

    var body: some View {
        VStack(spacing: 8) {
            Text(state.value.text)
            Button(action: counter.increment) { Text("Increment") }
        }
    }
}

For navigation, the CountersView.swift file demonstrates using StackView with a ChildStack:

struct CountersView: View {
    private let counters: CountersComponent
    @StateValue private var stack: ChildStack<AnyObject, CounterComponent>

    init(_ counters: CountersComponent) {
        self.counters = counters
        _stack = StateValue(counters.stack)
    }

    var body: some View {
        StackView(
            stackValue: stack,
            getTitle: { $0.title },
            onBack: { index in counters.onBack(to: index) }
        ) { child in
            CounterView(child.instance)
        }
    }
}

Setting Up DecomposeHelpers in Your Project

Due to limitations in publishing Kotlin/Native code that depends on SwiftUI (see issue #206 in the repository), Decompose cannot distribute the SwiftUI helpers as a binary framework. Instead, you must manually copy the helper files into your iOS project.

Copy the following files from the DecomposeHelpers folder of the sample app into your Xcode project:

These files establish the bridge between your Kotlin components and SwiftUI views.

Summary

  • Decompose exposes state through Value<T> interfaces that require bridging for SwiftUI consumption.
  • The @StateValue property wrapper in StateValue.swift converts Kotlin Value instances into ObservableObject for SwiftUI reactivity.
  • Navigation uses ChildStack managed by Decompose, rendered in SwiftUI through the StackView adapter which supports both NavigationStack (iOS 16+) and UIKit fallback.
  • SwiftUI helpers must be copied manually into projects due to Kotlin/Native publishing limitations (issue #206).

Frequently Asked Questions

Can Decompose publish SwiftUI helpers as a binary framework?

No. Due to current limitations in the Kotlin/Native toolchain regarding publishing code that depends on SwiftUI, Decompose cannot distribute the SwiftUI integration helpers as a pre-built binary. According to issue #206 in the repository, you must manually copy the DecomposeHelpers folder containing StateValue.swift and StackView.swift into your iOS project.

How does StateValue handle memory management between Kotlin and Swift?

The StateValue property wrapper creates an ObservableValue object that holds a reference to the Kotlin Value<T>. This intermediate object conforms to ObservableObject and manages the subscription lifecycle. When the SwiftUI view is destroyed, the ObservableValue is deallocated, which properly cleans up the subscription to the Kotlin state, preventing memory leaks across the language boundary.

What iOS versions are supported for Decompose SwiftUI integration?

The StackView implementation provides two rendering paths. For iOS 16.1 and later, it uses the native SwiftUI NavigationStack with proper path binding. For earlier iOS versions, it falls back to a UINavigationController wrapped via UIViewControllerRepresentable. This ensures compatibility with iOS versions predating SwiftUI's modern navigation APIs while leveraging the latest features on newer devices.

Where can I find the complete sample implementation?

The complete integration examples are located in the sample/app-ios directory of the arkivanov/decompose repository. Key files include CountersView.swift which demonstrates navigation with StackView, and the DecomposeHelpers folder containing StateValue.swift and StackView.swift. These files serve as the reference implementation for integrating Decompose components with SwiftUI.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →