使用KVO观察UIView的子视图数组的变化

16

我已经四处寻找答案并尝试实现它,但是没有任何作用。基本上,我需要能够观察VC视图的子视图数组中的更改。如果从该数组中删除现有视图,则希望收到通知并运行一些代码。

这可能吗?

编辑-更多信息

我正在尝试修复一个奇怪的边缘情况错误,即在UISearchDisplayController(非常自定义)的UISearchBar上快速点击会导致sdController(或者更确切地说是嵌入在navBar效果的管理searchBar)消失在视图中,但sdController仍然处于活动状态。这意味着navBar保持在-y原点,下面的tableView无法滚动。

我的最初想法是达到一个状态,在其中sdController处于活动状态,但UISearchDisplayControllerContainerView不在视图层次结构中。我尝试在VC的viewDidLayoutSubviews中进行测试,但遗憾的是,当您点击搜索栏并启动sdController动画时,sdController处于活动状态,而UISearchDisplayControllerContainerView不在视图层次结构中 :(。


肮脏的技巧:通过 NSNotificationCenter 发送通知,在删除子视图时捕获它,然后在需要运行一些代码的地方处理它。或者使用委托。 - Oleg Sobolev
2
你不能 KVO subviews。但是,当一个视图获得或失去子视图时(如果父视图在屏幕上的视图层次结构中),系统会向该视图发送 layoutSubviews。也许你可以使用自定义的 UIView 子类作为父视图,并在 layoutSubviews 中执行所需操作。如果这还不够,请编辑你的问题以包含更多关于为什么你想在子视图被移除时得到通知的细节。我们可能能够给你一个更好的解决方案。 - rob mayoff
@robmayoff 感谢您的评论。我已经更新了我的问题。 - Lizza
2
听起来你正在尝试应用一个hack解决方案来修复在屏幕上快速点击元素时出现的意外行为。当你说一个视图控制器“从视图中消失”时,这是令人困惑的,因为视图控制器不是可见对象。你应该考虑调查是什么导致了你的视图首先消失并防止它,而不是试图在它被移除时进行追溯操作。 - Ian MacDonald
感谢您的评论。我所说的sdController变得不可见是指由sdController管理的搜索栏不再可见,导致导航栏区域为空,因为真正的导航栏仍然停留在-y原点处。 - Lizza
4个回答

24
您可以观察 CALayer 的属性sublayers,该属性符合 KVO,而不是 UIView 的subviews

2
请提供一份文档链接,说明核心动画通常或CALayer的子视图特别是符合KVO。据我了解,它们不符合。(注意:KVC兼容性并不意味着KVO兼容性)。 - Nikolai Ruhe
2
代码:[v.layer addObserver:self forKeyPath:@"sublayers" options:NSKeyValueObservingOptionNew context:nil]; - Nadzeya

4

和苹果框架中的大多数属性一样,subviews不符合KVO标准

如果您控制子视图或父视图,则可以通过子类化和覆盖来观察视图层次结构的更改:

在父视图中,您必须覆盖...

- (void)willRemoveSubview:(UIView *)subview

如果您控制子视图,您可以重写...

- (void)willMoveToSuperview

这两种方法在视图被移除之前都会被调用。


2

Swift 3.x

使用自定义视图,例如:

class ObservableView: UIView {
    weak var delegate:ObservableViewDelegate?
    
    override func didAddSubview(_ subview: UIView) {
        super.didAddSubview(subview)
        delegate?.observableView(self, didAddSubview: subview)
    }
    
    override func willRemoveSubview(_ subview: UIView) {
        super.willRemoveSubview(subview)
        delegate?.observableview(self, willRemoveSubview: subview)
    }
    
}

protocol ObservableViewDelegate: class {
    func observableView(_ view:UIView, didAddSubview:UIView)
    func observableview(_ view:UIView, willRemoveSubview:UIView)
}

//Use

class ProfileViewController1: UIViewController, ObservableViewDelegate {
    @IBOutlet var headerview:ObervableView! //set custom class in storyboard or xib and make outlet connection
    
    override func viewDidLoad() {
        super.viewDidLoad()
        headerview.delegate = self
    }
    
    //Delegate methods
    func observableView(_ view: UIView, didAddSubview: UIView) {
        //do somthing
    }
    
    func observableview(_ view: UIView, willRemoveSubview: UIView) {
        //do somthing
    }
}

0

嗯,我遇到了类似的情况。基本上,如果您需要一个视图来观察其父子视图数组,或者甚至是其大小或任何更改(这是非KVO兼容的),以便例如将其保持在顶部,您可以使用关联值和一些交换的组合。

首先,声明子项,并且可选地,由于我喜欢尽可能将这种边缘解决方案隔离开来,因此嵌入一个UIView私有扩展以添加KVO机制(Swift 5.1代码):

class AlwaysOnTopView: UIView {

    private var observer: NSKeyValueObservation?
    
    override func willMove(toSuperview newSuperview: UIView?) {
        if self.superview != nil {
            self.observer?.invalidate()
        }
        super.willMove(toSuperview: newSuperview)
    }
    
    override func layoutSubviews() {
        self.superview?.bringSubviewToFront(self)
    }
    
    override func didMoveToSuperview() {
        super.didMoveToSuperview()
        
        if self.superview != nil {
            self.layer.zPosition = CGFloat.greatestFiniteMagnitude
            self.superview?.swizzleAddSubview()
            observer = self.superview?.observe(\.subviewsCount, options: .new) { (view, value) in
                guard view == self.superview else {return}
                self.superview?.bringSubviewToFront(self)
            }
        }
    }}

    private extension UIView {
        @objc dynamic var subviewsCount: Int {
            get {
                return getAssociatedValue(key: "subviewsCount", object: self, initialValue: self.subviews.count)
            }
            set {
                self.willChangeValue(for: \.subviewsCount)
                set(associatedValue: newValue, key: "subviewsCount", object: self)
                self.didChangeValue(for: \.subviewsCount)
            }
        }
        
        @objc dynamic func _swizzled_addSubview(_ view: UIView) {
            _swizzled_addSubview(view)
            self.subviewsCount = self.subviews.count
        }
        
        func swizzleAddSubview() {
            let selector1 = #selector(UIView.addSubview(_:))
            let selector2 = #selector(UIView._swizzled_addSubview(_:))
            
            let originalMethod = class_getInstanceMethod(UIView.self, selector1)!
            let swizzleMethod = class_getInstanceMethod(UIView.self, selector2)!
            method_exchangeImplementations(originalMethod, swizzleMethod)
        }
    }
}

通过这种方式,您可以使内部属性可观察,并与任何添加的视图保持一致。这只是一个快速实现,有许多角落情况需要处理(例如:使用插入或任何其他UIView方法添加的视图等),但这是一个起点。此外,这可以根据不同的需求进行定制(例如观察兄弟姐妹,而不仅仅是父母等)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接