SwiftDux

Predictable state management for SwiftUI applications.

Swift Version Platform Versions License

This is still a work in progress.

Introduction

This is yet another redux inspired state management solution for swift. It’s built on top of the Combine framework with hooks for SwiftUI. This library helps build applications around an elm-like architecture using a single, centralized state container. For more information about the architecture and this library, take a look at the getting started guide.

This library is designed around Combine and SwiftUI. For a more established library that doesn’t require iOS 13, check out ReSwift.

Top Features

Redux-like State Management.

  • Middleware support
  • ActionPlan for action-based workflows.
    • Use them like action creators in Redux.
    • Supports async operations.
    • Supports returning a Combine publisher
  • OrderedState<_> for managing an ordered collection of state objects.

SwiftUI Integration.

  • @MappedState injects state into a view.
  • @MappedDispatch let’s views dispatch actions.
    • Automatically updates the view after each sent action.
    • Supports action plans.
  • Connectable API connects and maps the application state into SwiftUI.
  • onAction(perform:) allows you to track or modify dispatched actions.
  • OrderedState<_> has direct support of List views.

Extras

  • PersistStateMiddleware to automatically persist and restore application state.
  • PrintActionMiddleware Simple middleware that prints out each dispatched action for debugging.

Documentation

Visit the documentation website.

Example

Todo Example App

Installation

Xcode 11

Use the new swift package manager integration to include the library.

Swift Package Manager

Include the library as a dependencies as shown below:

import PackageDescription

let package = Package(
  dependencies: [
    .Package(url: "https://github.com/StevenLambion/SwiftDux.git", majorVersion: 0, minor: 11)
  ]
)

SwiftUI Examples

Adding the SwiftDux store to the SwiftUI environment:

/// Basic store example

var store = Store(state: AppState(), reducer: AppReducer())

/// Advanced store example with middleware

var store = Store(
  state: AppState(),
  reducer: AppReducer(),
  middleware: [
    PrintActionMiddleware(),
    PersistStateMiddleware(JSONStatePersistor())
  ]
)

/// Inject the store

RootView().provideStore(store)

Inject the state into a view using property wrappers:

struct BookListView : View {

  @MappedState private var books: OrderedState<Book>
  @MappedDispatch() private var dispatch

  var body: some View {
    List {
      ForEach(books) { book in
        BookRow(title: book.title)
      }
      .onMove { self.dispatch(BookAction.move(from: $0, to: $1)) }
      .onDelete { self.dispatch(BookAction.delete(at: $0)) }
    }
  }
}

/// Adhere to the Connectable protocol to map a parent state to
/// one that the view requires.

extension BookListView : Connectable {

  func map(state: AppState) -> OrderedState<Book>? {
    state.books
  }

}

Update a view whenever an action is dispatched to the store.

extension BookListView : Connectable {

  /// Views always update when dispatching an action, but
  /// sometimes you may want it to update every time a given
  /// action is sent to the store.

  updateWhen(action: Action) -> Bool {
    action is BookStatusAction
  }

  func map(state: AppState) -> OrderedState<Book>? {
    state.books
  }

}

Modify actions sent from child views before they get to the store

struct AuthorView : View {

  @MappedState private var author: Author

  var body: some View {
    BookListContainer()
      .connect(with: author.id)
      .onAction { [author] action in
        if let action = action as? BookAction {
          return AuthorAction.routeBookAction(for: author.id, action)
        }
        return action
      }
  }

}