• Stars
    star
    646
  • Rank 67,218 (Top 2 %)
  • Language
    Swift
  • License
    Apache License 2.0
  • Created almost 5 years ago
  • Updated over 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A very naive implementation of Redux using Combine BindableObject to serve as an example

Swift

SwiftUIFlux

A very naive implementation of Redux using Combine BindableObject to serve as an example

Usage

In this little guide, I'll show you two ways to access your proprerties from your state, one very naive, which works by using direct access to store.state global or injected @EnvironmentObject and the other one if you want to use ConnectedView.

You first have to make a struct which will contain your application state and it needs to conform to FluxState. You can add any substate you want.

import SwiftUIFlux

struct AppState: FluxState {
    var moviesState: MoviesState
}

struct MoviesState: FluxState, Codable {
    var movies: [Int: Movie] = [:]
}

struct Movie: Codable, Identifiable {
    let id: Int
    
    let original_title: String
    let title: String
}

The second piece you'll need is your app main reducer, and any substate reducer you need.

import SwiftUIFlux

func appStateReducer(state: AppState, action: Action) -> AppState {
    var state = state
    state.moviesState = moviesStateReducer(state: state.moviesState, action: action)
    return state
}

func moviesStateReducer(state: MoviesState, action: Action) -> MoviesState {
    var state = state
    switch action {
    case let action as MoviesActions.SetMovie:
        state.movies[action.id] = action.movie

    default:
        break
    }

    return state
}

Finally, you have to add you Store which will contain you current application state AppState as a global constant.

let store = Store<AppState>(reducer: appStateReducer,
                            middleware: nil,
                            state: AppState())

You instantiate with your initial application state and your main reducer function.

And now the part where you inject it in your SwiftUI app. The most common way to do it is in your SceneDelegate when your initiate your view hierarchy is created. You should use the provided StoreProvider to wrap you whole app root view hiearchy inside it. It'll auto magically inject the store as an @EnvironmentObject in all your views.

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
           
            let controller = UIHostingController(rootView:
                StoreProvider(store: store) {
                    HomeView()
            })
            
            window.rootViewController = controller
            self.window = window
            window.makeKeyAndVisible()
        }
        }
}

From there, there are two ways to access your state properties.

In any view where you want to access your application state, you can do it using @EnvironmentObject

struct MovieDetail : View {
    @EnvironmentObject var store: Store<AppState>
    
    let movieId: Int
    
    var movie: Movie {
        return store.state.moviesState.movies[movieId]
    }

    //MARK: - Body
    var body: some View {
        ZStack(alignment: .bottom) {
            List {
                MovieBackdrop(movieId: movie.id)
                // ...
            }
        }
    }
}

This is the naive, brutal, not so redux compliant way, but it works.

Note that any view where you add explicilty add @EnvironmentObject var store: Store<AppState>will be redraw anywhere it's needed as your state is updated. The diff is done at the view level by SwiftUI.

And it's efficient enough that this library don't have to provide custom subscribers or a diffing mechanism. This is where it shine compared to a UIKit implementation.

You can also use ConnectedView, this is the new prefered way to do it as it feels more redux like. But the end result is exactly the same. You just have a better separation of concerns, no wild call to store.state and proper local properties.

struct MovieDetail : ConnectedView {  
    struct Props {
        let movie: Movie
    }  

    let movieId: Int
    

    func map(state: AppState, dispatch: @escaping DispatchFunction) -> Props {
        return Props(movie: state.moviesState.movies[movieId]!)
    }

    func body(props: Props) -> some View {
        ZStack(alignment: .bottom) {
            List {
                MovieBackdrop(movieId: props.movie)
                // ...
            }
        }
    }
}

You have to implement a map function which convert properties from your state to local view props. And also a new body method which will provide you with your computed props at render time.

You can look at more complexe examples from my app here and there.

At some point, you'll need to make changes to your state, for that you need to create and dispatch Action

AsyncAction is available as part of this library, and is the right place to do network query, it'll be executed by an internal middleware when you dispatch it.

You can then chain any action when you get a result or an error.

struct MoviesActions {
    struct FetchDetail: AsyncAction {
        let movie: Int
        
        func execute(state: FluxState?, dispatch: @escaping DispatchFunction) {
            APIService.shared.GET(endpoint: .movieDetail(movie: movie))
            {
                (result: Result<Movie, APIService.APIError>) in
                switch result {
                case let .success(response):
                    dispatch(SetDetail(movie: self.movie, movie: response))
                case .failure(_):
                    break
                }
            }
        }
    }

    struct SetDetail: Action {
        let movie: Int
        let movie: Movie
    }

}

And then finally, you can dispatch them, if you look at the code of the reducer at the begining of this readme, you'll see how actions are reduced. The reducer is the only function where you are allowed to mutate your state.

As everything in the AppState are Swift struct, you actually return a new copy of your state, which is aligned with the Redux achitecture.

