Push multiple views on Stack #2465
-
Hi everyone, I'm trying to make a simple app that discovers some BTLE devices (mock for now). I used the Path paradigm along with a struct DiscoverFeature: Reducer {
@Dependency(\.continuousClock) var clock
struct State: Equatable {
var discovering = false
var devices: Set<Device> = []
@PresentationState var detailDevice: Destination.State?
}
enum Action: Equatable {
case discoverButtonTapped
case discoveredDevice(_: Device)
case deviceButtonTapped(_: Device)
case destination(PresentationAction<Destination.Action>)
}
struct Destination: Reducer {
enum State: Equatable {
case detail(DevicesFeature.State)
}
enum Action: Equatable {
case detail(DevicesFeature.Action)
}
var body: some ReducerOf<Self> {
Scope(state: /State.detail,
action: /Action.detail) {
DevicesFeature()
}
}
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .discoverButtonTapped:
state.discovering.toggle()
return .run { send in
try await clock.sleep(for: .seconds(1))
await send(.discoveredDevice(.device1))
try await clock.sleep(for: .seconds(3))
await send(.discoveredDevice(.device2))
try await clock.sleep(for: .seconds(1.5))
await send(.discoveredDevice(.device3))
}
case let .discoveredDevice(profile):
state.devices.insert(profile)
return .none
case let .deviceButtonTapped(device):
state.detailDevice = .detail(DevicesFeature.State(device: device))
return .none
case .destination:
return .none
}
}
}
}
struct DiscoverView: View {
let store: StoreOf<DiscoverFeature>
var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
NavigationStack {
ZStack {
LinearGradient(colors: [Palette.color.accent, Palette.color.secondary], startPoint: .top, endPoint: .bottom)
.ignoresSafeArea()
VStack(alignment: .center) {
if viewStore.discovering {
Group {
ProgressView()
.tint(.white)
List {
ForEach(Array(viewStore.devices).sorted(by: { $0.name < $1.name }), id: \.id) { device in
Button {
viewStore.send(.deviceButtonTapped(device))
} label: {
HStack {
Image("bluetooth")
.resizable()
.frame(width: 30, height: 30)
.foregroundStyle(.accent)
Text(device.name)
.foregroundStyle(.mainText)
}
}
}
}
.scrollContentBackground(.hidden)
}
} else {
Button(action: {
viewStore.send(.discoverButtonTapped)
}, label: {
Image("bluetooth")
})
.foregroundColor(.white)
Text("Please connect your 4C-Breath")
.font(.title)
.foregroundColor(.white)
.padding(.horizontal, 40)
.multilineTextAlignment(.center)
.transition(.slide)
}
}
}
.navigationTitle(viewStore.discovering ? "Device found" : "")
.navigationDestination(store: self.store.scope(state: \.$detailDevice, action: { .destination($0) }),
state: /DiscoverFeature.Destination.State.detail,
action: DiscoverFeature.Destination.Action.detail,
destination: { store in
DevicesView(store: store)
})
}
.tint(.white)
}
}
} You can see that the
I tried to add and remove the NavigationStack at the top of the DeviceView (question btw : if a parent view have a NavigationStack, does child view have to use it too or not ? Does it behave like a NavigationController ? Sorry, I'm new to SwiftUI) struct DevicesFeature: Reducer {
struct State: Equatable {
@BindingState var device: Device
@PresentationState var destination: DevicesFeature.Destination.State?
}
enum Action: BindableAction, Equatable {
case binding(BindingAction<State>)
case profileTapped(_: Profile)
case newProfileButtonTapped
case destination(PresentationAction<DevicesFeature.Destination.Action>)
}
struct Destination: Reducer {
enum State: Equatable {
case detail(BreathDetailFeature.State)
}
enum Action: Equatable {
case detail(BreathDetailFeature.Action)
}
var body: some ReducerOf<Self> {
Scope(state: /State.detail,
action: /Action.detail) {
BreathDetailFeature()
}
}
}
var body: some ReducerOf<Self> {
BindingReducer()
Reduce { state, action in
switch action {
case .binding:
return .none
case .destination:
return .none
case let .profileTapped(profile):
state.destination = .detail(BreathDetailFeature.State(profile: profile, mode: .update))
return .none
case .newProfileButtonTapped:
return .none
}
}
}
}
struct DevicesView: View {
let store: StoreOf<DevicesFeature>
var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
NavigationStack {
ZStack {
LinearGradient(colors: [Palette.color.accent, Palette.color.secondary], startPoint: .top, endPoint: .bottom)
.ignoresSafeArea()
VStack {
ForEach(viewStore.device.profiles) { profile in
Button {
viewStore.send(.profileTapped(profile))
} label: {
ZStack {
Circle()
.fill(.white)
.frame(width: 150)
Text(profile.name)
.foregroundStyle(.mainText)
}
}
}
Spacer()
Button {
viewStore.send(.newProfileButtonTapped)
} label: {
ZStack {
Circle()
.fill(.white)
.frame(width: 150)
Text("+ New profile")
.foregroundStyle(.mainText)
}
}
}
.padding()
}
.navigationDestination(store: self.store.scope(state: \.$destination, action: { .destination($0) }),
state: /DevicesFeature.Destination.State.detail,
action: DevicesFeature.Destination.Action.detail) { store in
BreathDetailView(store: store)
}
}
.tint(.white)
}
}
} I guess I must do something wrong, but I can't see what 😓 |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
it seems that you just forget to add the |
Beta Was this translation helpful? Give feedback.
Hi @jerometonnelier
it seems that you just forget to add the
Destination
reducer into the body ofDiscoverFeature
reduceryou can update your body of
DiscoverFeature
like this example caseswift-composable-architecture/Examples/CaseStudies/SwiftUICaseStudies/03-Navigation-Multiple-Destinations.swift
Lines 63 to 65 in 667d92f