Skip to content

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

Open
@myihsan

Description

@myihsan

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working due to a bug in the library.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions