4

I have a custom view that has some subviews like labels and text fields. When I use multiple custom views into one controller I want to know the subview Accessibility Identifier. What I want to achieve is that set parent identifier(parent_accessibility_identifier) and than subview identifier can be an extension of it (eg parent_accessibility_identifier_label, parent_accessibility_identifier_text_field). Can we do this by setting the parent identifier accessibly to false and adding labels and text into the child's view but is there any better way to do it? this code doesn't work in the subview class.

public override var accessibilityIdentifier: String? {
    didSet {
        if let accessibilityIdentifier = self.accessibilityIdentifier {
            self.accessibilityIdentifier = accessibilityIdentifier + "_label"
        }
    }
}

this work in the custom view class

public override var accessibilityIdentifier: String? {
    didSet {
        guard let accessibilityIdentifier = accessibilityIdentifier else { return }
        
        textLabel.accessibilityIdentifier = accessibilityIdentifier + "_text_label"
    }
}
Retro
  • 3,985
  • 2
  • 17
  • 41

1 Answers1

1

I would suggest using swizzling for that. It enables you to override the behavior of opened properties and functions of system-level frameworks.

  1. First, put the swizzling code somewhere:

     ///
     extension UIView {
    
         /**
          */
         class func swizzle() {
             let orginalSelectors = [
                 #selector(getter: accessibilityIdentifier),
             ]
             let swizzledSelectors = [
                 #selector(getter: swizzledAccessibilityIdentifier)
             ]
    
             let orginalMethods = orginalSelectors.map { class_getInstanceMethod(UIView.self, $0) }
             let swizzledMethods = swizzledSelectors.map { class_getInstanceMethod(UIView.self, $0) }
    
             orginalSelectors.enumerated().forEach { item in
                 let didAddMethod = class_addMethod(
                     UIView.self,
                     item.element,
                     method_getImplementation(swizzledMethods[item.offset]!),
                     method_getTypeEncoding(swizzledMethods[item.offset]!))
    
                 if didAddMethod {
                     class_replaceMethod(
                         UIView.self,
                         swizzledSelectors[item.offset],
                         method_getImplementation(orginalMethods[item.offset]!),
                         method_getTypeEncoding(orginalMethods[item.offset]!))
                 } else {
                     method_exchangeImplementations(orginalMethods[item.offset]!, swizzledMethods[item.offset]!)
                 }
             }
         }
    
         /// that's where you override `accessibilityIdentifier` getter for 
         @objc var swizzledAccessibilityIdentifier: String {
             if let parentIdentifier = superview?.accessibilityIdentifier {
                 return "\(parentIdentifier)_\(self.swizzledAccessibilityIdentifier)"
             } else {
                 return self.swizzledAccessibilityIdentifier
             }
         }
     }
    
  2. Call UIView.swizzle(). Usually, you do it at the app launch. Somewhere in the AppDelegate or similar.

  3. Setup view hierarchy, assign identifiers and test:

     class ParentView: UIView {}
     class SubclassedChildView: UIView {}
    
     let parentView = ParentView()
     let child1 = SubclassedChildView()
     let child2 = UIView()
    
     parentView.accessibilityIdentifier = "parent"
     child1.accessibilityIdentifier = "child1"
     child2.accessibilityIdentifier = "child2"
    
     parentView.addSubview(child1)
     child1.addSubview(child2)
    
     print(parentView.accessibilityIdentifier) // "parent"
     print(child1.accessibilityIdentifier) // "parent_child1"
     print(child2.accessibilityIdentifier) // "parent_child1_child2"
    
Vadim Popov
  • 1,177
  • 8
  • 17
  • Thank you for the answer, I was under the impression there is something that could be done with public API than swizzling the framework. – Retro Dec 13 '21 at 11:06