← Назад к вопросам

Какие знаешь способы проверки нахождения View на экране?

1.8 Middle🔥 162 комментариев
#Язык Swift

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Проверка нахождения View на экране в iOS

В iOS разработке проверка видимости UIView на экране — нетривиальная задача, такую проверку выполняют по отношению к окну приложения (UIWindow). Вот основные подходы, которые я использую в зависимости от контекста и требований.

1. Проверка через geometry и window

Наиболее фундаментальный способ — анализ фрейма view в координатах окна.

extension UIView {
    var isVisibleOnScreen: Bool {
        // 1. View должна быть частью иерархии окон
        guard let window = self.window else { return false }
        
        // 2. Преобразуем frame view в координаты окна
        let viewFrameInWindow = convert(bounds, to: window)
        
        // 3. Проверяем пересечение с bounds окна
        let windowBounds = window.bounds
        let isOnScreen = windowBounds.intersects(viewFrameInWindow)
        
        // 4. Дополнительные проверки
        return isOnScreen && 
               !isHidden && 
               alpha > 0.01 && 
               window.isKeyWindow
    }
}

Ключевые моменты:

  • convert(_:to:) — критически важный метод для корректного преобразования координат
  • intersects() — проверяет фактическое пересечение прямоугольников
  • Дополнительные условия: isHidden, alpha, состояние окна

2. Проверка с учетом родительских view

Часто view может быть технически в пределах окна, но скрыта родительскими view.

extension UIView {
    var isFullyVisibleOnScreen: Bool {
        guard let window = self.window,
              !isHidden,
              alpha > 0.01,
              window.isKeyWindow else {
            return false
        }
        
        // Проверяем всю цепочку родителей
        var currentView: UIView? = self
        while let view = currentView {
            if view.isHidden || view.alpha < 0.01 {
                return false
            }
            currentView = view.superview
        }
        
        // Проверка геометрической видимости
        let viewFrameInWindow = convert(bounds, to: window)
        return window.bounds.intersects(viewFrameInWindow)
    }
}

3. Проверка через UIScrollView

Для содержимого скролл-вью нужен особый подход, учитывающий contentOffset и contentInset.

extension UIView {
    func isVisibleInScrollView(_ scrollView: UIScrollView) -> Bool {
        let viewFrameInScrollView = convert(bounds, to: scrollView)
        let visibleRect = CGRect(
            x: scrollView.contentOffset.x,
            y: scrollView.contentOffset.y,
            width: scrollView.bounds.width,
            height: scrollView.bounds.height
        ).inset(by: scrollView.contentInset)
        
        return visibleRect.intersects(viewFrameInScrollView)
    }
}

4. Проверка с учетом safe area

В современных iOS важно учитывать safe area insets, особенно для iPhone X и новее.

extension UIView {
    var isVisibleInSafeArea: Bool {
        guard let window = self.window,
              isVisibleOnScreen else {
            return false
        }
        
        let viewFrameInWindow = convert(bounds, to: window)
        let safeAreaFrame = window.bounds.inset(by: window.safeAreaInsets)
        
        return safeAreaFrame.intersects(viewFrameInWindow)
    }
}

5. Оптимизированная проверка для коллекций

Для UITableView и UICollectionView есть более эффективные методы:

// Для UITableView
func visibleTableViewCells() -> [UITableViewCell] {
    return tableView.visibleCells.filter { cell in
        guard let indexPath = tableView.indexPath(for: cell) else { return false }
        let cellRect = tableView.rectForRow(at: indexPath)
        return tableView.bounds.contains(cellRect)
    }
}

// Для UICollectionView
func visibleCollectionViewItems() -> [UICollectionViewCell] {
    return collectionView.visibleCells.filter { cell in
        guard let indexPath = collectionView.indexPath(for: cell) else { return false }
        let layoutAttributes = collectionView.layoutAttributesForItem(at: indexPath)
        return collectionView.bounds.contains(layoutAttributes?.frame ?? .zero)
    }
}

Практические рекомендации

Когда использовать каждый подход:

  1. Базовый isVisibleOnScreen — для большинства обычных view
  2. Полная проверка с родителями — когда важно убедиться, что view не скрыта в иерархии
  3. Проверка в скролл-вью — для динамического контента
  4. Учет safe area — для корректного отображения на устройствах с "чёлкой"

Производительность:

  • Геометрические расчеты могут быть затратными при частом вызове
  • Для анимаций лучше использовать CADisplayLink или отложенные проверки
  • Кэширование результатов для статичного layout

Распространённые ошибки:

  • Проверка только superview без учета всего цепочки иерархии
  • Игнорирование contentOffset в скролл-вью
  • Неучёт transform при расчете frame
  • Забывают про alpha и isHidden

Пример комплексного решения

protocol ViewVisibilityTracker {
    func checkVisibility(for view: UIView, completion: @escaping (Bool) -> Void)
}

class DefaultVisibilityTracker: ViewVisibilityTracker {
    func checkVisibility(for view: UIView, completion: @escaping (Bool) -> Void) {
        DispatchQueue.main.async {
            let isVisible = view.isFullyVisibleOnScreen
            completion(isVisible)
        }
    }
}

Выбор метода зависит от конкретной задачи: для ленивой загрузки изображений подойдет простая проверка пересечения, для аналитики кликов нужна более строгая проверка с учетом всей иерархии, а для оптимизации производительности в сложных интерфейсах может потребоваться кастомная реализация с учетом специфики layout.