Determine if UIView is visible to the user?

Solution 1:

For anyone else that ends up here:

To determine if a UIView is onscreen somewhere, rather than checking superview != nil, it is better to check if window != nil. In the former case, it is possible that the view has a superview but that the superview is not on screen:

if (view.window != nil) {
    // do stuff
}

Of course you should also check if it is hidden or if it has an alpha > 0.

Regarding not wanting your NSTimer running while the view is not visible, you should hide these views manually if possible and have the timer stop when the view is hidden. However, I'm not at all sure of what you're doing.

Solution 2:

You can check if:

  • it is hidden, by checking view.hidden
  • it is in the view hierarchy, by checking view.superview != nil
  • you can check the bounds of a view to see if it is on screen

The only other thing I can think of is if your view is buried behind others and can't be seen for that reason. You may have to go through all the views that come after to see if they obscure your view.

Solution 3:

This will determine if a view's frame is within the bounds of all of its superviews (up to the root view). One practical use case is determining if a child view is (at least partially) visible within a scrollview.

Swift 5.x:

func isVisible(view: UIView) -> Bool {
    func isVisible(view: UIView, inView: UIView?) -> Bool {
        guard let inView = inView else { return true }
        let viewFrame = inView.convert(view.bounds, from: view)
        if viewFrame.intersects(inView.bounds) {
            return isVisible(view: view, inView: inView.superview)
        }
        return false
    }
    return isVisible(view: view, inView: view.superview)
}

Older swift versions

func isVisible(view: UIView) -> Bool {
    func isVisible(view: UIView, inView: UIView?) -> Bool {
        guard let inView = inView else { return true }
        let viewFrame = inView.convertRect(view.bounds, fromView: view)
        if CGRectIntersectsRect(viewFrame, inView.bounds) {
            return isVisible(view, inView: inView.superview)
        }
        return false
    }
    return isVisible(view, inView: view.superview)
}

Potential improvements:

  • Respect alpha and hidden.
  • Respect clipsToBounds, as a view may exceed the bounds of its superview if false.