Визначити, чи UIView видимий користувачеві?


78

чи можна визначити UIView, видимий користувач чи ні?

My View додано subviewкілька разів у файл Tab Bar Controller.

Кожен екземпляр цього подання має NSTimerоновлення подання.

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

Чи можливо це?

Дякую


Якщо можете, чи не могли б ви оновити вибрану відповідь до тієї, що має найбільшу кількість голосів, тобто тієї, яка перевіряє .window (за допомогою walkbrad), оскільки відповідь, яка перевіряє .superview(від mahboudz), технічно не правильна і викликала у мене помилки .
Альберт Реншоу,

Відповіді:


78

Ви можете перевірити, чи:

  • він прихований, перевіряючи view.hidden
  • він знаходиться в ієрархії подання, перевіряючи view.superview != nil
  • Ви можете перевірити межі подання, щоб побачити, чи є воно на екрані

Єдине, що я можу придумати, - це якщо ваш погляд схований за іншими, і з цієї причини його не видно. Можливо, вам доведеться пройти всі погляди, що з’являються після, щоб побачити, чи вони закривають ваш погляд.


Я зробив це таким чином, але забув опублікувати рішення тут :) Дякую (+1)
jantimon

Як ви здійснили перевірку невідомості?
Грег Комбс

1
Непросто. Перевірте межі всіх непрозорих дочірніх подань та відстежуйте частини свого перегляду, які не затьмарені, щоб перевірити на наступний дочірній підпрогляд. Щоб зробити це простіше, ви можете визначити кілька точок, і якщо ці точки видно, то припустимо, що ваш вигляд є. Ви можете вибрати, чи всі точки, які видно, є ознакою того, що ваш вид видно, чи будь-яка окрема видима точка задовольнить ваші потреби.
mahboudz

третій працює, навіть якщо UIView знаходиться всередині іншого UIView, який знаходиться всередині перегляду прокрутки?
Раду Сіміонеску

4
Я б додав до списку перевірку альфа
Джуліан Кроль

120

Для всіх, хто опиниться тут:

Щоб визначити, чи є UIView десь на екрані, а не перевіряти superview != nil, краще перевірити, чи є window != nil. У першому випадку можливо, що подання має суперперегляд, але що суперперегляд не відображається на екрані:

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

Звичайно, вам слід також перевірити, чи є це, hiddenабо воно має alpha > 0.

Щодо того, що ви не хочете, щоб ваш NSTimerзапуск працював, поки подання не видно, вам слід приховати ці подання вручну, якщо це можливо, і зупинити таймер, коли вигляд приховано. Однак я зовсім не впевнений у тому, що ти робиш.


ай карамба! саме те, що мені потрібно було, щоб убити циклічну анімацію
Антон Тропашко

2
На жаль, я проголосував за це, це не працює. Коли я роблю, po [self.view recursiveDescription]мої підпрограми відображаються в ієрархії подань, але їх view.windowзавжди нуль.
Logicsaurus Rex

@LogicsaurusRex Ви можете мати ієрархію подання, яка зараз не відображається у вікні (отже, чому я спочатку повторно відповів на запитання). У випадку, який ви тут описали, я думаю, це self.viewтакож має windowнуль. Так це? (вибачте за пізню відповідь)
walkbrad

Я купив би тобі пляшку вина;)
Bartłomiej Semańczyk

24

Це визначить, чи кадр подання знаходиться в межах усіх його суперпоглядів (до кореневого подання). Одним із практичних випадків використання є визначення того, чи є дочірній вигляд (принаймні частково) видимим у сувій.

Свіфт 5.x:

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

Старі швидкі версії

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)
}

Потенційні покращення:

  • Повага alphaі hidden.
  • Повага clipsToBounds, оскільки подання може перевищувати межі свого суперпрогляду, якщо воно хибне.

19

Рішення, яке працювало для мене, було спочатку перевірити, чи є у поданні вікно, а потім переглядати суперпрогляди та перевіряти, чи:

  1. вид не прихований.
  2. погляд знаходиться в межах його супервиглядів.

Здається, поки що добре працює.

Свіфт 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
}

Чудова відповідь. Це може не вдатися в UITableView та UIScrollView з того, що я можу сказати.
Рейд,

3

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

  • Чи не є вікно подання нульовим і дорівнює самому верхньому вікну
  • Чи являє собою подання та всі його суперперегляди альфа> = 0,01 (порогове значення, яке також використовує UIKit для визначення того, чи повинен він обробляти дотики), а не приховане
  • Чи є z-індекс (значення укладання) подання вищим, ніж інші представлення в тій самій ієрархії.
  • Навіть якщо z-індекс нижчий, це може бути видно, якщо інші види зверху мають прозорий колір фону, альфа 0 або приховані.

Особливо прозорий колір фону спереду може представляти проблему для програмної перевірки. Єдиний спосіб бути по-справжньому впевненим - зробити програмний знімок подання, щоб перевірити та відрізнити його у своєму кадрі зі знімком усього екрану. Однак це не буде працювати для поглядів, які недостатньо відмінні (наприклад, повністю білі).

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


3

Випробуваний розчин.

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
}

2

Для viewWillAppear встановіть для значення "isVisible" значення true, для viewWillDisappear - значення false. Найкращий спосіб дізнатись про підзаголовки UITabBarController, він також працює для контролерів навігації.


так, здається, це єдиний надійний спосіб
Антон Тропашко

2

Я порівняв свої рішення як Одрі М., так і @ Джон Гібб.
І @Audrey M. його шлях вийшов кращим (разів 10).
Тож я використав цей, щоб зробити його спостережуваним.

Я зробив RxSwift Observable, щоб отримувати сповіщення, коли UIView стає видимим.
Це може бути корисно, якщо ви хочете ініціювати подію банера "view"

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 let disposeBag = DisposeBag()

private func _checkBannerVisibility() {

    bannerView.rx.isVisibleToUser
        .filter { $0 }
        .take(1) // Only trigger it once
        .subscribe(onNext: { [weak self] _ in
            // ... Do something
        }).disposed(by: disposeBag)
}

1

Це може допомогти вам зрозуміти, чи ваш UIView є найвищим видом. Може бути корисним:

let visibleBool = view.superview?.subviews.last?.isEqual(view)
//have to check first whether it's nil (bc it's 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
}

0

спробуйте це:

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
}

0

Іншим корисним методом є didMoveToWindow() Приклад: Коли ви натискаєте контролер перегляду, подання вашого попереднього контролера перегляду викликатимуть цей метод. Перевірка self.window != nilвсередині сторінки didMoveToWindow()допомагає дізнатись, з’являється чи зникає ваш погляд з екрана.


-3

Якщо ви використовуєте приховане властивість перегляду:

view.hidden (об'єкт C) або view.isHidden (swift) - властивість читання / запису. Так ви зможете легко читати чи писати

Для швидкої 3.0

if(view.isHidden){
   print("Hidden")
}else{
   print("visible")
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.