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

AttributedLabel Accessibility Links #537

Merged
merged 8 commits into from
Jan 28, 2025
Merged

Conversation

RoyalPineapple
Copy link
Collaborator

@RoyalPineapple RoyalPineapple commented Jan 22, 2025

This PR migrates AttributedLabel's link rotor from a stateful to functional pattern, eliminating the need for two variables and preventing voiceover from getting out of sync.

I also fixed a bug in links(at location: CGPoint) that could cause a crash if the AttributedString lacked a specified NSTextAlignment.

@RoyalPineapple RoyalPineapple changed the title [WIP] [DNR] playing with some rotor helpers [WIP] [DNR] AttributedLabel Links Rotor Jan 23, 2025
@@ -389,7 +364,7 @@ extension AttributedLabel {
}

assert(
alignments.count == 1,
Copy link
Collaborator Author

@RoyalPineapple RoyalPineapple Jan 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was causing a crash in the demo app.
Someone went through the trouble of writing the nil case below, it'd be a shame never to use it.

@RoyalPineapple RoyalPineapple changed the title [WIP] [DNR] AttributedLabel Links Rotor AttributedLabel Accessibility Links Jan 23, 2025
@RoyalPineapple RoyalPineapple force-pushed the alex/RotorAdditions branch 2 times, most recently from e5ccc90 to 91e208b Compare January 23, 2025 18:27
private var accessibilityLinkIndex = -1

/// These elements need to be retained by the view, and cannot be created inside the
/// `accessibilityCustomRotors` getter.
Copy link
Collaborator Author

@RoyalPineapple RoyalPineapple Jan 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This concern about the elements being retained is still valid. We cannot pass back unretained elements created within the itemSearchBlock.

The trick here is the elements are now retained in an array which is itself captured by the itemSearchBlock.

Once VoiceOver releases the rotor these elements all get deallocated and then will be recreated the next time VoiceOver requests the rotor.

@RoyalPineapple RoyalPineapple marked this pull request as ready for review January 23, 2025 18:56
@RoyalPineapple RoyalPineapple requested a review from a team as a code owner January 23, 2025 18:56
extension BidirectionalCollection where Element: Equatable, Element: NSObjectProtocol {
private func itemSearch(_ predicate: UIAccessibilityCustomRotorSearchPredicate) -> UIAccessibilityCustomRotorItemResult? {
guard let first else { return nil }
guard let currentItem = predicate.currentItem.targetElement as? Element,
Copy link
Collaborator

@kyleve kyleve Jan 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking outloud, im surprised to see both the generic constraint and this dynamic check; are both required?

Copy link
Collaborator Author

@RoyalPineapple RoyalPineapple Jan 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generic constraint is on the array contents, but the dynamic check is being run on the item that we've been passed in via the predicate, which is only guaranteed to be an NSObjectProtocol not necessarily Equatable.

We could get away with just checking for Equatable, but I thought it would be nice to ensure that all the types match by using Element instead.

Copy link
Contributor

@g-mark g-mark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@@ -0,0 +1,21 @@
import UIKit

extension BidirectionalCollection where Element: Equatable, Element: NSObjectProtocol {
Copy link
Contributor

@g-mark g-mark Jan 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I think you can also combine the constraints

Suggested change
extension BidirectionalCollection where Element: Equatable, Element: NSObjectProtocol {
extension BidirectionalCollection where Element: Equatable & NSObjectProtocol {

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, of course!
I initially wrote Element: Equatable, NSObjectProtocol and this was Xcode's suggested fix.

}

/// Returns a UIAccessibilityCustomRotor with the provided system type which cycles through the contained elements.
public func accessibilityRotor(systemType type: UIAccessibilityCustomRotor.SystemRotorType) -> UIAccessibilityCustomRotor {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Put the public func at the top of the file, and the private extension after?

BlueprintUICommonControls/Sources/AttributedLabel.swift Outdated Show resolved Hide resolved
@RoyalPineapple RoyalPineapple merged commit a70cf54 into main Jan 28, 2025
4 checks passed
@RoyalPineapple RoyalPineapple deleted the alex/RotorAdditions branch January 28, 2025 16:16
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

Successfully merging this pull request may close these issues.

3 participants