-
Hello, I'm new to The Composable Architecture(TCA). I need some ideas for implementing the following. I want to implement a scrollable view that consists of multiple forms of row view. (This is simple because it is an example, but in reality it consists of views that look completely different. Our API server returns a response like the following. The top-level struct Model {
let users: [User]
}
protocol User {
var id: Int { get }
var name: String { get }
}
struct NormalUser: User, Equatable {
let id: Int
let name: String
let someNiceProperty: String
}
struct PremiumUser: User, Equatable {
let id: Int
let name: String
let someGreatProperty: String
} The following is written in pure SwiftUI struct ContentView: View {
let users: [User]
var body: some View {
List {
ForEach(users, id: \.id) {
if let normal = $0 as? NormalUser {
NormalUserView(user: normal)
}
if let normal = $0 as? PremiumUser {
PremiumUserView(user: normal)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(users: mock)
}
}
struct NormalUserView: View {
let user: NormalUser
var body: some View {
HStack {
Image(systemName: "person")
Text(user.name)
}
}
}
struct PremiumUserView: View {
let user: PremiumUser
var body: some View {
HStack {
Image(systemName: "person.badge.plus")
Text(user.name)
}
}
} How can I implement the above view using TCA? I've tried something like the following, but I don't know how to construct the state tree. struct TCAContentView: View {
let store: Store<AppState, AppAction>
var body: some View {
WithViewStore(store) { viewStore in
List {
ForEachStore(...) { rowStore in
...
}
}
}
}
}
struct TCAContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(users: mock)
}
}
struct TCANormalUserView: View {
let store: Store<NormalUserState, NormalUserAction>
var body: some View {
WithViewStore(store) { viewStore in
HStack {
Image(systemName: "person")
Text(viewStore.user.name)
}
}
}
}
struct TCAPremiumUserView: View {
let store: Store<PremiumUserState, PremiumUserAction>
var body: some View {
WithViewStore(store) { viewStore in
HStack {
Image(systemName: "person.badge.plus")
Text(viewStore.user.name)
}
}
}
} Any ideas? Thanks. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
I got a solution. Does it make sense? Model struct Model: Equatable {
let users: [User]
}
enum User: Equatable {
case normalUser(NormalUser)
case premiumUser(PremiumUser)
}
struct NormalUser: Equatable {
let id: Int
let name: String
let someNiceProperty: String
}
struct PremiumUser: Equatable {
let id: Int
let name: String
let someGreatProperty: String
} TCA Core struct AppState: Equatable {
var users: [UserState]
}
enum AppAction: Equatable {
case user(id: Int, action: UserAction)
}
struct AppEnvironment {}
let appReducer: Reducer<AppState, AppAction, AppEnvironment> =
userReducer.forEach(
state: \.users,
action: /AppAction.user(id:action:),
environment: { _ in .init() }
)
enum UserState: Equatable, Identifiable {
case normalUser(NormalUserState)
case premiumUser(PremiumUserState)
var id: Int {
switch self {
case let .normalUser(normalUserState):
return normalUserState.user.id
case let .premiumUser(premiumUserState):
return premiumUserState.user.id
}
}
var normalUser: NormalUserState? {
get {
if case let .normalUser(state) = self {
return state
} else {
return nil
}
}
set {
if let newValue = newValue {
self = .normalUser(newValue)
}
}
}
var premiumUser: PremiumUserState? {
get {
if case let .premiumUser(state) = self {
return state
} else {
return nil
}
}
set {
if let newValue = newValue {
self = .premiumUser(newValue)
}
}
}
}
enum UserAction: Equatable {
case normalUser(NormalUserAction)
case premiumUser(PremiumUserAction)
}
struct UserEnvironment {}
let userReducer = Reducer<UserState, UserAction, UserEnvironment>.combine(
normalUserReducer
.optional()
.pullback(
state: \.normalUser,
action: /UserAction.normalUser,
environment: { _ in .init() }
),
premiumUserReducer
.optional()
.pullback(
state: \.premiumUser,
action: /UserAction.premiumUser,
environment: { _ in .init() }
)
)
struct NormalUserState: Equatable {
let user: NormalUser
}
enum NormalUserAction: Equatable {
}
struct NormalUserEnvironment {}
let normalUserReducer = Reducer<NormalUserState, NormalUserAction, NormalUserEnvironment>.empty
struct PremiumUserState: Equatable {
let user: PremiumUser
}
enum PremiumUserAction: Equatable {
}
struct PremiumUserEnvironment {}
let premiumUserReducer = Reducer<PremiumUserState, PremiumUserAction, PremiumUserEnvironment>.empty View struct ContentView: View {
let store: Store<AppState, AppAction>
var body: some View {
WithViewStore(store) { viewStore in
List {
ForEachStore(
store.scope(state: \.users, action: AppAction.user(id:action:))
) { rowStore in
RowView(store: rowStore)
}
}
}
}
}
struct RowView: View {
let store: Store<UserState, UserAction>
var body: some View {
WithViewStore(store) { viewStore in
switch viewStore.state {
case .normalUser:
IfLetStore(
store.scope(
state: \.normalUser,
action: UserAction.normalUser
)
) { store in
NormalUserView(store: store)
}
case .premiumUser:
IfLetStore(
store.scope(
state: \.premiumUser,
action: UserAction.premiumUser
)
) { store in
PremiumUserView(store: store)
}
}
}
}
}
struct NormalUserView: View {
let store: Store<NormalUserState, NormalUserAction>
var body: some View {
WithViewStore(store) { viewStore in
HStack {
Image(systemName: "person")
Text(viewStore.user.name)
}
}
}
}
struct PremiumUserView: View {
let store: Store<PremiumUserState, PremiumUserAction>
var body: some View {
WithViewStore(store) { viewStore in
HStack {
Image(systemName: "person.badge.plus")
Text(viewStore.user.name)
}
}
}
} |
Beta Was this translation helpful? Give feedback.
I got a solution. Does it make sense?
Model
TCA Core