Trying to understand state changes, lists, and pull to refresh…

I'm trying to get my head around ViewStores and how to correctly pare things down to minimise unnecessary redraws…

e.g.

Trying to add pull-to-fresh to a List—currently using SwiftUIRefresh.

The following code triggers a redraw of the whole list on every isRefreshing change. I think, at least in this trivial case, I need something more like the WithViewStore(store) to be stateless, and to be able to rescope the Store to a new ViewStore on pullToRefresh

I'm sure I must be missing something… any tips? Cheers. :)

struct AppState: Equatable {
    var states: [Int] = [0]
    var isRefreshing = false
}

enum AppAction: Equatable {
    case refreshRequested
    case refreshing(Bool)

    case states(index: Int, action: Never)
}

let reducer = Reducer<AppState, AppAction, Void> { state, action, _ in
    switch action {
    case .refreshRequested:
        // Terminate after 1 second, no updates
        return Effect(value: .refreshing(false))
            .delay(for: 1, scheduler: DispatchQueue.main.eraseToAnyScheduler())
            .eraseToEffect()
    case let .refreshing(new):
        state.isRefreshing = new
        return .none
    }
}

struct ContentView: View {
    let store: Store<AppState, AppAction> = Store(initialState: AppState(), reducer: reducer, environment: ())

    var body: some View {
        WithViewStore(store) { viewStore in
            List {
                ForEachStore(
                    store.scope(state: \.states, action: AppAction.states),
                    id: \.self,
                    content: row
                )
            }
            .pullToRefresh(viewStore.binding(get: \.isRefreshing, send: AppAction.refreshing)) {
                viewStore.send(.refreshRequested)
            }
        }
    }

    func row(store: Store<Int, Never>) -> some View {
        print("Rendering row")
        return WithViewStore(store) { viewStore in
            Text("\(viewStore.state)")
        }
    }
}

I think you're trying to over-simplify things, having the view refresh every time you finish something like a network call is not necessarily a bad thing! You'll just end up with weird scopes and lots of boilerplate trying to separate this logic from others.

Also as a side note, SwiftUI is really good at diffing and perform delta changes, you can hammer view "redraws" much you like and you'll likely not hit a performance decrease!

2 Likes

So even though the view is being called again (there is a print in the body), SwiftUI may not actually be rerendering it?

Is there a good way to debug what SwiftUI is actually drawing? It would be interesting to see…

To get more about what is going on under SwiftUI, you can use Instruments with SwiftUI template

1 Like