Closed olcayertas closed 1 year ago
@olcayertas Is it possible to share a building project that reproduces the problem?
Without that, I think the problem is here:
internal func handleNewUserStore(_ id: String) {
guard let nc = getNavController() else { return }
guard let index = viewStore.userProfileStates.firstIndex (where: { $0.id == id }) else { return }
showUserProfile(
nc,
store: store.scope(
state: \.userProfileStates[index],
action: { .userProfileAction(id: id, action: $0) }
)
)
}
Grabbing an element via the index
is an unsafe operation over time. Is it possible to refactor the above using the safer, [id:]
-based subscript instead?
Hi @stephencelis. Thank you for your response.
Normally I wouldn't use the index but I have seen this approach in your UIKitCaseStudies/ListsOfState.swift
example. I was trying to find the correct way to scope the state and make my child screens and main tab bar communicate correctly.
This is even happening if there is only one child state and there is no other side effects that is running that changes the array.
I have tried your suggestion and updated my code like this:
internal func handleNewUserStore(_ id: String) {
guard let nc = getNavController() else { return }
guard viewStore.userProfileStates[id: id] != nil else { return }
showUserProfile(
nc,
store: store.scope(
state: { $0.userProfileStates[id: id]! },
action: { .userProfileAction(id: id, action: $0) }
)
)
}
And this is also crashing:
I have also tried to get the state before calling showUserProfile
and pass it in the (scope -> state) closure like this:
internal func handleNewUserStore(_ id: String) {
guard let nc = getNavController() else { return }
guard let state = viewStore.userProfileStates[id: id] else { return }
showUserProfile(
nc,
store: store.scope(
state: { [state] _ in state },
action: { .userProfileAction(id: id, action: $0) }
)
)
}
But this time my action to fetch content didn't update the UI.
Can you please show me what is the correct way to have an array of child states and present child view controllers when a new state is added to array. Then remove the added state when the child view controller is dismissed?
With using Index or ID, I am able to show and communicate with my child view controller and clean it's state when user taps to back button using delayed clean action. The last thing remains is the clean all child states when user taps to one of the tab bar items and dismiss all the presented child view controllers.
OK, I think I have find the non-crashing way to do it:
internal func handleNewUserStore(_ id: String) {
guard let nc = getNavController() else { return }
guard viewStore.userProfileStates[id: id] != nil else { return }
showUserProfile(
nc,
store: store.scope(
state: { $0.userProfileStates[id: id] ?? .init(userId: id) },
action: { .userProfileAction(id: id, action: $0) }
)
)
}
internal func showUserProfile(
_ navController: UINavigationController?,
store: UserStore?
) {
guard let parent = viewStore.parentViewController else { return }
guard let store else { return }
let view = UserProfileView(
store: store,
parent: parent,
delegate: self
)
let controller = UIHostingController(rootView: view)
controller.hidesBottomBarWhenPushed = false
navController?.pushViewController(controller, animated: true)
navController?.setNavigationBarHidden(true, animated: true)
}
But I am not sure if this is the best approach.
Thank you very much for directing me to a solution that works for me! Love you guys!
Hi I have following case and state and I am trying to clear a state when user dismiss a view controller:
var userProfileStates: IdentifiedArrayOf<User.State> = .init()
But I am getting this error with all four of the approaches above:
Swift/ContiguousArrayBuffer.swift:600: Fatal error: Index out of range
These are the steps I produce the error:
viewStore .publisher .addedUserState .sink { [weak self] state in self?.handleNewUserStore(state?.id ?? "") } .store(in: &cancellables) ...
internal func handleNewUserStore(_ id: String) { guard let nc = getNavController() else { return } guard let index = viewStore.userProfileStates.firstIndex (where: { $0.id == id }) else { return } showUserProfile( nc, store: store.scope( state: .userProfileStates[index], action: { .userProfileAction(id: id, action: $0) } ) ) }
...
internal func showUserProfile( _ navController: UINavigationController?, store: UserStore ) { guard let parent = viewStore.parentViewController else { return } let view = UserProfileView( store: store, parent: parent, delegate: self ) let controller = UIHostingController(rootView: view) controller.hidesBottomBarWhenPushed = false navController?.pushViewController(controller, animated: true) navController?.setNavigationBarHidden(true, animated: true) }
(lldb) po state.userProfileStates
▿ 1 element ▿ 0 : State
Lib version: 0.5.0
UPDATE: Sending the clear action with a delay partially solved my problem:
This trick solves the problem when user taps to back button and clears out the particular state.
But if I push multiple user screens and tap a different tab bar item, all the sub view controllers are dismissed by tab bar and I should remove all the child states. For this one app still crashes.
Here is the stack trace for the case where user tapped to a tab bar item:
Thread 1 Queue : com.apple.main-thread (serial)
And this is the stack trace for TCA debug tread:
Thread 14 Queue : co.pointfree.ComposableArchitecture.DebugEnvironment (serial)