Unable to set accessibilityIdentifier of UISegmentedControl's segments
Asked Answered
T

7

12

I found out, that even though I could set accessibilityLabel of UISegmentedControl's segment (see: How do I set the accesibility label for a particular segment of a UISegmentedControl?), I couldn't set accessibilityIdentifier, which was equally important for my project. I need to target a segment irrespective of its text and accessibilityLabel for automation purposes.

For example, the code:

NSString *name = [NSString stringWithFormat:@"Item %li", (long)idx];
segment.accessibilityIdentifier = name;
NSLog(@"ID: %@", segment.accessibilityIdentifier);

results in:

ID: (null)

No exceptions are thrown.

Does anybody have insight into why accessibilityLabel is implemented, but not accessibilityIdentifier?

Tinkle answered 4/4, 2014 at 21:41 Comment(0)
S
3

I got around this issue by writing a Swift extension for XCUIElement that added a new method tap(at: UInt). This method gets the buttons query of the element and sort the results based on their x position. This allows us to specify which segment of the UISegmentedControl we want to tap rather than relying on the button text.

extension XCUIElement {
    func tap(at index: UInt) {
        guard buttons.count > 0 else { return }
        var segments = (0..<buttons.count).map { buttons.element(boundBy: $0) }
        segments.sort { $0.0.frame.origin.x < $0.1.frame.origin.x }
        segments[Int(index)].tap()
    }
}
Statecraft answered 9/2, 2017 at 20:46 Comment(3)
Best answer IMO. Thanks! :)Intuit
This answer is great, except when the language is RTL, the indexes are reversed. Not sure how to detect RTL when running a UI test, the normal methods seem to fail.Disgust
The answer is great, except that it doesn't answer the original question. Whereas Patrick's one does.Stoffel
R
3

Here is an example of looping through the views to set the accessibilityIdentifier by referencing the segment title. Unfortunately when you set the identifier it doesn't persist. UISegments must be doing some tricky overriding. Still at a loss for how to get this to work.

extension UISegmentedControl {

    /// Sets accessibility for segment buttons
    func setAccessibilityIdentifier(_ accessibilityIdentifier: String, for segmentTitle: String) {

        guard let segment = subviews.first(where: {
            $0.subviews.contains(where: { ($0 as? UILabel)?.text == Optional(segmentTitle) })
        }) else { return }

        segment.accessibilityIdentifier = accessibilityIdentifier
    }

}
Realpolitik answered 16/1, 2019 at 4:53 Comment(0)
E
1

I only had images, without any labels, so I used the code below. I found the indexes didn't correspond to the order on screen, so I keyed off of the initial accessibilityLabel values, which were the names of the images I specified in Interface Builder.

override func viewDidLoad() {
    super.viewDidLoad()

    for segment in segmentedControl.subviews {
        switch segment.accessibilityLabel {
        case .Some("First Image"):
            segment.accessibilityLabel = "Description of first item"
            break

        case .Some("Second Image"):
            segment.accessibilityLabel = "Description of second item"
            break

        default:
            NSLog("Unknown accessibility label: \(segment.accessibilityLabel)")
            break
        }
    }
}
Excruciating answered 14/10, 2015 at 20:14 Comment(0)
C
0

I had tested the following code with Xcode 5.1.1 and iOS Simulator 7.1:

UISegmentedControl *contol = [[UISegmentedControl alloc] initWithItems:
                              @[@"0", @"1"]];
[self.view addSubview:contol];

UIView *segment = [[contol subviews] firstObject];
segment.accessibilityIdentifier = @"Item 0";
NSLog(@"ID: %@", segment.accessibilityIdentifier);

it didn't work for iPhone Retina (3.5-inch) and iPhone Retina (4-inch) i.e. result was:

ID: (null)

but it worked for iPhone Retina (4-inch 64-bit) i.e. result was:

ID: Item 0

Then I've replaced @[@"0", @"1"] with @[@"", @""] in UISegmentedControl initialization and the code worked for all mentioned platforms.

It appears, both accessibilityIdentifier and accessibilityLabel are implemented, but somehow the initial values of UISegmentedControl interfere with accessibilityIdentifiers of its segments.

Coniology answered 21/5, 2014 at 18:23 Comment(2)
So, there's no solution for this?Retrace
It's over 2 years later and this is still busted. Encountered this with Xcode 9.1.Tolentino
N
0

I implemented this workaround and got automation to work (with KIF).

Code is in Swift, works for me with Xcode 6.1.1, iOS 8 SDK

for index in 0..<segmentedControl.numberOfSegments {
    let identifierView = UIView()
    identifierView.accessibilityIdentifier = "Item \(index)"
    (segmentedControl.subviews[index] as UIView).addSubview(identifierView)
}
Nu answered 12/1, 2015 at 7:44 Comment(0)
T
0

I ran into this. Some of the previous answers didn't seem to address the accessibilityIdentifier at all. I did try Jawwad's approach of accessing the segments and adding a new UIView and setting the accessibilityIdentifier on that. However, I'm using EarlGrey for UI Testing and unfortunately, when it tried to tap on that view, it didn't work. However, based on this I did the following variation which DID work.

The trick is to enumerate the segments (as per Jawwad) and then for each, find the UIImageView subview and set its accessibilityID. This works for lookup and interaction.

let ids = ["Identifier1", "Identifier2"]

for index in 0..<segmentedControl.numberOfSegments {
    if let view = p.subviews[index] as? UIView {
        for v in view.subviews {
            // Setting the ID twice would cause EarlGrey tests to fail
            if let iv = v as? UIImageView, iv.accessibilityIdentifier == nil {
                iv.accessibilityIdentifier = ids[index]
                break
            }
        }
    }
}
Trocar answered 16/8, 2018 at 23:54 Comment(0)
B
0

I was able to work around the issue by overriding UIView's NSArray *accessibilityElements property, and adding accessibilityIdentifier to each of the returned UISegment.

- (NSArray *)accessibilityElements {
    NSArray *elements = [super accessibilityElements];
    [elements enumerateObjectsUsingBlock:^(UIView *obj, NSUInteger idx, BOOL *stop) {
        obj.accessibilityIdentifier = self.identifiers[idx].accessibilityIdentifier;
    }];
    return elements;
}
Belovo answered 27/4, 2022 at 10:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.