15
15
//
16
16
17
17
import Accessibility
18
+ import SwiftUI
18
19
import UIKit
19
20
20
21
public struct AccessibilityMarker {
@@ -37,6 +38,10 @@ public struct AccessibilityMarker {
37
38
/// focus.
38
39
public var description : String
39
40
41
+ /// A unique identifier for the element, primarily used in UI tests for locating and interacting with elements.
42
+ /// This identifier is not visible to users.
43
+ public var identifier : String ?
44
+
40
45
/// A hint that will be read by VoiceOver if focus remains on the element after the `description` is read.
41
46
public var hint : String ?
42
47
@@ -209,6 +214,7 @@ public final class AccessibilityHierarchyParser {
209
214
210
215
return AccessibilityMarker (
211
216
description: description,
217
+ identifier: element. object. identifier,
212
218
hint: hint,
213
219
userInputLabels: userInputLabels,
214
220
shape: accessibilityShape ( for: element. object, in: root) ,
@@ -714,7 +720,7 @@ extension UIView {
714
720
fileprivate extension NSObject {
715
721
var customContent : [ ( label: String , value: String , isImportant: Bool ) ] {
716
722
// Github runs tests on specific iOS versions against specific versions of Xcode in CI.
717
- // Forward deployment on old versions of Xcode require a compile time check which require diferentiation by swift version rather than iOS SDK.
723
+ // Forward deployment on old versions of Xcode require a compile time check which require differentiation by swift version rather than iOS SDK.
718
724
// See https://swiftversion.net/ for mapping swift version to Xcode versions.
719
725
720
726
if #available( iOS 14 . 0 , * ) {
@@ -741,6 +747,61 @@ fileprivate extension NSObject {
741
747
}
742
748
return [ ]
743
749
}
750
+
751
+ var identifier : String ? {
752
+ // The `accessibilityIdentifier` property is part of the `UIAccessibilityIdentification` protocol,
753
+ // distinct from other accessibility properties in UIKit.
754
+ if let idProtocol = self as? UIAccessibilityIdentification {
755
+ return idProtocol. accessibilityIdentifier
756
+ }
757
+
758
+ // Swift occasionally fails to recognize Objective-C subclasses conforming to `UIAccessibilityIdentification`.
759
+ // This is likely due to a Swift bug where Objective-C classes lose their protocol conformance
760
+ // when converted to `Any` types for use in accessibility APIs.
761
+ // See https://github.com/swiftlang/swift/issues/46456 for details.
762
+
763
+ // Explicitly check UIKit types that conform to `UIAccessibilityIdentification`:
764
+ if let view = self as? UIView {
765
+ return view. accessibilityIdentifier
766
+ }
767
+ if let barItem = self as? UIBarItem {
768
+ return barItem. accessibilityIdentifier
769
+ }
770
+ if let alertAction = self as? UIAlertAction {
771
+ return alertAction. accessibilityIdentifier
772
+ }
773
+ if let menuElement = self as? UIMenuElement {
774
+ return menuElement. accessibilityIdentifier
775
+ }
776
+ if let image = self as? UIImage {
777
+ return image. accessibilityIdentifier
778
+ }
779
+
780
+ // Use key-value coding as a fallback to access the `accessibilityIdentifier`.
781
+ // This is necessary for SwiftUI views, which are wrapped in a `UIHostingController`
782
+ // and don't directly expose an `accessibilityIdentifier`.
783
+ if let accessibilityIdentifier = value ( forKey: " accessibilityIdentifier " ) as? String {
784
+ return accessibilityIdentifier
785
+ }
786
+
787
+ return nil
788
+ }
789
+ }
790
+
791
+ // MARK: -
792
+
793
+ private extension UIHostingController {
794
+ /// Provides access to the `accessibilityIdentifier` of the hosted SwiftUI view.
795
+ /// This is necessary because SwiftUI views are wrapped in a `UIHostingController`,
796
+ /// and don't directly expose an `accessibilityIdentifier`.
797
+ var accessibilityIdentifier : String ? {
798
+ get {
799
+ return view. accessibilityIdentifier
800
+ }
801
+ set {
802
+ view. accessibilityIdentifier = newValue
803
+ }
804
+ }
744
805
}
745
806
746
807
// MARK: -
0 commit comments