Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save appfrosch/2ab3aba7d1fc6ad1071159962627ee5c to your computer and use it in GitHub Desktop.
Save appfrosch/2ab3aba7d1fc6ad1071159962627ee5c to your computer and use it in GitHub Desktop.
TCA poc with Child-to-Parent (working) and Parent-to-Child (open question) communication
import SwiftUI
@main
struct poc_tca_Child_Parent_Child_CommunicationApp: App {
var body: some Scene {
WindowGroup {
ParentView(
store: Store(
initialState: ParentFeature.State(),
reducer: { ParentFeature() }
)
)
}
}
}
import ComposableArchitecture
@Reducer
struct ParentFeature {
@ObservableState
struct State: Equatable {
@Presents var child: ChildCase.State?
}
enum Action: Equatable {
case child(PresentationAction<ChildCase.Action>)
case child1ButtonTapped
case child2ButtonTapped
case resetButtonTapped
case answerFromParent(String)
}
@Reducer(state: .equatable, action: .equatable)
enum ChildCase {
case child1(Child1Feature)
case child2(Child2Feature)
//Unfortunately, I was not able to hide away the setting of the value logic
//to `ChildCase`, as proposed in the slack chat.
// var value: String {
// get {
// switch self {
// case let .child1(state):
// state.value
// case let .child2(state):
// state.value
// }
// }
// set {
// switch self {
// case let .child1(state):
// state.value = newValue
// case let .child2(state):
// state.value = newValue
// }
// }
// }
// }
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case let .child(.presented(.child1(.delegate(.setValue(sentValue))))):
return Self.processValue(sentValue)
case let .child(.presented(.child2(.delegate(.setValue(sentValue))))):
return Self.processValue(sentValue)
case .child:
return .none
case .child1ButtonTapped:
state.child = .child1(Child1Feature.State(value: "Init State Child 1"))
return .none
case .child2ButtonTapped:
state.child = .child2(Child2Feature.State(value: "Init State Child 2"))
return .none
case .resetButtonTapped:
state.child = nil
return .none
case let .answerFromParent(sentValue):
print(sentValue)
//How can I scope down to the child here **to set the value from the parent**?
//https://pointfreecommunity.slack.com/archives/C04KQQ7NXHV/p1717596949744659
//**Like this:**
// While the following works …
// if case .child1 = state.child {
// state.child?.child1?.value = sentValue
// }
// if case .child2 = state.child {
// state.child?.child2?.value = sentValue
// }
// … it is better to work with a `switch` here to let the compiler help
//you on all possible states!
switch state.child {
case .none:
return .none
case .some(.child1):
state.child?.child1?.value = sentValue
case .some(.child2):
state.child?.child2?.value = sentValue
}
return .none
}
}
.ifLet(\.$child, action: \.child)
}
}
extension ParentFeature {
static func processValue(_ sentValue: String) -> Effect<Action> {
let value = sentValue + " " + "(processed by parent)"
return .run { send in
await send(.answerFromParent(value))
}
}
}
struct ParentView: View {
let store: StoreOf<ParentFeature>
var body: some View {
VStack {
if let store = self.store.scope(state: \.child?.child1, action: \.child.child1.presented) {
Child1View(store: store)
} else if let store = self.store.scope(
state: \.child?.child2,
action: \.child.child2.presented
) {
Child2View(store: store)
} else {
VStack {
Spacer()
Text("No Child selected …")
Spacer()
}
}
}
HStack {
Button("Child 1") {
store.send(.child1ButtonTapped)
}
Spacer()
Button("Reset") {
store.send(.resetButtonTapped)
}
Spacer()
Button("Child 2") {
store.send(.child2ButtonTapped)
}
}
.padding(.horizontal)
}
}
#Preview("Parent") {
ParentView(
store: Store(
initialState: ParentFeature.State(),
reducer: { ParentFeature() }
)
)
}
@Reducer
struct Child1Feature {
@ObservableState
struct State: Equatable {
var value: String
}
enum Action: Equatable {
case loadValueButtonTapped
case delegate(Delegate)
enum Delegate: Equatable {
case setValue(String)
}
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .delegate:
return .none
case .loadValueButtonTapped:
return .none
}
}
}
}
struct Child1View: View {
let store: StoreOf<Child1Feature>
var body: some View {
VStack {
Spacer()
Text("Child 1")
.font(.title)
Text(store.value)
Button("Send string to parent …") {
store.send(.delegate(.setValue("Greetings from child 1")))
}
Spacer()
}
}
}
@Reducer
struct Child2Feature {
@ObservableState
struct State: Equatable {
var value: String
}
enum Action: Equatable {
case loadValueButtonTapped
case delegate(Delegate)
enum Delegate: Equatable {
case setValue(String)
}
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .delegate:
return .none
case .loadValueButtonTapped:
return .none
}
}
}
}
struct Child2View: View {
let store: StoreOf<Child2Feature>
var body: some View {
VStack {
Spacer()
Text("Child 2")
.font(.title)
Text(store.value)
Button("Send string to parent …") {
store.send(.delegate(.setValue("Greetings from child 2")))
}
Spacer()
}
}
}
@appfrosch
Copy link
Author

appfrosch commented Jun 5, 2024

Requirements:

  1. tapping on “Child 1” button shows Child1
  2. tapping on the “Send string to parent …” button sends a string to the parent
  3. the parent processes this string
  4. the parent sends the processed string back

Asked question on how to communicate back to the child in the TCA slack.

Simulator.Screen.Recording.-.iPhone.15.Plus.-.2024-06-05.at.17.28.44.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment