open class ViewControllerWithLocalState<V: ViewControllerModellableView & UIView>: ViewController<V> where V.VM: ViewModelWithLocalState

Special case of a ViewController that contains a LocalState.


A ViewController is managing the UI of a screen, listening for Katana global state changes, keeping the UI updated and dispatching actions in response to user interactions in order to change the global state.

There are times when you have some kind of state information that is only specific to the screen managed by a ViewController, like for instance the item selected in a list. In this case, in order to avoid polluting the global state, you can represent that information inside a LocalState and promote that ViewController to be a ViewControllerWithLocalState.

A ViewControllerWithLocalState contains a localState variable that you can change directly in order to represent local state changes. The ViewControllerWithLocalState will be listening for changes of both global and local states, updating the UI using the appropriate ViewModelWithLocalState.

You can change the global state dispatching Katana actions like in every ViewController. You can change the LocalState manipulating directly the localState variable.

The lifecycle of a ViewControllerWithLocalState is the same as a normal ViewController, please refer to that for more details.

   struct GlobalState: State {
     var todos: [String] = [
       "buy milk",
       "find a unicorn",
       "visit Rome"
   struct RemoveTodo: AppAction {
     var index: Int

     func updatedState(inout currentState: GlobalState) {
       currentState.todos.remove(at: index)
   struct ListLocalState: LocalState {
     var selectedIndex: Int
   struct TodolistViewModel: ViewModelWithLocalState {
     var todos: [String]
     var selectedIndex: Int

     init?(state: GlobalState?, localState: ListLocalState) {
       guard let state = state else { return nil }
       self.todos = state.todos
       self.selectedIndex = localState.selectedIndex
   class TodoListView: UIView, ViewControllerModellableView {

     // subviews
     var todoListView = ListView()

     // interactions
     var didTapToRemoveItem: ((Int) -> ())?
     var didSelectItem: ((Int) -> ())?

     // setup
     func setup() {
       self.todoListView.on(.selection) { [unowned self] indexPath in
       self.todoListView.on(.deleteItem) { [unowned self] indexPath in

     // style
     func style() {
       self.backgroundColor = .white
       self.todoListView.backgroundColor = .white

     // update
     func update(oldModel: CounterViewModel?) {
       self.todoListView.source = model?.todos ?? []
       self.todoListView.selectedIndex = model?.todos

     // layout
     override func layoutSubviews() {
       self.todoListView.frame = self.bounds
   class TodoListViewController: ViewControllerWithLocalState<TodoListView> {

   override func setupInteraction() {
     self.rootView.didTapRemoveItem = { [unowned self] index in
       self.dispatch(RemoveTodo(index: index))
     self.rootView.didSelectItem = { [unowned self] index in
       self.localState.selectedIndex = index