You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

UISearchController层级出现意外UIView,阻碍Appium自动化测试求助

我之前做自动化测试时也遇到过这个一模一样的问题!这个烦人的视图是UISearchController内置的无障碍辅助视图,专门给视障用户提供双击关闭搜索的功能,但确实会打乱Appium的元素定位逻辑。我来给你详细拆解一下:

这个视图的来龙去脉
  • 何时/为何被添加:当UISearchController的搜索栏成为第一响应者(也就是用户激活搜索状态)时,系统会自动在搜索控制器的视图层级中插入一个全屏透明的UIView。它的accessibilityLabel设为"Double-tap to dismiss",isAccessibilityElement=true,目的是让VoiceOver用户可以通过双击空白区域快速退出搜索模式。这个视图属于苹果的私有实现,没有公开API控制它的创建时机,是系统自动注入的。
  • 为何会被重置属性:系统在搜索控制器的状态变化时(比如搜索栏失焦后重新激活、视图控制器执行viewWillAppear/viewDidAppear等生命周期方法),会重新同步这个视图的无障碍属性,所以你手动设置isAccessibilityElement=false后,后续的系统更新会把它改回true。
可靠的解决办法

我试过几种方案,下面两个是最稳定的:

方案1:用KVO监听并强制覆盖属性

通过KVO监听这个视图的isAccessibilityElement属性,一旦系统把它设为true,立刻改回false。步骤如下:

  1. 先实现一个辅助方法找到这个目标视图:
private func findDismissAccessibilityView(in view: UIView) -> UIView? {
    for subview in view.subviews {
        if subview.accessibilityLabel == "Double-tap to dismiss" {
            return subview
        }
        if let found = findDismissAccessibilityView(in: subview) {
            return found
        }
    }
    return nil
}
  1. 在搜索控制器激活后(比如viewDidAppear或者搜索栏的textDidBeginEditing回调里)添加KVO观察者:
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    if let dismissView = findDismissAccessibilityView(in: searchController.view) {
        dismissView.addObserver(self, forKeyPath: #keyPath(UIView.isAccessibilityElement), options: [.new], context: nil)
    }
}
  1. 实现KVO的回调方法,强制把属性改回false:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    guard keyPath == #keyPath(UIView.isAccessibilityElement), 
          let targetView = object as? UIView,
          targetView.accessibilityLabel == "Double-tap to dismiss" else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        return
    }
    // 强制覆盖为false,阻止系统重置
    targetView.isAccessibilityElement = false
}
  1. 记得在视图销毁时移除观察者,避免内存泄漏:
deinit {
    if let dismissView = findDismissAccessibilityView(in: searchController.view) {
        dismissView.removeObserver(self, forKeyPath: #keyPath(UIView.isAccessibilityElement))
    }
}

方案2:通过UIView分类重写无障碍属性(更彻底)

如果KVO还是偶尔失效,可以用Runtime给UIView添加一个分类,重写isAccessibilityElement的getter方法,针对这个特定视图直接返回false:

import ObjectiveC

private var AssociatedKeys = [String: UInt8]()

extension UIView {
    private var originalIsAccessibilityElement: Bool {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.originalAccessibility) as? Bool ?? super.isAccessibilityElement
        }
        set {
            objc_setAssociatedObject(self, &AssociatedKeys.originalAccessibility, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
    
    override open var isAccessibilityElement: Bool {
        get {
            if accessibilityLabel == "Double-tap to dismiss" {
                return false
            }
            return originalIsAccessibilityElement
        }
        set {
            originalIsAccessibilityElement = newValue
        }
    }
}

注意:这个方案会全局影响所有UIView,所以一定要加判断条件,只针对目标视图生效。另外,Runtime相关的代码在iOS新版本中可能有兼容性问题,测试时要覆盖目标版本。

避坑提醒
  • 不要直接移除这个视图:系统会在需要时重新创建它,而且操作私有视图可能导致苹果审核拒绝,或者在后续iOS版本中崩溃。
  • 优先在代码层面解决:Appium层面的规避(比如忽略元素)可能不稳定,因为元素的层级或属性可能随iOS版本变化。

内容的提问来源于stack exchange,提问作者Prettygeek

火山引擎 最新活动