Как найти первую UIView с заданным backgroundColor в иерархии вью
Что проверяют
Умеете ли вы обходить дерево и уточнять задачу до кода: что считать «первой» вью и как вообще сравнивать цвета. Следом обычно просят переписать рекурсию итеративно или найти ближайшую к корню вью.
Как отвечать
Сначала уточните условие вслух:
- порядок обхода: рекурсия даёт поиск в глубину, и «первой» окажется вью из первой ветки, даже если она лежит глубоко. Если нужна ближайшая к корню, это обход в ширину с очередью;
- порядок сабвью: в
subviewsэлемент с индексом 0 лежит ниже всех. Если «первая» значит «верхняя на экране», идти нужно с конца массива,subviews.reversed(); - границы поиска: включать ли сам корень и учитывать ли скрытые вью (
isHidden,alpha == 0).
Решение удобно оформить расширением UIView с предикатом, тогда поиск по цвету становится частным случаем: func firstDescendant(where predicate: (UIView) -> Bool) -> UIView?. Внутри рекурсия с ранним return либо массив в роли стека или очереди, что снимает вопрос о глубине рекурсии. Сложность O(n) по числу вью. Работа с UIKit идёт на главном потоке, поэтому метод стоит пометить @MainActor.
Самое интересное это сравнение цветов. Оператор == у UIColor вызывает isEqual, а тот учитывает цветовое пространство: UIColor.white лежит в пространстве оттенков серого и с UIColor(red: 1, green: 1, blue: 1, alpha: 1) не совпадёт. Системные цвета вроде .systemBackground динамические, их сначала приводят к конкретному значению через resolvedColor(with: traitCollection). Надёжнее сравнивать компоненты после getRed(_:green:blue:alpha:) с небольшим допуском.
Где спотыкаются
Пишут рекурсию и не могут объяснить, в каком порядке она находит вью. На вопрос «а если нужна ближайшая к корню» ответа нет.
Сравнивают цвета через == и получают nil там, где вью на экране явно нужного цвета. Особенно часто с белым, чёрным и цветами из ассетов.
Ищут через view.subviews.first(where:) и проверяют только прямых потомков. Вопрос про всю иерархию, нужен полный обход вглубь или вширь.