struct MovieDetail : View {
    @EnvironmentObject var store: Store<AppState>
    
    let movieId: Int
    
    var movie: Movie {
        return store.state.moviesState.movies[movieId]
    }

    func fetchMovieDetails() {
        store.dispatch(action: MoviesActions.FetchDetail(movie: movie.id))
    }

    //MARK: - Body
    var body: some View {
        ZStack(alignment: .bottom) {
            List {
                MovieBackdrop(movieId: movie.id)
                // ...
            }
        }.onAppear {
            self.fetchMovieDetails()
        }
    }
}

More Repositories

1

MovieSwiftUI

SwiftUI & Combine app using MovieDB API. With a custom Flux (Redux) implementation.
Swift
6,369
star
2

IceCubesApp

A SwiftUI Mastodon client
Swift
4,506
star
3

RedditOS

The product name is Curiosity, a SwiftUI Reddit client for macOS Big Sur
Swift
3,915
star
4

SwiftHN

A Hacker News reader in Swift
Swift
1,707
star
5

ACHNBrowserUI

Animal Crossing New Horizon companion app in SwiftUI
Swift
1,654
star
6

MortyUI

A very simple Rick & Morty app to demo GraphQL + SwiftUI
Swift
461
star
7

HackerSwifter

A Swift Hacker News library
Swift
170
star
8

DMCustomModalViewController

A UIViewController which take a root view controller and present it modally with a nice animation
Objective-C
114
star
9

DMCustomTransitions

Some custom transitions for iOS 7
Objective-C
94
star
10

MovieSwiftUI2

A complete reinvention
Swift
64
star
11

RunewordsApp

A SwiftUI app to filter & search runewords for Diablo II
Swift
58
star
12

The-Roundtable

An Elden Ring companion app using SwiftUI + GraphQL
Swift
48
star
13

DMFilterView

A UIView Subclass which add itself at the bottom of any view.
Objective-C
39
star
14

OSRSUI

Old School Runescape database browser in SwiftUI
Swift
35
star
15

SwiftUIDemo

SwiftUI + Redux
Swift
35
star
16

Sublime-Hacker-News-Reader

Read Hacker News front page right from Sublime Text.
Python
24
star
17

SwiftUIKit

A package with the missing SwiftUI components
Swift
21
star
18

PopupViewController

UIViewController drop in replacement with much more customisation
Swift
21
star
19

DMSocialContactsList

An objective-c contacts picker example which fetch local and Facebook contacts, merge and sort them. Plus handle selection. Useful to send SMS, Email, Facebook invitation.
Objective-C
19
star
20

Musix

A macOS Apple Music client using MusicKit
Swift
17
star
21

DMRESTRequest-objc

Super simple objective-c REST request wrapper.
Objective-C
14
star
22

PocketSwiftUI

Pocket SwiftUI: Learn and practice SwiftUI on the go
14
star
23

icecubesweb

Experimental SwiftUI app for the web
Swift
12
star
24

LittleOrion

Master of Orion like for your iOS devices, for now it's mostly nothing...
Swift
12
star
25

SwiftTests

Swift
10
star
26

CryptoTickerSwiftUI

A SwiftUI crypto ticker (show latest transactions) on the CoinbasePro/GDAX socket API
Swift
8
star
27

DMGesturedNavigationController

Objective-C
8
star
28

Date-ACNHEvents

Swift
7
star
29

OSRSUIGraphQL

A GraphQL Old School RuneScape + SwiftUI helper app
Swift
6
star
30

GrailerApp

A Diablo 2 app to track your items and much, much more
Swift
6
star
31

FrenchKitSwiftUIClassroom

Swift
5
star
32

dimillian

4
star
33

pico8-suvivor

A vampire survivor clone for pico-8
C
3
star
34

NotesCards

Swift
3
star
35

dimillian.github.io

HTML
3
star
36

iOS-ReactHN

An Hacker News reader built with React Native
3
star
37

GDAXTicker

A GDAX Ticker in Swift
Swift
3
star
38

dimillian.app

dimillian.app website
MDX
2
star
39

PTCGCollector

An app to collect & browse your Pokemon Trading Card collection
2
star
40

TronCycle

A Tron inspired game in Swift.
Swift
2
star
41

vCard

My personal website at http://thomasricouard.info
CSS
2
star
42

Galaxy-Traders

1
star
43

Blog

My new Blog powered by Hugo
Shell
1
star
44

rSDK

Raven SDK
Objective-C
1
star
45

Raven-For-Mac

Raven Browser
Objective-C
1
star
46

PicSpeak

Something cool
1
star
47

iOSReduxBoilerplate

iOS Redux app example
1
star
48

folio

New folio in react
TypeScript
1
star
49

McReddit

Swift
1
star
50

No-Name

Something in Swift to play with.
1
star
51

PopApp

TBD
Swift
1
star
52

blog-articles

Articles of http://blog.thomasricouard.info
1
star
53

objective-c-stuff

Various stuff
Objective-C
1
star