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

Are accessibility labels supported within ConfirmationDialogState? #32

Open
acosmicflamingo opened this issue Nov 22, 2022 · 4 comments

Comments

@acosmicflamingo
Copy link

acosmicflamingo commented Nov 22, 2022

Let's say I want to initialize a ConfirmationDialogState object within a reducer like this:

state.confirmationDialog = .init(
  title: .init("Skip tutorial?"),
    buttons: [
      .destructive(
        .init("Yes, skip"), action: .send(.confirmDeletionButtonTapped, animation: .default)
        ),
      .cancel(.init("No, resume"), action: .send(.dismissDeletionPrompt))
    ]
)

In addition, I want to add accessibility labels (for example, I want to have a confirmation dialog display a timestamp but for VoiceOver users, I want it to not say 'zero colon 54'). I think the way to do this the SwiftUI way would be:

state.confirmationDialog = .init(
  title: .init("Skip tutorial?"),
    buttons: [
      .destructive(
        .init("Yes, skip").accessibilityLabel("HELLO WORLD"), action: .send(.confirmDeletionButtonTapped, animation: .default)
        ),
      .cancel(.init("No, resume").accessibilityLabel("HOWDY"), action: .send(.dismissDeletionPrompt))
    ]
)

Doing this however does not actually pass accessibility labels as I'd expect, and it might have to do with how the convenience initializers and helper functions setup the buttons. But I do see that TCA includes accessibility support in TextState.swift. Perhaps I'm missing how to properly use accessibility labels? Or is there an issue with how ConfirmationDialogState supports accessibility labels? I'd be happy to debug this myself and create a PR, but I want to make sure I'm not missing anything. Thanks!

@stephencelis
Copy link
Member

@acosmicflamingo Have you tried reproducing in vanilla SwiftUI using Text in a confirmation dialog? Many text modifiers, especially those that do styling, do not work in alerts. If vanilla SwiftUI has different behavior, though, definitely sounds like a bug we should track down!

@acosmicflamingo
Copy link
Author

acosmicflamingo commented Nov 29, 2022

Actually, if I use the CaseStudies example and add accessibilityLabel modifier to a button, it works as expected. I think the problem (which in hindsight I should've mentioned in the initial issue description) is actually coming from how UIAlertController configures the buttons using ConfirmationDialogState in the convenience initializer:

/// Creates a `UIAlertController` from `ConfirmationDialogState`.
///
/// - Parameters:
///   - state: The state of dialog that can be shown to the user.
///   - send: A function that wraps a dialog action in the view store's action type.
public convenience init<Action>(
  state: ConfirmationDialogState<Action>, send: @escaping (Action) -> Void
) {
  self.init(
    title: String(state: state.title),
    message: state.message.map { String(state: $0) },
    preferredStyle: .actionSheet
  )
  for button in state.buttons {
    self.addAction(.init(button, action: send))
  }
}

In the addAction function, it's getting a UIAlertAction object instantiated from the following convenience initializer:

extension UIAlertAction {
  convenience init<Action>(
    _ button: AlertState<Action>.Button,
    action: @escaping (Action) -> Void
  ) {
    self.init(
      title: String(state: button.label),
      style: button.role.map(UIAlertAction.Style.init) ?? .default,
      handler: button.action.map { _ in { _ in button.withAction(action) } }
    )
    self.accessibilityLabel = button.label.???  <= theoretical solution but don't know what property to use
  }
}

It looks like that is where the accessibility label information is not translating from SwiftUI to UIKit. The button parameter of type AlertState<Action>.Button may have a property label of type TextState, but calling .accessibilityLabel doesn't return the current accessibility label as it would in UIKit. Instead it is transforming the TextState type to have a specific accessibility label. I don't know if I'm missing something because I'm unfamiliar with SwiftUI, or maybe keeping some properties hidden inTextState is intentional, but the solution to this problem might be a simple one that resides in TextState.

@stephencelis
Copy link
Member

It's definitely possible that TextState should expose methods/properties to compute these kinds of things. Simple examples are simple to theorize:

var label = TextState("Hello!").accessibilityLabel("An enthusiastic greeting!")
label.accessibilityLabel // "An enthusiastic greeting!"

We'd have to figure out how to treat certain defaults, though:

var label = TextState("Hello!")
label.accessibilityLabel
// nil
//     ...or...
// "Hello"?

And composition makes it a little more tricky:

var label = TextState("Hello!").accessibilityLabel("Greetings") + TextState(" And welcome!")
label.accessibilityLabel // What should be returned here?

@acosmicflamingo
Copy link
Author

acosmicflamingo commented Nov 29, 2022

That is tricky indeed. Well, what currently happens in UIKit is that accessibilityLabel is nil by default. Then, UIButton checks whether the property has a value and if it doesn't, then the UIButton's label's text value is instead used.

If we were going to mimic this kind of behavior, then a solution below might get us close to that:

extension UIAlertAction {
  convenience init<Action>(
    _ button: AlertState<Action>.Button,
    action: @escaping (Action) -> Void
  ) {
    self.init(
      title: String(state: button.label),
      style: button.role.map(UIAlertAction.Style.init) ?? .default,
      handler: button.action.map { _ in { _ in button.withAction(action) } }
    )
    self.accessibilityLabel = String(state: (button.accessibilityLabel ?? button.label))
  }
}

For your examples, that would mean I'd expect the following:

var label = TextState("Hello!")
label.accessibilityLabel // should be nil

var label = TextState("Hello!").accessibilityLabel("Greetings") + TextState(" And welcome!")
// oh noooooooooooooooooooooo things break down here :( I guess to be consistent, label.accessibilityLabel
// would be "Greetings"?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants