Определить, является ли UIView видимым для пользователя?

Можно ли определить, является ли мой UIView видимым для пользователя или нет?

Мой вид добавляется как subview несколько раз в Tab Bar Controller.

Каждый экземпляр этого представления имеет NSTimer, который обновляет представление.

Однако я не хочу обновлять представление, которое не отображается пользователю.

Возможно ли это?

Спасибо

Ответ 1

Вы можете проверить:

  • он скрыт, проверив view.hidden
  • он находится в иерархии представлений, проверив view.superview != nil
  • вы можете проверить границы представления, чтобы увидеть, находится ли он на экране

Единственное, что я могу придумать, это то, что ваш взгляд похоронен за другими и не может быть замечен по этой причине. Возможно, вам придется пройти через все взгляды, которые появляются после того, как они скрывают ваше мнение.

Ответ 2

Для всех, кто заканчивается здесь:

Чтобы определить, где UIView находится на экране, вместо проверки superview != nil, лучше проверить, есть ли window != nil. В первом случае возможно, что представление имеет супервизор, но супервизор не отображается на экране:

if (view.window != nil) {
    // do stuff
}

Конечно, вы должны также проверить, есть ли он hidden или имеет alpha > 0.

Если вы не хотите, чтобы ваш NSTimer работал, пока вид не отображается, вы должны скрыть эти представления вручную, если это возможно, и остановить таймер при скрытии представления. Тем не менее, я не совсем уверен, что вы делаете.

Ответ 3

Это определит, находится ли рамка представления в пределах всех своих супервидов (вплоть до корневого представления). Одним из практических вариантов использования является определение того, является ли дочернее представление (хотя бы частично) видимым в прокрутке.

func isVisible(view: UIView) -> Bool {
    func isVisible(view: UIView, inView: UIView?) -> Bool {
        guard let inView = inView else { return true }
        let viewFrame = inView.convertRect(view.bounds, fromView: view)
        if CGRectIntersectsRect(viewFrame, inView.bounds) {
            return isVisible(view, inView: inView.superview)
        }
        return false
    }
    return isVisible(view, inView: view.superview)
}

Потенциальные улучшения:

  • Respect alpha и hidden.
  • Respect clipsToBounds, поскольку представление может превышать границы его супервизора, если false.

Ответ 4

Решение, которое сработало для меня, состояло в том, чтобы сначала проверить, имеет ли представление окно, а затем перебирать супервизоры и проверять:

  • вид не скрыт.
  • представление находится в пределах своих ограничений.

Кажется, все хорошо.

Swift 3.0

public func isVisible(view: UIView) -> Bool {

  if view.window == nil {
    return false
  }

  var currentView: UIView = view
  while let superview = currentView.superview {

    if (superview.bounds).intersects(currentView.frame) == false {
      return false;
    }

    if currentView.isHidden {
      return false
    }

    currentView = superview
  }

  return true
}

Ответ 5

Я действительно хочу знать, видимо ли для пользователя вид, который вы должны учитывать:

  • Является ли окно просмотра не ноль и равно самому верхнему окну
  • Является ли представление и все его наблюдения альфa >= 0,01 (пороговое значение также используется UIKit для определения того, должно ли оно обрабатывать касания), а не скрыто
  • Является ли z-индекс (значение стекирования) представления более высоким, чем другие представления в той же иерархии.
  • Даже если индекс z ниже, он может быть виден, если другие виды сверху имеют прозрачный цвет фона, alpha 0 или скрыты.

Особенно прозрачный цвет фона в представлении спереди может представлять проблему для программной проверки. Единственный способ быть уверенным в том, чтобы сделать программный снимок представления, чтобы проверить и разбить его в своем фрейме с моментальным снимком всего экрана. Однако это не будет работать для представлений, которые не являются достаточно отличительными (например, полностью белые).

Для вдохновения см. метод isViewVisible в проект iOS Calabash-сервера

Ответ 6

В viewWillAppear установите значение "isVisible" в true, в viewWillDisappear установите значение false. Лучший способ узнать подпрограммы UITabBarController, также работает для контроллеров навигации.

Ответ 7

Это поможет вам понять, является ли ваш UIView самым верхним видом. Может быть полезно:

let visibleBool = view.superview?.subviews.last?.isEqual(view)
//have to check first whether it nil (bc it an optional) 
//as well as the true/false 
if let visibleBool = visibleBool where visibleBool { value
  //can be seen on top
} else {
  //maybe can be seen but not the topmost view
}

