判断UIView是否对用户可见?

发布于 2024-08-06 13:46:22 字数 199 浏览 5 评论 0原文

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

我的视图多次作为子视图添加到UITabBarController中。

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

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

这可能吗?

Is it possible to determine whether my UIView is visible to the user or not?

My View is added as a subview several times into a UITabBarController.

Each instance of this view has an NSTimer that updates the view.

However, I don't want to update a view which is not visible to the user.

Is this possible?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

节枝 2024-08-13 13:46:22

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

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

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

当然,您还应该检查它是否隐藏或是否有 alpha > ; 0 。

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

For anyone else that ends up here:

To determine if a UIView is onscreen somewhere, rather than checking superview != nil, it is better to check if window != nil. In the former case, it is possible that the view has a superview but that the superview is not on screen:

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

Of course you should also check if it is hidden or if it has an alpha > 0.

Regarding not wanting your NSTimer running while the view is not visible, you should hide these views manually if possible and have the timer stop when the view is hidden. However, I'm not at all sure of what you're doing.

烟雨扶苏 2024-08-13 13:46:22

您可以检查是否:

  • 它是隐藏的,通过检查 view.hidden
  • 它在视图层次结构中,通过检查 view.superview != nil
  • 您可以检查视图的边界以查看它是否处于打开状态screen

我唯一能想到的另一件事是,如果您的视图被埋在其他人后面并且因此而无法被看到。您可能必须仔细检查随后出现的所有视图,看看它们是否遮挡了您的视图。

You can check if:

  • it is hidden, by checking view.hidden
  • it is in the view hierarchy, by checking view.superview != nil
  • you can check the bounds of a view to see if it is on screen

The only other thing I can think of is if your view is buried behind others and can't be seen for that reason. You may have to go through all the views that come after to see if they obscure your view.

深空失忆 2024-08-13 13:46:22

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

Swift 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,因为如果为 false,视图可能会超出其父视图的边界。

This will determine if a view's frame is within the bounds of all of its superviews (up to the root view). One practical use case is determining if a child view is (at least partially) visible within a scrollview.

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

Older swift versions

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

Potential improvements:

  • Respect alpha and hidden.
  • Respect clipsToBounds, as a view may exceed the bounds of its superview if false.
歌枕肩 2024-08-13 13:46:22

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

  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
}

The solution that worked for me was to first check if the view has a window, then to iterate over superviews and check if:

  1. the view is not hidden.
  2. the view is within its superviews bounds.

Seems to work well so far.

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
}
南街九尾狐 2024-08-13 13:46:22

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

I benchmarked both @Audrey M. and @John Gibb their solutions.
And @Audrey M. his way performed better (times 10).
So I used that one to make it observable.

I made a RxSwift Observable, to get notified when the UIView became visible.
This could be useful if you want to trigger a banner 'view' event

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

Use it as like this:

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)
}
白日梦 2024-08-13 13:46:22

经过测试的解决方案。

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
}

Tested solution.

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
}
╭⌒浅淡时光〆 2024-08-13 13:46:22

我真的想知道视图是否对用户可见,您必须考虑以下事项:

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

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

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

I you truly want to know if a view is visible to the user you would have to take into account the following:

  • Is the view's window not nil and equal to the top most window
  • Is the view, and all of its superviews alpha >= 0.01 (threshold value also used by UIKit to determine whether it should handle touches) and not hidden
  • Is the z-index (stacking value) of the view higher than other views in the same hierarchy.
  • Even if the z-index is lower, it can be visible if other views on top have a transparent background color, alpha 0 or are hidden.

Especially the transparent background color of views in front may pose a problem to check programmatically. The only way to be truly sure is to make a programmatic snapshot of the view to check and diff it within its frame with the snapshot of the entire screen. This won't work however for views that are not distinctive enough (e.g. fully white).

For inspiration see the method isViewVisible in the iOS Calabash-server project

如歌彻婉言 2024-08-13 13:46:22

我能想到的最简单的 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
        }
    }

The simplest Swift 5 solution I could come up with that worked in my situation (I was looking for a button embedded in my tableViewFooter).

John Gibbs solution also worked but in my cause I did not need all the recursion.

    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
        }
    }
疧_╮線 2024-08-13 13:46:22

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

In viewWillAppear set a value "isVisible" to true, in viewWillDisappear set it to false. Best way to know for a UITabBarController subviews, also works for navigation controllers.

樱花落人离去 2024-08-13 13:46:22

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

Another useful method is didMoveToWindow()
Example: When you push view controller, views of your previous view controller will call this method. Checking self.window != nil inside of didMoveToWindow() helps to know whether your view is appearing or disappearing from the screen.

夏尔 2024-08-13 13:46:22

这可以帮助您确定您的 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
}

This can help you figure out if your UIView is the top-most view. Can be helpful:

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
}
清风不识月 2024-08-13 13:46:22

试试这个:

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
}

try this:

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
}
风向决定发型 2024-08-13 13:46:22

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

view.hidden(目标C)或view.isHidden(swift)是读/写属性。这样你就可以轻松地读取或写入

For swift 3.0

if(view.isHidden){
   print("Hidden")
}else{
   print("visible")
}

In case you are using hidden property of view then :

view.hidden (objective C) or view.isHidden(swift) is read/write property. So you can easily read or write

For swift 3.0

if(view.isHidden){
   print("Hidden")
}else{
   print("visible")
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文