我们使用 KIF 进行功能测试,它使用元素的可访问性标签来确定将事件发送到何处。我目前正在尝试测试 UISegmentedControl 的行为,但为此我需要为控件的不同段设置不同的可访问性标签。如何为特定细分设置可访问性标签?
13 回答
正如顶点所说,
对象-c
[[[self.segmentOutlet subviews] objectAtIndex:3] setAccessibilityLabel:@"GENERAL_SEGMENT"];
迅速
self.segmentOutlet.subviews[3].accessibilityLabel = "GENERAL_SEGMENT"
一些建议,这样你就不会像我一样发疯:
- 要在辅助功能模式中滚动,请滑动三个手指
- 段的索引比您预期的要向后,即最右边的段是第 0 个索引,最左边的是第 n 个索引,其中 n 是 UISegmentControl 中的元素数
我自己刚刚开始使用 KIF,所以我还没有测试过这个,但它可能值得一试。我相信我很快就会遇到同样的问题,所以我很想知道它是否有效。
首先,UIAccessibility Protocol Reference在accessibilityLabel 下有一条说明:
“如果您提供 UIImage 对象以在 UISegmentedControl 中显示,则可以在每个图像上设置此属性以确保可以正确访问这些段。”
所以,我想知道您是否也可以在每个 NSString 对象上设置accessibilityLabel,并能够使用它来访问带有KIF 的每个段。首先,您可以尝试创建几个字符串,设置它们的可访问性标签,并使用 [[UISegmentedControl alloc] initWithItems:myStringArray]; 填充它。
请向我们更新您的进度。我想听听这是怎么回事
的每个段UISegmentedControl
都是UISegment
类实例,它是 的子类UIImageView
。subviews
您可以通过属性访问这些实例,UISegmentedControl
并尝试以编程方式为它们添加可访问性。
您不能依赖 subviewsarray 中的索引来获取位置。对于单个子视图的自定义,我在设置任何属性之前对子视图的 X 位置进行排序。对于 accesibilityLbel 也有效。
let sortedViews = self.subviews.sorted( by: { $0.frame.origin.x < $1.frame.origin.x } )
sortedViews[0].accessibilityLabel = "segment_full"
sortedViews[1].accessibilityLabel = "segment_not_full"
这是一个老问题,但以防万一其他人遇到这个问题,我发现这些段自动有一个可访问性标签指定为其文本。因此,如果添加了选项 1 和选项 2 的两个选项。调用
[tester tapViewWithAccessibilityLabel:@"Option 2"];
成功选择了细分。
使用索引子视图的解决方案不起作用,因为您不能依赖正确的顺序,并且很难更改段数。并且按原点排序不起作用,因为框架(至少对于当前版本)似乎总是在 x: 0。
我的解决方案:
(segmentedControl.accessibilityElement(at: 0) as? UIView)?.accessibilityLabel = "Custom VoiceOver Label 1"
(segmentedControl.accessibilityElement(at: 1) as? UIView)?.accessibilityLabel = "Custom VoiceOver Label 2"
(segmentedControl.accessibilityElement(at: 2) as? UIView)?.accessibilityLabel = "Custom VoiceOver Label 3"
似乎对我有用并且有正确的顺序。你也不依赖图像。也不是那么漂亮,但可能比其他解决方案更可靠。
这是一个老问题,但以防万一其他人遇到这个问题,我发现这些段自动有一个可访问性标签指定为其文本。
除了 Stuart 的回答,我发现在编写测试用例以在模拟器上打开“辅助功能检查器”(设置 - > 常规 - > 辅助功能 - > 辅助功能检查器)时非常有用。您会惊讶地发现有多少元素已经包含了可访问性标签,例如标准 iOS UI 元素甚至第三方框架。
注意:手势现在会有所不同 - 点击查看辅助功能信息,双击选择。最小化辅助功能检查器窗口(通过点击 X 按钮)将使手势恢复正常。
你们想看看Apple建议如何完成吗?
太丑了。
这是来自这个例子:
func configureCustomSegmentsSegmentedControl() {
let imageToAccessibilityLabelMappings = [
"checkmark_icon": NSLocalizedString("Done", comment: ""),
"search_icon": NSLocalizedString("Search", comment: ""),
"tools_icon": NSLocalizedString("Settings", comment: "")
]
// Guarantee that the segments show up in the same order.
var sortedSegmentImageNames = Array(imageToAccessibilityLabelMappings.keys)
sortedSegmentImageNames.sort { lhs, rhs in
return lhs.localizedStandardCompare(rhs) == ComparisonResult.orderedAscending
}
for (idx, segmentImageName) in sortedSegmentImageNames.enumerated() {
let image = UIImage(named: segmentImageName)!
image.accessibilityLabel = imageToAccessibilityLabelMappings[segmentImageName]
customSegmentsSegmentedControl.setImage(image, forSegmentAt: idx)
}
customSegmentsSegmentedControl.selectedSegmentIndex = 0
customSegmentsSegmentedControl.addTarget(self,
action: #selector(SegmentedControlViewController.selectedSegmentDidChange(_:)),
for: .valueChanged)
}
他们将可访问性标签应用于图像,然后附加图像。和上面的答案差别不大。
如果不愿意设置可访问性标签,另一种选择可能是计算每个段部分的位置并使用
[tester tapScreenAtPoint:segementPosition];
触发动作
如果您通过可访问性检查器查看分段控件,您会发现这些段是 UISegment 对象。此外,它们是 UISegmentedControl 的直接子视图。这一事实表明以下疯狂但非常安全的 Swift 4 代码可以设置 UISegmentedControl 段的可访问性标签:
let seg = // the UISegmentedControl
if let segclass = NSClassFromString("UISegment") {
let segments = seg.subviews.filter {type(of:$0) == segclass}
.sorted {$0.frame.minX < $1.frame.minX}
let labels = ["Previous article", "Next article"] // or whatever
for pair in zip(segments,labels) {
pair.0.accessibilityLabel = pair.1
}
}
如已接受的答案中所述,添加accessibilityLabel
到文本中应该可以解决问题:
let title0 = "Button1" as NSString
title0.accessibilityLabel = "MyButtonIdentifier1"
segmentedControl.setTitle("\(title0)", forSegmentAt: 0)
let title1 ="Button2" as NSString
title1.accessibilityLabel = "MyButtonIdentifier2"
segmentedControl.setTitle("\(title1)", forSegmentAt: 1)
XCode 12 / iOS 14.3 / 斯威夫特 5
这是一篇旧文章,但我在尝试为 UISegmentedControl 中的各个段设置可访问性提示时遇到了同样的问题。我对一些较旧的解决方案也有问题。目前为我的应用程序工作的代码借鉴了来自 matt 和 Ilker Baltaci 的回复,然后使用 UIView.description 混合到我自己的 hack 中。
首先,一些评论:
- 对于我的具有 3 个段的 UISegmentedControl,父 UIVIewController 的 viewDidLoad() 和 viewWillAppear() 中的子视图计数为 3。但 viewDidAppear() 中的子视图计数为 7。
- 在 viewDidLoad() 或 viewWillAppear() 中未设置子视图框架,因此对子视图进行排序对我不起作用。显然 Benjamin B 在帧起源方面遇到了同样的问题。
- 在 viewDidAppear() 中,7 个子视图包括 4 个UIImageView类型的视图和 3 个UISegment类型的视图。
- UISegment 是私有类型。直接使用私有 API可能会将您的应用标记为被拒绝。(见下面的评论)
- type(of:) 没有产生任何对 UISegment 子视图有用的东西
- (HACK!)UIView.description可用于在不访问私有 API 的情况下检查类型。
- 根据 X 顺序设置可访问性提示将 UI 段标题和提示与其当前位置紧密结合。如果用户测试建议更改段顺序,则必须在 UI 和代码中进行更改以设置可访问性提示。很容易错过这一点。
使用枚举设置段标题是依赖于在 UI 中手动设置的 X 排序的替代方法。如果您的枚举继承自 String 并采用 CaseIterable 和 RawRepresentable 协议,则可以直接从枚举案例创建标题,并根据段标题确定枚举案例。
鉴于对description.contains("UISegment")的依赖,不能保证以下内容将在框架的未来版本中工作,但它对我有用。得继续前进。
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// get only the UISegment items; ignore UIImageView
let segments = mySegmentedControl.subviews.compactMap(
{ $0.description.contains("UISegment") ? $0 : nil }
)
let sortedSegments = segments.sorted(
by: { $0.frame.origin.x < $1.frame.origin.x }
)
for i in 0 ..< sortedSegments.count {
let segment = sortedSegments[i]
// set .accessibilityHint or .accessibilityLabel by index
// or check for a segment title matching an enum case
// ...
}
}
关于私有 API 和拒绝
我指的是 2016 年 4 月 @dan 在Test if object is an instance of class UISegment 中的评论:
是私人课。您可以使用 [... isKindOfClass:NSClassFromString(@"UISegment")] 进行检查,但这可能会导致您的应用程序因使用私有 api 而被拒绝,或者如果苹果更改内部类名称或结构,将来可能会停止工作。
另外: 私有 API 到底是什么,如果使用了一个 iOS 应用程序,为什么 Apple 会拒绝它?
“应用因非公开 api 而被拒绝”:https ://discussions.apple.com/thread/3838251
正如 Vortex 所说,数组从右到左,[0] 从右边开始。您可以通过访问子视图来设置每个可访问性选项。由于子视图是可选的,因此最好先拉出子视图,然后分配您想要的可访问性特征。一个简单的两个选项段控制的 Swift 4 示例:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard let rightSegment = segmentControl.subviews.first, let leftSegment = segmentControl.subviews.last else { return }
rightSegment.accessibilityLabel = "A label for the right segment."
rightSegment.accessibilityHint = "A hint for the right segment."
leftSegment.accessibilityLabel = "A label for the left segment."
leftSegment.accessibilityHint = "A hint for the left segment."
}