-
-
Save ngtk/7d4a92ad506df7f9a36f75f6c63ec835 to your computer and use it in GitHub Desktop.
import UIKit | |
/** | |
`Accessible` provides the way to find the element by a consistent name | |
in UI tests. | |
`generateAccessibilityIdentifiers()` creates the identifier that forms | |
the combination of the class name and the property name that is styled | |
like "ClassName.propertyName" and sets it to `accessibilityIdentifier` | |
to each property that is UIView. | |
e.g. | |
- `WelcomeViewController.view` | |
- `WelcomeViewController.buttonStackView` | |
You can conform `Accessible` to your custom `UIViewController` or `UIView` | |
and call `generateAccessibilityIdentifiers()` after all view properties | |
are initalized like in the last line of `viewDidLoad()` or an initalizer. | |
**/ | |
public protocol Accessible { | |
func generateAccessibilityIdentifiers() | |
} | |
public extension Accessible where Self: NSObject { | |
func generateAccessibilityIdentifiers() { | |
#if DEBUG | |
let mirror = Mirror(reflecting: self) | |
generateAccessibilityIdentifiers(mirror) | |
#endif | |
} | |
private func generateAccessibilityIdentifiers(_ mirror: Mirror) { | |
// If it has no children, we treat the object as objc implementation. | |
if mirror.children.isEmpty { | |
generateAccessibilityIdentifiersForObjc(mirror) | |
} else { | |
for child in mirror.children { | |
generateAccessibilityIdentifier(for: child) | |
} | |
} | |
if let superclassMirror = mirror.superclassMirror { | |
generateAccessibilityIdentifiers(superclassMirror) | |
} | |
} | |
private func generateAccessibilityIdentifier(for child: Mirror.Child) { | |
guard let view = child.value as? UIView else { return } | |
guard let label = child.label else { return } | |
let property = label.replacingOccurrences(of: ".storage", with: "") // lazy vars appends ".storage". | |
view.accessibilityIdentifier = "\(type(of: self)).\(property)" | |
} | |
private func generateAccessibilityIdentifiersForObjc(_ mirror: Mirror) { | |
var outCount: UInt32 = 0 | |
guard let properties = class_copyPropertyList(mirror.subjectType as? AnyClass, &outCount) else { return } | |
// swiftlint:disable:next identifier_name | |
for i: UInt32 in 0 ..< outCount { | |
let property = properties[Int(i)] | |
let ignoreProperties = [ | |
"window", // Weak referenced as the property of a view controller. | |
"viewIfLoaded", // `view` has been overridden by this property. | |
] | |
if | |
let name = property.name(), | |
!ignoreProperties.contains(name), | |
property.type() is UIView.Type, | |
let view = value(forKey: name) as? UIView { | |
view.accessibilityIdentifier = "\(type(of: self)).\(name)" | |
} | |
} | |
} | |
} | |
private extension objc_property_t { | |
func name() -> String? { | |
return String(cString: property_getName(self), encoding: .utf8) | |
} | |
func type() -> AnyClass? { | |
guard let attributes = String(cString: property_getAttributes(self)!, encoding: .utf8) | |
else { return nil } | |
guard let regexp = try? NSRegularExpression(pattern: "T@\"(.*)\"", options: []) else { return nil } | |
guard let result = regexp.firstMatch( | |
in: attributes, | |
options: [], | |
range: NSRange(location: 0, length: attributes.count) | |
) else { return nil } | |
let range = result.range(at: 1) | |
let className = attributes[range] | |
return NSClassFromString(className) | |
} | |
} |
Hello, on iOS13 this protocol throws exception
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Project.ViewController 0x7ff2cf714870> valueForUndefinedKey:]: this class is not key value coding-compliant for the key tabBarObservedScrollView.'
particularly in line 72. It's strange bc i have only one VC which is not a tabBarVC, it's an empty project and still throws that error. Do you know what might causing this problem, or how to fix it? Btw. thanks for sharing code
EDIT: quick fix between line 72 and 71 name != "tabBarObservedScrollView"
and it's working. I think this might be a bug bc tabBarObservedScrollView applies to tvOS 13+ so why it's there idk
Sorry, but, I don't have any idea. I'll try this later on iOS 13.
環境差異ぽいですが、より新しくはそうですね。コメントありがとうございます!😊