为何UIDeferredMenuElement无法加入UIAction数组却可组合传入UIMenu?
你定义了一个UIAction类型的数组ele,并将其赋值给按钮菜单:
var ele = // 一个 [UIAction] 类型数组 yourButton.menu = UIMenu(children: ele)
为监听菜单打开事件,你用常规技巧创建了UIDeferredMenuElement实例:
let trick = UIDeferredMenuElement.uncached({completion in print("menu opened") completion([]) })
但执行ele.append(trick)时会报错:
[X] Cannot convert value of type 'UIDeferredMenuElement' to expected argument type 'UIAction'
不过直接把它和UIAction组合传入UIMenu却能正常运行,比如:
aButton.menu = UIMenu(children: [ele[0], ele[1], ele[2], trick])
aButton.menu = UIMenu(children: ele + [trick])也能正常执行,但ele.append(contentsOf: [trick])还是失败,强制转换trick as! UIAction也提示类型不相关。请问该现象的技术原因是什么?
补充:通常创建UIAction数组的方式如下:
let noms = [ ("TAG", "Do crazy things", "arrowtriangle.right.and.line.vertical.and.arrowtriangle.left"), ("BLAH", "Even more\nwild stuff.", "rectangle.grid.3x2"), ("LOUT", "Logout now", "lock.open") ] let ele = noms.map({ fn in return UIAction(title: fn.1, image: UIImage(systemName: fn.2)) { [weak self] _ in guard let self else { return } aButtonProcess(fn.0) } })
技术原因分析
核心原因是Swift强类型特性下的容器类型约束差异:
数组
ele的类型是严格的[UIAction]
你通过map生成的ele是明确的UIAction专属数组,Swift作为强类型语言,要求数组内的元素必须完全匹配定义的类型。UIDeferredMenuElement和UIAction是毫无继承/兼容关系的独立类型,所以直接追加必然触发类型不匹配错误,强制转换也不可能生效——两者根本不在同一个类型分支上。UIMenu的children参数接收的是[UIMenuElement]
实际上UIAction和UIDeferredMenuElement都属于UIMenuElement的子类(准确说是遵循UIMenuElement协议的类型)。当你写出[ele[0], trick]或者ele + [trick]时,Swift会自动将这个临时数组的类型推断为[UIMenuElement],刚好符合UIMenu初始化方法的参数要求,因此可以正常运行。
总结来说:你的ele是只能装UIAction的“专用容器”,而UIMenu的children是能装所有UIMenuElement子类的“通用容器”,两者的类型约束不同,才导致了这种看似矛盾的现象。
内容的提问来源于stack exchange,提问作者Fattie

