确定UIView是否对用户可见?

jan*_*mon 69 iphone visibility uitabbarcontroller nstimer uiview

是否可以确定我UIView的用户是否可见?

我的视图被subview多次添加到一个Tab Bar Controller.

此视图的每个实例都有一个NSTimer更新视图的实例.

但是,我不想更新用户不可见的视图.

这可能吗?

谢谢

wal*_*rad 107

对于其他任何人来说:

要确定UIView是否在某个屏幕上,而不是检查superview != nil,最好检查是否window != nil.在前一种情况下,视图可能具有超级视图但超级视图不在屏幕上:

if (view.window != nil) {
    // do stuff
}
Run Code Online (Sandbox Code Playgroud)

当然你也应该检查它是否有,hidden或者是否有alpha > 0.

关于NSTimer在视图不可见时不想运行的情况,如果可能,应手动隐藏这些视图,并在隐藏视图时停止计时器.但是,我完全不确定你在做什么.

  • 对不起,我投票了,它不起作用.当我做'po [self.view recursiveDescription]时,我的子视图显示在视图层次结构中,但他们的`view.window`总是为零. (2认同)

mah*_*udz 74

您可以检查是否:

  • 通过检查view.hidden来隐藏它
  • 它是在视图层次结构中,通过检查 view.superview != nil
  • 您可以检查视图的边界以查看它是否在屏幕上

我能想到的唯一另一件事就是如果你的观点被埋没在其他人的背后,并且由于这个原因而无法看到.您可能需要查看之后的所有视图,看看它们是否模糊了您的观点.

  • 我会添加到列表中检查alpha (4认同)

Joh*_*ibb 19

这将确定视图的帧是否在其所有超级视图的范围内(直到根视图).一个实际用例是确定子视图是否(至少部分地)在滚动视图中可见.

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)
}
Run Code Online (Sandbox Code Playgroud)

潜在改进:

  • 尊重alphahidden.
  • 尊重clipsToBounds,如果视图可能超出其超级视图的界限,如果是错误的.


Ale*_*don 15

对我有用的解决方案是首先检查视图是否有窗口,然后迭代超视图并检查是否:

  1. 视图没有隐藏.
  2. 视图位于其超视图范围内.

到目前为止似乎运作良好.

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
}
Run Code Online (Sandbox Code Playgroud)


And*_* M. 5

经过测试的解决方案。

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
}
Run Code Online (Sandbox Code Playgroud)


bas*_*svk 5

我对@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()
    }
}
Run Code Online (Sandbox Code Playgroud)

像这样使用它:

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)
}
Run Code Online (Sandbox Code Playgroud)