87

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

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

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

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

这可能吗?

谢谢

4

13 回答 13

131

对于在这里结束的其他任何人:

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

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

当然,您还应该检查它是否存在hidden或是否具有alpha > 0.

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

于 2013-11-12T21:26:42.603 回答
82

您可以检查是否:

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

我唯一能想到的另一件事是,如果您的视图被隐藏在其他人之后并且因此而无法看到。您可能必须浏览所有随后出现的视图,看看它们是否会遮挡您的视图。

于 2009-10-11T07:19:07.120 回答
36

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

斯威夫特 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)
}

较旧的 swift 版本

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,因为如果为假,视图可能会超出其父视图的范围。
于 2016-01-06T20:15:21.673 回答
20

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

  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
}
于 2017-07-14T13:06:23.013 回答
7

我对@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)
        .map { [base] _ in
            return 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)
}
于 2019-10-10T07:57:00.550 回答
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
}
于 2019-08-06T11:30:09.523 回答
3

我你真的想知道视图是否对用户可见,你必须考虑以下几点:

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

尤其是前面视图的透明背景颜色可能会给以编程方式检查带来问题。真正确定的唯一方法是制作视图的编程快照,以在其框架内与整个屏幕的快照进行检查和比较。然而,这不适用于不够独特的视图(例如全白)。

有关灵感,请参阅iOS Calabash-server 项目中的方法 isViewVisible

于 2016-11-07T12:04:13.883 回答
2

我能想出的最简单的 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
        }
    }
于 2021-02-10T15:22:43.357 回答
2

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

于 2020-05-16T19:11:49.277 回答
2

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

于 2016-01-26T18:34:15.837 回答
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
}
于 2016-04-26T20:53:39.073 回答
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
}
于 2019-03-13T10:35:46.153 回答
-4

如果您使用 view 的 hidden 属性,则:

view.hidden (objective C) 或 view.isHidden(swift) 是读/写属性。这样您就可以轻松地阅读或书写

对于迅捷3.0

if(view.isHidden){
   print("Hidden")
}else{
   print("visible")
}
于 2017-05-08T12:17:11.857 回答