在iOS工程中,通常我们的详情页面UI都是比较复杂的,这就导致往往视图层级较深,这种情况下,如果要将内部的UIView识别到事件时,抛出到VC来执行具体处理,需要一层一层中转,很麻烦。
本文是介绍一种解决一层一层中转直达VC的方式。
一、问题引出
如下是一个打车软件的详情截图。我们分析页面,打赏司机
按钮的层级结构: UIButton -》ZLToolView -》ZLHeaderView -》UITableView -》ZLContentView -》VC.view -》 VC。
现在,如果我们点击
打赏司机
,需要VC去执行逻辑。
1. 不管是使用代理或者block,这个链路都很长。
这个事件首先是在ZLToolView
添加按钮点击监听,然后需要中转回调给ZLHeaderView
, 再回调给UITableView
的父视图ZLContentView
, 最后再回调给VC。
2. 使用全局通知
如果使用通知的话,能做到跨层,但是缺点是通知更适合于全局的一对多场景,一个UI事件还是一对一的关系; 此外过多的通知也不利于维护。
二、解决方案
上述问题我这边思考的一个解决方案是通过响应者链来实现从内层的View
事件跨层抛给VC
,理由如下:
- 响应者链在UIView添加到Superview上之后就存在,不用自己额外再建立责任链(响应者链其实是一个责任链设计模式)。
- 通过响应链来传递可以使得链中任何关心这个事件的上级可拦截。
- 实现的是一对一的响应。
具体的封装代码如下:
enum XLInnerViewEvent {
case refreshABC // 刷新ABC
case refreshDetail // 刷新详情接口,如果有参数,可以通过枚举关联值传递
}
protocol XLInnerEventResponsible: UIResponder {
func innerEventHandle(type: XLInnerViewEvent)
}
extension UIView {
// 一个沿着响应链向上传递事件的方法,bubble=冒泡
func bubbleEvent(_ eventType: XLInnerViewEvent) {
var nextRespnder = self.next
while nextRespnder != nil {
if let savior = nextRespnder as? XLInnerEventResponsible {
savior.innerEventHandle(type: eventType)
nextRespnder = nil
}
nextRespnder = nextRespnder?.next
}
}
}
使用:
// 内层view传递事件
@objc func reloadBtnAction() {
self.bubbleEvent(.refreshABC)
}
// VC或响应链中感兴趣的类遵守协议XLInnerEventResponsible
extension VC: XLInnerEventResponsible {
func innerEventHandle(type: XLInnerViewEvent) {
switch type {
case .refreshABC:
print("refreshABC---")
case .refreshDetail:
print("refreshDetail---")
}
}
}