Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UIViewController.present doesn't set state to nil when dismissed #3451

Open
3 tasks done
myihsan opened this issue Oct 18, 2024 · 3 comments
Open
3 tasks done

UIViewController.present doesn't set state to nil when dismissed #3451

myihsan opened this issue Oct 18, 2024 · 3 comments
Labels
bug Something isn't working due to a bug in the library.

Comments

@myihsan
Copy link

myihsan commented Oct 18, 2024

Description

if the child state is Identifiable, the dismiss action is not be sent when dismissed.
I think #3309 introduced this issue.

Checklist

  • I have determined whether this bug is also reproducible in a vanilla SwiftUI project.
  • If possible, I've reproduced the issue using the main branch of this package.
  • This issue hasn't been addressed in an existing GitHub issue or discussion.

Expected behavior

Send dismiss action when dismissed whatever the child state is Identifiable.

Actual behavior

set {
if newValue == nil,
let childState = self.state[keyPath: state],
id == _identifiableID(childState),

The id that determines whether or not to send the dismiss action is set by the following.
public func scope<State: ObservableState, Action, ChildState, ChildAction>(
state: KeyPath<State, ChildState?>,
action: CaseKeyPath<Action, PresentationAction<ChildAction>>,
fileID: StaticString = #fileID,
filePath: StaticString = #filePath,
line: UInt = #line,
column: UInt = #column
) -> UIBinding<Store<ChildState, ChildAction>?>
where Value == Store<State, Action> {
self[
id: wrappedValue.currentState[keyPath: state].flatMap(_identifiableID),

However, when scoping for present, if the child state is nil which is the most of the time, the id will also be nil, but _identifiableID(childState) will not be. So the dismiss action will not be sent.

This issue doesn't affect SwiftUI implementations because the scoped Binding is created every time when body is called, including the time the child state becomes non-nil.

I can work around this by mimicking SwiftUI's behavior, but it's so weird.

observe { [weak self] in
  guard let self, store.alert != nil else { return }
  var token: ObserveToken!
  token = present(
    item: $store.scope(state: \.alert, action: \.alert),
    onDismiss: { token.cancel() }
  ) { store in
    return UIAlertController(store: store)
  }
}

Reproducing project

TicTacToe

present(item: $store.scope(state: \.alert, action: \.alert)) { store in
UIAlertController(store: store)
}

The alert is an AlertState which is Identifiable.

The Composable Architecture version information

1.14.0

@myihsan myihsan added the bug Something isn't working due to a bug in the library. label Oct 18, 2024
@jshier
Copy link
Contributor

jshier commented Oct 18, 2024

This is the issue I reported in a discussion where the presentation tools didn't work in UIKit when scoping stores. Nice to have a true root cause and a reduction.

@stephencelis
Copy link
Member

Thanks for the repro! We'll look into this soon.

@stephencelis stephencelis changed the title present doesn't set state to nil when dismissed UIViewController.present doesn't set state to nil when dismissed Oct 23, 2024
stephencelis added a commit that referenced this issue Oct 23, 2024
@acosmicflamingo
Copy link
Contributor

All the debugging I've been doing, and it turns out this ticket lays out all my findings >:( I'll have to search the GitHub tickets next time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working due to a bug in the library.
Projects
None yet
Development

No branches or pull requests

4 participants