Given two UIViews, find their closest common superview. How do you handle a view that is its own ancestor, or views in different trees?
Predict first
What closest common superview does the walk find for these five pairs?
import UIKit
func make(_ name: String, in parent: UIView? = nil) -> UIView {
let v = UIView()
v.accessibilityIdentifier = name
parent?.addSubview(v)
return v
}
func name(_ v: UIView?) -> String { v?.accessibilityIdentifier ?? "nil" }
/// The closest view that is an ancestor of both (a view counts as its own ancestor).
func commonSuperview(_ a: UIView, _ b: UIView) -> UIView? {
var seen = Set<ObjectIdentifier>()
var cursor: UIView? = a
while let v = cursor { seen.insert(ObjectIdentifier(v)); cursor = v.superview }
cursor = b
while let v = cursor {
if seen.contains(ObjectIdentifier(v)) { return v }
cursor = v.superview
}
return nil
}
let root = make("root")
let a = make("A", in: root), b = make("B", in: root)
let a1 = make("a1", in: a), a2 = make("a2", in: a), b1 = make("b1", in: b)
let loose = make("loose")
print(name(commonSuperview(a1, a2)))
print(name(commonSuperview(a1, b1)))
print(name(commonSuperview(a, a1)))
print(name(commonSuperview(a1, loose)))
print(name(commonSuperview(b1, b1)))
- A / root / A / nil / b1
- A / root / root / nil / b1
- A / root / A / root / nil
Mental model. Two people trace their family trees upward; the first ancestor they have in common is their closest shared relative, and a parent counts as an ancestor of their own child.
Every view knows its superview, so the hierarchy is a tree with parent pointers and this is the “lowest common ancestor” problem. The clean approach: walk from the first view up to the root, recording each view you pass in a set (keyed by ObjectIdentifier, because views are reference types and not hashable by value), then walk up from the second view and return the first one already in the set. O(depth) time and memory. A constant-memory variant measures both depths, lifts the deeper view until the depths match, then lifts both together until they meet. UIKit also has isDescendant(of:), which answers “is A inside B?” but not “what is their shared ancestor?”.
Decide and state the edge cases: if one view is the ancestor of the other, the ancestor is the answer (the program starts each walk at the view itself, so commonSuperview(a, a1) returns a and a view with itself returns itself); if the views are in different trees, for instance one is not in the hierarchy yet, there is none, so return an optional nil. The check builds root > (A > a1, a2), (B > b1) plus a detached view, and runs in the iOS Simulator. Reported at Meta (anonymous, unverified) as one of the view-and-tree style questions. In real code you would use it to convert a rect between two unrelated views (convert(_:to:) already finds the shared ancestor for you).
Siblings share their parent, cousins share the root, an ancestor wins over its descendant, a detached view has none, and a view with itself is itself.
Common traps
- Starting the upward walk at the superview. You then miss the case where one view contains the other.
- Comparing views with
==on frames or tags. Compare identity (===orObjectIdentifier).
They might follow up with
- Can you do it with O(1) extra memory? Yes: compute both depths, lift the deeper one, then move both up together.
- What does
convert(_:to:)do with two unrelated views? It goes via the shared ancestor or window; with no shared ancestor the result is not meaningful.
Related questions
- 7.13.2 Write a function that prints a UIView’s subview tree with indentation, like recursiveDescription. Recursive…
- 7.2.2 What is the difference between frame and bounds, and what happens to them under a transform?
- 7.1.1 How does UIKit decide which view receives a touch, and why can a subview outside its parent’s bounds not be…
Write a function that prints a UIView’s subview tree with… →