Ответ 8

Проверенное решение.

func isVisible(_ view: UIView) -> Bool {
    if view.isHidden || view.superview == nil {
        return false
    }

    if let rootViewController = UIApplication.shared.keyWindow?.rootViewController,
        let rootView = rootViewController.view {

        let viewFrame = view.convert(view.bounds, to: rootView)

        let topSafeArea: CGFloat
        let bottomSafeArea: CGFloat

        if #available(iOS 11.0, *) {
            topSafeArea = rootView.safeAreaInsets.top
            bottomSafeArea = rootView.safeAreaInsets.bottom
        } else {
            topSafeArea = rootViewController.topLayoutGuide.length
            bottomSafeArea = rootViewController.bottomLayoutGuide.length
        }

        return viewFrame.minX >= 0 &&
               viewFrame.maxX <= rootView.bounds.width &&
               viewFrame.minY >= topSafeArea &&
               viewFrame.maxY <= rootView.bounds.height - bottomSafeArea
    }

    return false
}

Ответ 9

попробуйте это:

func isDisplayedInScreen() -> Bool
{
 if (self == nil) {
     return false
  }
    let screenRect = UIScreen.main.bounds 
    // 
    let rect = self.convert(self.frame, from: nil)
    if (rect.isEmpty || rect.isNull) {
        return false
    }
    // 若view 隐藏
    if (self.isHidden) {
        return false
    }

    // 
    if (self.superview == nil) {
        return false
    }
    // 
    if (rect.size.equalTo(CGSize.zero)) {
        return  false
    }
    //
    let intersectionRect = rect.intersection(screenRect)
    if (intersectionRect.isEmpty || intersectionRect.isNull) {
        return false
    }
    return true
}

Ответ 10

Я сравнил @Audrey M. и @John Gibb с их решениями.
И @Audrey M. его путь показал себя лучше (раз 10).
Поэтому я использовал его, чтобы сделать его заметным.

Я сделал RxSwift Observable, чтобы получать уведомления, когда UIView становится видимым.
Это может быть полезно, если вы хотите инициировать событие просмотра баннера

import Foundation
import UIKit
import RxSwift

extension UIView {
    var isVisibleToUser: Bool {

        if isHidden || alpha == 0 || superview == nil {
            return false
        }

        guard let rootViewController = UIApplication.shared.keyWindow?.rootViewController else {
            return false
        }

        let viewFrame = convert(bounds, to: rootViewController.view)

        let topSafeArea: CGFloat
        let bottomSafeArea: CGFloat

        if #available(iOS 11.0, *) {
            topSafeArea = rootViewController.view.safeAreaInsets.top
            bottomSafeArea = rootViewController.view.safeAreaInsets.bottom
        } else {
            topSafeArea = rootViewController.topLayoutGuide.length
            bottomSafeArea = rootViewController.bottomLayoutGuide.length
        }

        return viewFrame.minX >= 0 &&
            viewFrame.maxX <= rootViewController.view.bounds.width &&
            viewFrame.minY >= topSafeArea &&
            viewFrame.maxY <= rootViewController.view.bounds.height - bottomSafeArea

    }
}

extension Reactive where Base: UIView {
    var isVisibleToUser: Observable<Bool> {
        // Every second this will check 'isVisibleToUser'
        return Observable<Int>.interval(.milliseconds(1000),
                                        scheduler: MainScheduler.instance)
        .flatMap { [base] _ in
            return Observable.just(base.isVisibleToUser)
        }.distinctUntilChanged()
    }
}

Используйте это так:

import RxSwift
import UIKit
import Foundation

private var _disposeBag: DisposeBag?

private func _checkBannerVisibility() {
    _disposeBag = DisposeBag()
    bannerView.rx.isVisibleToUser
        .filter { $0 }
        .subscribe(onNext: { [weak self] _ in
            self?._disposeBag = nil // Only trigger it once
            // ... Do something with the 'isVisibleToUser' event
        }).disposed(by: _disposeBag!)
}

Ответ 11

Если вы используете скрытое свойство вида, то:

view.hidden(объект C) или view.isHidden(swift) - свойство чтения/записи. Таким образом, вы можете легко читать или писать

Для быстрого 3.0

if(view.isHidden){
   print("Hidden")
}else{
   print("visible")
}