mirror of
https://github.com/pointfreeco/swift-composable-architecture.git
synced 2025-12-20 09:11:33 +01:00
* more main actor audit * wip * wip * fix * better task result == * task result tests * wip * wip * wip * wip * wip * wip * wip * wip * wip * fix merge conflicts * wip * wip * lots of doc fixes and modernizations * lots more docs and better hashable conformance for TaskResult * more docs * clean up * more tests and docs * clean up * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * small clean up * wip * wip * wip * wip * wip * wip * wip * explicit * wip * fix bug in TestStore.receive * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * fixes * wip * tools for non-deterministic TestStore.receive * fix * wip * wip * remove inAnyOrder stuff * wip * wip * wip * wip * wip * wip * wip * convert download case study to use async/await * animations * fix tests * remove executor experiment * wip * wip * wip * wip * wip * speech simplification * wip * wip * wip * wip * wip * wip * add a few todos * wrote some tests * simplify speech recognizer * fix tests * update some docs about error throwing behavior * wip * wip * fix * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * Swift 5.5.2 fixes * wip * Bump timeout * wip * wip * Finesse * proper way to detect main queue * extra guard * revert main queue check * move stuff around * docs * fixed a bunch of warnings * Fix references * clean up * clean up * fix a bunch of warnings * clean up * un-soft deprecate concatenate * async teststore.send * fix uikit tests * drop sendable * wip * wip * wip * wip * wip * clean up * clean up * reorganize, remove extra task cancellation handler * wip * wip * wip * wip * wip * wip * Make TestStore.send async (#1190) * async teststore.send * fix uikit tests * Converted all tests to async * clean up * added docs * Update Sources/ComposableArchitecture/TestStore.swift Co-authored-by: Stephen Celis <stephen@stephencelis.com> * Update Sources/ComposableArchitecture/TestStore.swift Co-authored-by: Stephen Celis <stephen@stephencelis.com> * docs and readme update * Update README.md * Update Tests/ComposableArchitectureTests/StoreTests.swift Co-authored-by: Stephen Celis <stephen@stephencelis.com> * fix * Update Sources/ComposableArchitecture/TestStore.swift Co-authored-by: Stephen Celis <stephen@stephencelis.com> * Update Sources/ComposableArchitecture/TestStore.swift Co-authored-by: Stephen Celis <stephen@stephencelis.com> * Update Sources/ComposableArchitecture/TestStore.swift Co-authored-by: Stephen Celis <stephen@stephencelis.com> * clean up Co-authored-by: Stephen Celis <stephen@stephencelis.com> * wip * wip * wip * make fetchNumber throwing and fix tests * effect basics clean up * use local state for isLoading in refreshable case study * clean up * fix test * wip * wip * wip * wip * wip * wip * fixes * clean up * clean up * Simplify * wip * clean up * wip * AsyncStream.finished() * give Send a public initializer * make send public * temporarily make box public * remove concurrency flag * wip * wip * wip * wip * wip * docs * speech * simplify * clean up; * unchecked sendable * clean up * clean up * wip * docs * docs * more docs * lots of docs * wip * wip * wip * more docs for streamWithContinuation * wip * wip * wip * Make internal, too * wip * Remove sendability detection It breaks things, like: let request = UncheckedSendable( SKProductsRequest(productIdentifiers: [] ) // UncheckedSendable<NSObject> // *not* _<SKProductsRequest> * wip * doc clean up; * fixed some todos * docs * wip * remove thread safety FAQ from readme * fix test * wip * docs clean up * docs clean up * added a testing article and fixed some docs * rearrange * docs clean up * wip * Update Sources/ComposableArchitecture/Documentation.docc/Articles/Testing.md Co-authored-by: Thomas Grapperon <35562418+tgrapperon@users.noreply.github.com> * Update Sources/ComposableArchitecture/Effects/ConcurrencySupport.swift Co-authored-by: Thomas Grapperon <35562418+tgrapperon@users.noreply.github.com> * Update Sources/ComposableArchitecture/Effects/ConcurrencySupport.swift Co-authored-by: Thomas Grapperon <35562418+tgrapperon@users.noreply.github.com> * Update Sources/ComposableArchitecture/Effects/ConcurrencySupport.swift Co-authored-by: Thomas Grapperon <35562418+tgrapperon@users.noreply.github.com> * Update Sources/ComposableArchitecture/Effects/ConcurrencySupport.swift Co-authored-by: Thomas Grapperon <35562418+tgrapperon@users.noreply.github.com> * Update Sources/ComposableArchitecture/Documentation.docc/Articles/Testing.md Co-authored-by: Thomas Grapperon <35562418+tgrapperon@users.noreply.github.com> * Update Sources/ComposableArchitecture/Documentation.docc/Articles/Testing.md Co-authored-by: Thomas Grapperon <35562418+tgrapperon@users.noreply.github.com> * Update Sources/ComposableArchitecture/Documentation.docc/Articles/Testing.md Co-authored-by: Thomas Grapperon <35562418+tgrapperon@users.noreply.github.com> * Update Sources/ComposableArchitecture/Documentation.docc/Articles/Testing.md Co-authored-by: Thomas Grapperon <35562418+tgrapperon@users.noreply.github.com> * Update Sources/ComposableArchitecture/Documentation.docc/Articles/Testing.md Co-authored-by: Thomas Grapperon <35562418+tgrapperon@users.noreply.github.com> * Update Sources/ComposableArchitecture/Documentation.docc/Articles/Testing.md Co-authored-by: Thomas Grapperon <35562418+tgrapperon@users.noreply.github.com> * Update Sources/ComposableArchitecture/Documentation.docc/Articles/Testing.md Co-authored-by: Thomas Grapperon <35562418+tgrapperon@users.noreply.github.com> * wip * wip * wip Co-authored-by: Stephen Celis <stephen@stephencelis.com> Co-authored-by: Thomas Grapperon <35562418+tgrapperon@users.noreply.github.com>
122 lines
3.6 KiB
Swift
122 lines
3.6 KiB
Swift
import OrderedCollections
|
|
import SwiftUI
|
|
|
|
/// A Composable Architecture-friendly wrapper around `ForEach` that simplifies working with
|
|
/// collections of state.
|
|
///
|
|
/// ``ForEachStore`` loops over a store's collection with a store scoped to the domain of each
|
|
/// element. This allows you to extract and modularize an element's view and avoid concerns around
|
|
/// collection index math and parent-child store communication.
|
|
///
|
|
/// For example, a todos app may define the domain and logic associated with an individual todo:
|
|
///
|
|
/// ```swift
|
|
/// struct TodoState: Equatable, Identifiable {
|
|
/// let id: UUID
|
|
/// var description = ""
|
|
/// var isComplete = false
|
|
/// }
|
|
/// enum TodoAction {
|
|
/// case isCompleteToggled(Bool)
|
|
/// case descriptionChanged(String)
|
|
/// }
|
|
/// struct TodoEnvironment {}
|
|
/// let todoReducer = Reducer<TodoState, TodoAction, TodoEnvironment { ... }
|
|
/// ```
|
|
///
|
|
/// As well as a view with a domain-specific store:
|
|
///
|
|
/// ```swift
|
|
/// struct TodoView: View {
|
|
/// let store: Store<TodoState, TodoAction>
|
|
/// var body: some View { ... }
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// For a parent domain to work with a collection of todos, it can hold onto this collection in
|
|
/// state:
|
|
///
|
|
/// ```swift
|
|
/// struct AppState: Equatable {
|
|
/// var todos: IdentifiedArrayOf<TodoState> = []
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// Define a case to handle actions sent to the child domain:
|
|
///
|
|
/// ```swift
|
|
/// enum AppAction {
|
|
/// case todo(id: TodoState.ID, action: TodoAction)
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// Enhance its reducer using ``Reducer/forEach(state:action:environment:file:fileID:line:)-n7qj``:
|
|
///
|
|
/// ```swift
|
|
/// let appReducer = todoReducer.forEach(
|
|
/// state: \.todos,
|
|
/// action: /AppAction.todo(id:action:),
|
|
/// environment: { _ in TodoEnvironment() }
|
|
/// )
|
|
/// ```
|
|
///
|
|
/// And finally render a list of `TodoView`s using ``ForEachStore``:
|
|
///
|
|
/// ```swift
|
|
/// ForEachStore(
|
|
/// self.store.scope(state: \.todos, AppAction.todo(id:action:))
|
|
/// ) { todoStore in
|
|
/// TodoView(store: todoStore)
|
|
/// }
|
|
/// ```
|
|
///
|
|
public struct ForEachStore<
|
|
EachState, EachAction, Data: Collection, ID: Hashable, Content: View
|
|
>: DynamicViewContent {
|
|
public let data: Data
|
|
let content: () -> Content
|
|
|
|
/// Initializes a structure that computes views on demand from a store on a collection of data and
|
|
/// an identified action.
|
|
///
|
|
/// - Parameters:
|
|
/// - store: A store on an identified array of data and an identified action.
|
|
/// - content: A function that can generate content given a store of an element.
|
|
public init<EachContent>(
|
|
_ store: Store<IdentifiedArray<ID, EachState>, (ID, EachAction)>,
|
|
@ViewBuilder content: @escaping (Store<EachState, EachAction>) -> EachContent
|
|
)
|
|
where
|
|
Data == IdentifiedArray<ID, EachState>,
|
|
Content == WithViewStore<
|
|
OrderedSet<ID>, (ID, EachAction), ForEach<OrderedSet<ID>, ID, EachContent>
|
|
>
|
|
{
|
|
self.data = store.state.value
|
|
self.content = {
|
|
WithViewStore(store.scope(state: { $0.ids })) { viewStore in
|
|
ForEach(viewStore.state, id: \.self) { id -> EachContent in
|
|
// NB: We cache elements here to avoid a potential crash where SwiftUI may re-evaluate
|
|
// views for elements no longer in the collection.
|
|
//
|
|
// Feedback filed: https://gist.github.com/stephencelis/cdf85ae8dab437adc998fb0204ed9a6b
|
|
var element = store.state.value[id: id]!
|
|
return content(
|
|
store.scope(
|
|
state: {
|
|
element = $0[id: id] ?? element
|
|
return element
|
|
},
|
|
action: { (id, $0) }
|
|
)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public var body: some View {
|
|
self.content()
|
|
}
|
|
}
|