确定UIView是否对用户可见?

时间:2009-10-08 10:24:24

标签: iphone uiview uitabbarcontroller visibility nstimer

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

我的观看次数已subview多次添加到Tab Bar Controller

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

但是我不想更新用户看不到的视图。

这可能吗?

由于

12 个答案:

答案 0 :(得分:98)

对于其他任何人来说:

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

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

当然,您还应该检查它是hidden还是alpha > 0

如果在视图不可见时不希望您的NSTimer正在运行,则应尽可能手动隐藏这些视图,并在隐藏视图时停止计时器。但是,我完全不确定你在做什么。

答案 1 :(得分:72)

您可以查看是否:

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

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

答案 2 :(得分:18)

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

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

潜在的改进:

  • 尊重alphahidden
  • 尊重clipsToBounds,因为如果视图错误,视图可能会超出其超级视图的范围。

答案 3 :(得分:14)

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

  1. 视图未隐藏。
  2. 视图位于其超视图范围内。
  3. 到目前为止似乎运作良好。

    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
    }
    

答案 4 :(得分:3)

我真的想知道用户是否可以看到某个视图,您必须考虑以下因素:

  • 视图的窗口是否为nil并且等于最顶层的窗口
  • 是视图及其所有超级视图alpha> = 0.01(UIKit也使用阈值来确定是否应该处理触摸)而不是隐藏
  • 视图的z-index(堆叠值)是否高于同一层次结构中的其他视图。
  • 即使z-index较低,如果顶部的其他视图具有透明背景颜色,alpha 0或隐藏,则可以看到它。

特别是前面视图的透明背景颜色可能会成为以编程方式检查的问题。唯一真正确定的方法是制作视图的程序化快照,以便使用整个屏幕的快照在其框架内检查和区分它。但是,对于不够独特的视图(例如全白),这不起作用。

有关灵感,请参阅iOS Calabash-server project

中的方法isViewVisible

答案 5 :(得分:2)

在viewWillAppear中设置一个值" isVisible"为true,在viewWillDisappear中将其设置为false。了解UITabBarController子视图的最佳方法,也适用于导航控制器。

答案 6 :(得分:2)

经过测试的解决方案。

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
}

答案 7 :(得分:1)

这可以帮助您确定您的UIView是否是最顶层的视图。可以提供帮助:

{{1}}

答案 8 :(得分:1)

我能想到的最简单的 Swift 5 解决方案适用于我的情况(我正在寻找一个嵌入在我的 tableViewFooter 中的按钮)。

John Gibbs 解决方案也有效,但在我的事业中我不需要所有的递归。

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let viewFrame = scrollView.convert(targetView.bounds, from: targetView)
        if viewFrame.intersects(scrollView.bounds) {
            // targetView is visible 
        }
        else {
            // targetView is not visible
        }
    }

答案 9 :(得分: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
}

答案 10 :(得分:0)

我对@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 :(得分:0)

另一种有用的方法是didMoveToWindow() 示例:当您推送视图控制器时,先前的视图控制器的视图将调用此方法。在self.window != nil中选中didMoveToWindow()有助于了解您的视图是从屏幕上出现还是从屏幕上消失。