找出 UIView 进入(滚动到)屏幕的时刻

Find out the moment UIView entered (scrolled into) the screen

View Controller B
    B View 
        Scroll View
            Image View
            Container View
                View Controller A view

需要以某种方式通知 A 的视图已 "scrolled into" 屏幕。

由于您几乎没有控制权,因此最好的解决方案实际上可能是 KVO。我喜欢避免它。您可以创建一个根本无法访问任何内容的解决方案。以下将采用第一个超级视图,即滚动视图,并尝试挂钩它的内容偏移量,并在滚动视图上可见和不可见时显示:

class MyView: UIView {

    private weak var scrollView: UIScrollView?
    private var observationContext = 0
    private(set) var isCurrentlyVisible: Bool = false {
        didSet {
            if(oldValue != isCurrentlyVisible) {
                print(isCurrentlyVisible ? "Visible" : "Not visisble")
            }
        }
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
            self.findParentScrollView()
            self.attachScrollViewObserving()
        }
    }

    private func findParentScrollView() {
        var scrollView: UIView? = self.superview
        while scrollView != nil && !(scrollView is UIScrollView) {
            scrollView = scrollView?.superview
        }
        self.scrollView = scrollView as? UIScrollView
    }

    private func attachScrollViewObserving() {
        guard let scrollView = self.scrollView else { return }
        scrollView.addObserver(self, forKeyPath: #keyPath(UIScrollView.contentOffset), options: [], context: &observationContext)
        refreshIsVisibleInScrollView()
    }

    private func refreshIsVisibleInScrollView() {
        guard let scrollView = self.scrollView else {
            isCurrentlyVisible = false
            return
        }
        let frameInScrollView = self.convert(bounds, to: scrollView)
        let visibleFrame = CGRect(x: scrollView.contentOffset.x, y: scrollView.contentOffset.y, width: scrollView.bounds.width, height: scrollView.bounds.height)
        isCurrentlyVisible = visibleFrame.intersects(frameInScrollView) // OR visibleFrame.contains(frameInScrollView)
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if &observationContext == context {
            refreshIsVisibleInScrollView()
        } else {
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        }
    }

}

我希望你能想象这里的很多事情都是危险的,但如果你至少有一些访问你的观点是可以避免的:

DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {

此处视图等待一秒钟以确保它实际上已经添加到视图层次结构中。更好的方法是在视图控制器中执行此操作,例如:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    myView.scrollView = self.parentScrollView
    myView.attachScrollViewObserving()
}

findParentScrollView也很危险。如果您可以找到以某种方式直接获取它或在创建视图控制器时分配它 A ,您将提高稳定性。问题自然是在某些情况下您可能在滚动视图中有滚动视图(请注意 table 视图和集合视图也是滚动视图)。

我希望剩下的应该很简单。但请注意,这还不是万无一失的实现。其他属性可能会影响您的视图的可见性,例如在其父视图中调整大小或重新定位您自己的视图;更改内容插图;改变滚动视图本身的大小;转换...尝试处理所有这些可能有点矫枉过正,所以只使用您认为可能会改变的东西。