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

Можно ли узнать, что view находится на экране, не используя расчеты видимости?

1.6 Junior🔥 41 комментариев
#UIKit и верстка

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

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

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

Можно ли определить видимость View без ручных вычислений?

Да, можно. iOS предоставляет несколько встроенных механизмов для определения, находится ли UIView в видимой области экрана, без необходимости самостоятельно вычислять frame, bounds, contentOffset и другие геометрические параметры. Вот основные подходы:

1. Использование метода UIView.isDescendant(of:) и проверка окна

Самый простой способ — проверить, имеет ли view window и находится ли оно в иерархии окна.

extension UIView {
    var isVisibleOnScreen: Bool {
        // Проверяем, что view находится в иерархии window
        guard let window = self.window else { return false }
        
        // Проверяем, что view не скрыто и его alpha > 0
        guard !self.isHidden, self.alpha > 0 else { return false }
        
        // Проверяем, что frame view пересекается с bounds окна
        let viewFrameInWindow = self.convert(self.bounds, to: window)
        return viewFrameInWindow.intersects(window.bounds)
    }
}

2. Key-Value Observing (KVO) для свойства window

Можно отслеживать изменения свойства window у UIView:

class ViewController: UIViewController {
    @IBOutlet weak var targetView: UIView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Наблюдаем за изменением window
        targetView.addObserver(
            self,
            forKeyPath: "window",
            options: [.new, .old],
            context: nil
        )
    }
    
    override func observeValue(
        forKeyPath keyPath: String?,
        of object: Any?,
        change: [NSKeyValueChangeKey : Any]?,
        context: UnsafeMutableRawPointer?
    ) {
        if keyPath == "window", let view = object as? UIView {
            let isNowVisible = view.window != nil
            print("View visibility changed: \(isNowVisible)")
        }
    }
    
    deinit {
        targetView.removeObserver(self, forKeyPath: "window")
    }
}

3. Использование методов жизненного цикла UIViewController

Для view контроллеров можно использовать встроенные методы:

class MyViewController: UIViewController {
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        // View гарантированно видима на экране
        print("View is now visible on screen")
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        // View гарантированно НЕ видима на экране
        print("View is no longer visible on screen")
    }
    
    // Для child view controllers
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        let isVisible = self.isViewLoaded && self.view.window != nil
        print("View will become visible: \(isVisible)")
    }
}

4. DispatchQueue.main.async для проверки

Иногда полезно отложить проверку до следующего run loop:

func checkVisibilityAsync(for view: UIView) {
    DispatchQueue.main.async {
        let isVisible = view.window != nil 
            && !view.isHidden 
            && view.alpha > 0
            && view.frame.intersects(view.superview?.bounds ?? .zero)
        
        if isVisible {
            // Выполняем действия для видимой view
            self.configureVisibleView(view)
        }
    }
}

5. Использование UIApplication.shared.keyWindow

Для глобальной проверки видимости:

extension UIView {
    func isVisibleInKeyWindow() -> Bool {
        guard let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) 
              else { return false }
        
        let frameInKeyWindow = self.convert(self.bounds, to: keyWindow)
        
        return !self.isHidden
            && self.alpha > 0
            && self.window != nil
            && frameInKeyWindow.intersects(keyWindow.bounds)
    }
}

Важные нюансы:

  • Иерархия окон: View может быть в window, но перекрыто другим window (например, alert)
  • Преобразование координат: Метод convert(_:to:) критически важен для корректных расчетов
  • Состояние приложения: При сворачивании приложения все views временно теряют window
  • Анимации: Во время анимации frame может быть в промежуточном состоянии

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

  1. Для большинства случаев достаточно проверки view.window != nil
  2. Для точной проверки пересечения с bounds окна добавьте проверку frame
  3. Для отслеживания изменений используйте KVO или методы жизненного цикла
  4. Избегайте heavy calculations в методах типа layoutSubviews() или draw(_:)

Эти подходы позволяют эффективно определять видимость view, используя встроенные механизмы iOS, что надежнее и производительнее ручных вычислений с учетом contentOffset, contentInset и других параметров scroll view.