UIKit
iOS Development
Swift Programming
Code Snippets
View Hierarchy

Check if a subview is in a view

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In UIKit, checking whether one view is "in" another can mean two different things: the view is a direct child in the subviews array, or it is anywhere inside the ancestor's hierarchy. Those cases need different APIs.

If you only check subviews.contains, you will miss grandchildren and deeper descendants. In most practical code, what you really want is "is this view a descendant of that container?"

Direct Subview vs Descendant

Every UIView has a subviews array containing only its immediate children. That means this check works only for direct containment:

swift
let isDirectSubview = parentView.subviews.contains(childView)

This returns true only when childView.superview === parentView.

If the hierarchy is:

text
parentView
  containerView
    childView

then parentView.subviews.contains(childView) is false because childView is not a direct subview of parentView.

Best Option: isDescendant(of:)

UIKit already has the method most developers want:

swift
if childView.isDescendant(of: parentView) {
    print("childView is somewhere inside parentView")
}

This returns true for direct children, grandchildren, and any deeper nested view in the same subtree.

Here is a complete example:

swift
1import UIKit
2
3let parentView = UIView()
4let containerView = UIView()
5let childView = UIView()
6
7parentView.addSubview(containerView)
8containerView.addSubview(childView)
9
10print(parentView.subviews.contains(childView))   // false
11print(childView.isDescendant(of: parentView))    // true
12print(childView.superview === containerView)     // true

That makes the intent explicit and avoids manual tree traversal.

Checking by Walking the superview Chain

If you need custom logic, you can walk upward through the superview chain yourself:

swift
1func isContained(_ view: UIView, in ancestor: UIView) -> Bool {
2    var current = view.superview
3
4    while let candidate = current {
5        if candidate === ancestor {
6            return true
7        }
8        current = candidate.superview
9    }
10
11    return false
12}

Usage:

swift
print(isContained(childView, in: parentView))   // true

This is useful when you want to stop early, collect the full ancestor path, or combine the containment test with other conditions.

When Identity Matters

Use identity comparison, not value comparison. UIView is a reference type, so the question is whether two variables point to the same view object.

In Swift, that means using ===:

swift
if childView.superview === parentView {
    print("Direct parent")
}

Using == is the wrong mental model here. You are not comparing two data values; you are comparing object identity in the hierarchy.

Practical Cases

This check appears in several common situations:

  • preventing duplicate addSubview operations
  • deciding whether to move a view to a different container
  • verifying a reusable cell or overlay is still mounted
  • debugging Auto Layout problems by confirming which container owns a view

For example:

swift
1func attachOverlay(_ overlay: UIView, to host: UIView) {
2    guard !overlay.isDescendant(of: host) else {
3        return
4    }
5
6    host.addSubview(overlay)
7    overlay.frame = host.bounds
8}

That avoids adding the same overlay repeatedly during state updates.

Why Not Search the Whole Tree Manually

You can recurse through every subview under parentView, but that is usually the wrong direction. If you already have the candidate child view, walking upward through superview is simpler and often cheaper because the chain length is usually shorter than the full subtree size.

Searching downward only makes sense when you know the ancestor and are trying to find a matching descendant by some property, such as a tag, identifier, or custom class.

Common Pitfalls

The most common mistake is using parentView.subviews.contains(childView) when the real question is whether the child appears anywhere in the hierarchy. That only tests direct children.

Another mistake is checking containment before the view has actually been inserted into the hierarchy. A newly created view has superview == nil, so any containment test will be false.

Developers also sometimes compare views using == instead of ===. For view hierarchy checks, identity is the correct comparison.

Finally, remember that hierarchy changes must happen on the main thread. If UI state is being mutated from background code, containment checks can become unreliable and the app may crash.

Summary

  • 'subviews.contains checks only direct children.'
  • 'isDescendant(of:) is the simplest way to test whether a view is anywhere inside another view.'
  • Walking the superview chain is a good custom alternative.
  • Use === when comparing view objects by identity.
  • If you already have the child view, walking upward is usually better than recursively scanning downward.
  • Run view hierarchy logic on the main thread to keep UIKit state consistent.

Course illustration
Course illustration

All Rights Reserved.