Replies: 1 comment
-
Unfortunately this is just a limitation of Swift's type checker. If you chain on enough things in a SwiftUI view it will eventually break the compiler. It is possible to recreate in vanilla SwiftUI. Often you can use a Click to seeimport ComposableArchitecture
import SwiftUI
struct StandupsList: Reducer {
struct State: Equatable {
@PresentationState var destination: Destination.State?
var standups: IdentifiedArrayOf<Standup> = []
init(
destination: Destination.State? = nil
) {
self.destination = destination
do {
@Dependency(\.dataManager.load) var load
self.standups = try JSONDecoder().decode(IdentifiedArray.self, from: load(.standups))
} catch is DecodingError {
self.destination = .alert(.dataFailedToLoad)
} catch {}
}
}
enum Action: Equatable {
case addStandupButtonTapped
case confirmAddStandupButtonTapped
case destination(PresentationAction<Destination.Action>)
case dismissAddStandupButtonTapped
case oneStandupButtonTapped
case twoStandupButtonTapped
case threeStandupButtonTapped
case fourStandupButtonTapped
case fiveStandupButtonTapped
}
struct Destination: Reducer {
enum State: Equatable {
case add(StandupForm.State)
case one(StandupForm.State)
case two(StandupForm.State)
case three(StandupForm.State)
case four(StandupForm.State)
case five(StandupForm.State)
case alert(AlertState<Action.Alert>)
}
enum Action: Equatable {
case add(StandupForm.Action)
case one(StandupForm.Action)
case two(StandupForm.Action)
case three(StandupForm.Action)
case four(StandupForm.Action)
case five(StandupForm.Action)
case alert(Alert)
enum Alert {
case confirmLoadMockData
}
}
var body: some Reducer<State, Action> {
Scope(state: /State.add, action: /Action.add) {
StandupForm()
}
}
}
@Dependency(\.continuousClock) var clock
@Dependency(\.uuid) var uuid
var body: some ReducerOf<Self> {
Reduce<State, Action> { state, action in
switch action {
case .addStandupButtonTapped:
state.destination = .add(StandupForm.State(standup: Standup(id: Standup.ID(self.uuid()))))
return .none
case .oneStandupButtonTapped:
state.destination = .one(StandupForm.State(standup: Standup(id: Standup.ID(self.uuid()))))
return .none
case .twoStandupButtonTapped:
state.destination = .two(StandupForm.State(standup: Standup(id: Standup.ID(self.uuid()))))
return .none
case .threeStandupButtonTapped:
state.destination = .three(StandupForm.State(standup: Standup(id: Standup.ID(self.uuid()))))
return .none
case .fourStandupButtonTapped:
state.destination = .four(StandupForm.State(standup: Standup(id: Standup.ID(self.uuid()))))
return .none
case .fiveStandupButtonTapped:
state.destination = .five(StandupForm.State(standup: Standup(id: Standup.ID(self.uuid()))))
return .none
case .confirmAddStandupButtonTapped:
guard case let .some(.add(editState)) = state.destination
else { return .none }
var standup = editState.standup
standup.attendees.removeAll { attendee in
attendee.name.allSatisfy(\.isWhitespace)
}
if standup.attendees.isEmpty {
standup.attendees.append(
editState.standup.attendees.first
?? Attendee(id: Attendee.ID(self.uuid()))
)
}
state.standups.append(standup)
state.destination = nil
return .none
case .destination(.presented(.alert(.confirmLoadMockData))):
state.standups = [
.mock,
.designMock,
.engineeringMock,
]
return .none
case .destination:
return .none
case .dismissAddStandupButtonTapped:
state.destination = nil
return .none
}
}
.ifLet(\.$destination, action: /Action.destination) {
Destination()
}
}
}
struct StandupsListView: View {
let store: StoreOf<StandupsList>
@ObservedObject var viewStore: ViewStore<IdentifiedArrayOf<Standup>, StandupsList.Action>
init(store: StoreOf<StandupsList>) {
self.store = store
self.viewStore = ViewStore(self.store, observe: \.standups)
}
var core: some View {
List {
ForEach(viewStore.state) { standup in
NavigationLink(
state: AppFeature.Path.State.detail(StandupDetail.State(standup: standup))
) {
CardView(standup: standup)
}
.listRowBackground(standup.theme.mainColor)
}
}
.toolbar {
ToolbarItem {
Menu {
Button {
viewStore.send(.addStandupButtonTapped)
} label: {
Image(systemName: "plus")
}
Button {
viewStore.send(.oneStandupButtonTapped)
} label: {
Image(systemName: "1.square")
}
Button {
viewStore.send(.twoStandupButtonTapped)
} label: {
Image(systemName: "2.square")
}
Button {
viewStore.send(.threeStandupButtonTapped)
} label: {
Image(systemName: "3.square")
}
Button {
viewStore.send(.fourStandupButtonTapped)
} label: {
Image(systemName: "4.square")
}
Button {
viewStore.send(.fiveStandupButtonTapped)
} label: {
Image(systemName: "5.square")
}
} label: {
Image(systemName: "line.horizontal.3")
}
}
}
.navigationTitle("Daily Standups")
}
var body: some View {
self.core
.alert(
store: self.store.scope(state: \.$destination, action: StandupsList.Action.destination),
state: /StandupsList.Destination.State.alert,
action: StandupsList.Destination.Action.alert
)
.sheet(
store: self.store.scope(state: \.$destination, action: StandupsList.Action.destination),
state: /StandupsList.Destination.State.add,
action: StandupsList.Destination.Action.add
) { store in
NavigationStack {
StandupFormView(store: store)
.navigationTitle("New standup")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Dismiss") {
viewStore.send(.dismissAddStandupButtonTapped)
}
}
ToolbarItem(placement: .confirmationAction) {
Button("Add") {
viewStore.send(.confirmAddStandupButtonTapped)
}
}
}
}
}
.sheet(
store: self.store.scope(state: \.$destination, action: StandupsList.Action.destination),
state: /StandupsList.Destination.State.one,
action: StandupsList.Destination.Action.one
) { _ in
Text("1")
}
.sheet(
store: self.store.scope(state: { $0.$destination }, action: { StandupsList.Action.destination($0) }),
state: /StandupsList.Destination.State.two,
action: StandupsList.Destination.Action.two
) { _ in
Text("2")
}
.sheet(
store: self.store.scope(state: \.$destination, action: StandupsList.Action.destination),
state: /StandupsList.Destination.State.three,
action: StandupsList.Destination.Action.three
) { _ in
Text("3")
}
// .sheet(
// store: self.store.scope(state: \.$destination, action: StandupsList.Action.destination),
// state: /StandupsList.Destination.State.four,
// action: StandupsList.Destination.Action.four
// ) { _ in
// Text("4")
// }
// .sheet(
// store: self.store.scope(state: \.$destination, action: StandupsList.Action.destination),
// state: /StandupsList.Destination.State.five,
// action: StandupsList.Destination.Action.five
// ) { _ in
// Text("5")
// }
}
}
extension AlertState where Action == StandupsList.Destination.Action.Alert {
static let dataFailedToLoad = Self {
TextState("Data failed to load")
} actions: {
ButtonState(action: .send(.confirmLoadMockData, animation: .default)) {
TextState("Yes")
}
ButtonState(role: .cancel) {
TextState("No")
}
} message: {
TextState(
"""
Unfortunately your past data failed to load. Would you like to load some mock data to play \
around with?
"""
)
}
}
struct CardView: View {
let standup: Standup
var body: some View {
VStack(alignment: .leading) {
Text(self.standup.title)
.font(.headline)
Spacer()
HStack {
Label("\(self.standup.attendees.count)", systemImage: "person.3")
Spacer()
Label(self.standup.duration.formatted(.units()), systemImage: "clock")
.labelStyle(.trailingIcon)
}
.font(.caption)
}
.padding()
.foregroundColor(self.standup.theme.accentColor)
}
}
struct TrailingIconLabelStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
HStack {
configuration.title
configuration.icon
}
}
}
extension LabelStyle where Self == TrailingIconLabelStyle {
static var trailingIcon: Self { Self() }
}
struct StandupsList_Previews: PreviewProvider {
static var previews: some View {
StandupsListView(
store: Store(initialState: StandupsList.State()) {
StandupsList()
} withDependencies: {
$0.dataManager.load = { _ in
try JSONEncoder().encode([
Standup.mock,
.designMock,
.engineeringMock,
])
}
}
)
.preferredColorScheme(.dark)
StandupsListView(
store: Store(initialState: StandupsList.State()) {
StandupsList()
} withDependencies: {
$0.dataManager = .mock(initialData: Data("!@#$% bad data ^&*()".utf8))
}
)
.previewDisplayName("Load data failure")
.preferredColorScheme(.dark)
}
} Since this is not an issue with the library I am going to convert it to a discussion. |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Description
Hi,
I started to convert an app to the prerelease/1.0 branch using stack-based and tree-based navigation. Some features use multiple destinations, e.g. one alert and four fullScreenCover. Unfortunately Xcode gives me the 'The compiler is unable to type-check this expression in reasonable time' error on the view's body. I can get rid of the error by commenting a few fullScreenCover modifiers, but Xcode gives no hint on the underlying problem. I'm using Xcode 14.3.1 and the latest TCA prerelease/1.0 commit/a4d371e
The problem seems to be not related to my app because I modified PointFree's Standups example slightly (added dummy destinations, added a menu to the toolbar...) and the error showed up.
Checklist
main
branch of this package.Expected behavior
The compiler should be able to resolve any number of .sheet view modifier.
Actual behavior
The compiler cannot resolve more than about three of these view modifiers.
Steps to reproduce
This is the modified file StandupsList from PointFree's Standups example.
The Composable Architecture version information
a4d371e
Destination operating system
No response
Xcode version information
Version 14.3.1 (14E300c)
Swift Compiler version information
Beta Was this translation helpful? Give feedback.
All reactions