判断UIView是否对用户可见?

问题描述 投票:0回答:13

是否可以确定我的

UIView
是否对用户可见?

我的视图多次作为

subview
添加到
UITabBarController
中。

此视图的每个实例都有一个用于更新视图的

NSTimer

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

这可能吗?

iphone uiview uitabbarcontroller visibility nstimer
13个回答
135
投票

对于最终到达这里的其他人:

要确定 UIView 是否在屏幕上的某个位置,与其检查

superview != nil
,不如检查是否
window != nil
。在前一种情况下,视图可能有一个超级视图,但超级视图不在屏幕上:

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

当然你也应该检查它是否是

hidden
或者是否有
alpha > 0

关于不希望在视图不可见时运行

NSTimer
,您应该尽可能手动隐藏这些视图,并在视图隐藏时停止计时器。但是,我完全不确定你在做什么。


82
投票

您可以检查是否:

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

我唯一能想到的另一件事是,如果你的观点被埋在别人后面并且因此而无法被看到。您可能需要仔细检查之后出现的所有视图,看看它们是否遮挡了您的视图。


40
投票

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

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

潜在的改进:

  • 尊重
    alpha
    hidden
  • 尊重
    clipsToBounds
    ,因为如果为 false,视图可能会超出其超级视图的边界。

21
投票

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

  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
}

8
投票

我对@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)
}

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
}

3
投票

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

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

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

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


3
投票

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

约翰吉布斯解决方案也有效,但在我看来,我不需要所有递归。

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

2
投票

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


2
投票

另一个有用的方法是

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


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
}

-4
投票

如果您使用视图的隐藏属性,则:

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

适用于 swift 3.0

if(view.isHidden){
   print("Hidden")
}else{
   print("visible")
}
© www.soinside.com 2019 - 2024. All rights reserved.