
TCA: Using Composable Architecture for creating an Expandable Picker
Being on the way of working with TCA in a potentially large application, the benefits of using TCA become more and more obvious in a variety of scopes.
In this article, I’d like to share my experience in “Thinking TCA” by building a small component that presents a Form
row that can be tapped; when tapped, it expands and shows a picker for a set of values; after picking a value, the row display is updated. And the row can be collapsed, certainly…



Usually, I’d approach this by first creating an empty view, adding a @State
var for the expansion status, adding a subview for the expandable row, adding a picker to the expanded content, adding properties for the picker’s content, and adding a @Binding
var for the picker’s index. Pretty straightforward.
With TCA, I tried to convert that top-down approach to States, Actions and Reducers, and I created a pretty complicated bundle for that view. Which I didn’t like. The separation of concerns was hurt, and fixing that was not so nice.
So, I tried to think bottom-up. What is the first view we need?
The very first view I created was the ExpandableRow
.
For TCA, we need a store first. The row has to states, collapsed and expanded. So a Bool
value is fully sufficient as TCA State.
What actions do we need? The Row simply reacts on a tap, so we need an action .toggleVisibility
that is triggered by .onTapGesture
.
Independent of the state, we’ll present a placeholder that is an injected view; if the state is set to true
, we’ll show the injected content view.
Now, for TCA, we need an action and a reducer. How do they look like?
The TCA Action is pretty simple. The reducer is it as well, we just invert the current state (which is, you’ll remember, a Bool
).
Now, we can use the view in a preview and see, if it works:
So, we’re done with that component. What next?
When the row is expanded, we want to see a picker. So, the injected content
to the ExpandableRow
view must be some kind of picker.
Here, again, we have a TCA Store, an injected placeholder view, and a Picker
with an InlinePickerStyle.
To fully understand the picker’s call, let’s dive into the other TCA stuff:
The picker needs a selectedIndex
, which we provide as Int
, and a list of selectableValues
, provided as array of String
.
Because the picker shall modify the selectedIndex
, we need a binding. The TCA framework provides a special feature for the viewStore
where the current value is handed to the picker using the get
property, and the action to be triggered by the picker when changing the value is provided by the send
property.
The values are a simple loop over the selectableValues
of the viewStore
.
Here, again, we can check that it’s working by using a preview:
The TCA Action now has a parameter, the index provided by the picker. The reducer therefore has to react on that action by setting the state’s selectedIndex
to the provided index
.
Obviously, you’ll see… nothing, so you may want to run the preview in debug mode and add a debugPrint
statement to the action, to see that the selectedIndex
really changes.
Again, a standalone component without any dependencies, except the injected `placeholder` view.
Now we have to combine both views, the ExpandableRow
and the ItemPicker
in order to get our final view.
This is how the view looks like:
Here again, we need a TCA store with a state and an action definition. With the viewStore
, we can then use the ExpandableRow
where the content
provides the ItemPicker
.
We now could provide the stores for the subview by using the standard initializer with state:reducer:environment
. However, if we do that, those views are completely independent: We cannot react here on changes there.
So, instead we need to use the store.scope(state:action:)
. To understand this, we have a look on the remaining TCA definitions:
What we see here is that we have a main
reducer, that combines the two reducers from the subviews by a pullback
of their actions. To have this work, we need two meta actions expandableRow
and itemPicker
with the respective subactions as parameter.
We may want to combine this with a local
reducer where we can react on whatever happens in the subviews (in this case: some debugPrint
statements).
So, by using the .scope(...)
statement, we enable our reducer to access those of the subviews, and with the .combine
and .pullback
operations, we can react here on what happens below.
Here again, we can check using a preview if everything’s working as expected:
This concludes our journey to another TCA application that gives us an expandable item picker that we can use itself as a component in a larger context, like we did here with the ExpandableRow
and the ItemPicker
. This makes clear, how transparent and easy large and complex application can be built using TCA, while keeping each component as isolated as possible.
I hope this is another step for your to better understand the benefits of TCA. Let me know if it was helpful for you, or if you have suggestions for more reflections.