是否可以确定我是否UIView
对用户可见?
我的视图被subview
多次添加到Tab Bar Controller
.
此视图的每个实例都有一个NSTimer
更新视图。
但是我不想更新用户看不到的视图。
这可能吗?
谢谢
是否可以确定我是否UIView
对用户可见?
我的视图被subview
多次添加到Tab Bar Controller
.
此视图的每个实例都有一个NSTimer
更新视图。
但是我不想更新用户看不到的视图。
这可能吗?
谢谢
对于在这里结束的其他任何人:
要确定 UIView 是否在屏幕上某处,而不是检查superview != nil
,最好检查 if window != nil
。在前一种情况下,视图可能有一个超级视图,但该超级视图不在屏幕上:
if (view.window != nil) {
// do stuff
}
当然,您还应该检查它是否存在hidden
或是否具有alpha > 0
.
关于不想NSTimer
在视图不可见时运行,如果可能,您应该手动隐藏这些视图,并在视图隐藏时停止计时器。但是,我完全不确定您在做什么。
您可以检查是否:
view.superview != nil
我唯一能想到的另一件事是,如果您的视图被隐藏在其他人之后并且因此而无法看到。您可能必须浏览所有随后出现的视图,看看它们是否会遮挡您的视图。
这将确定视图的框架是否在其所有超级视图的范围内(直到根视图)。一个实际用例是确定子视图是否(至少部分)在滚动视图中可见。
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)
}
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
,因为如果为假,视图可能会超出其父视图的范围。对我有用的解决方案是首先检查视图是否有窗口,然后遍历超级视图并检查是否:
到目前为止似乎运作良好。
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
}
我对@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)
}
经测试的解决方案。
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
}
我你真的想知道视图是否对用户可见,你必须考虑以下几点:
尤其是前面视图的透明背景颜色可能会给以编程方式检查带来问题。真正确定的唯一方法是制作视图的编程快照,以在其框架内与整个屏幕的快照进行检查和比较。然而,这不适用于不够独特的视图(例如全白)。
有关灵感,请参阅iOS Calabash-server 项目中的方法 isViewVisible
我能想出的最简单的 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
}
}
另一个有用的方法是didMoveToWindow()
示例:当您推送视图控制器时,您之前的视图控制器的视图将调用此方法。检查self.window != nil
内部didMoveToWindow()
有助于了解您的视图是出现在屏幕上还是从屏幕上消失。
在 viewWillAppear 中将值“isVisible”设置为 true,在 viewWillDisappear 中将其设置为 false。了解 UITabBarController 子视图的最佳方法也适用于导航控制器。
这可以帮助您确定您的 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
}
尝试这个:
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
}
如果您使用 view 的 hidden 属性,则:
view.hidden (objective C) 或 view.isHidden(swift) 是读/写属性。这样您就可以轻松地阅读或书写
对于迅捷3.0
if(view.isHidden){
print("Hidden")
}else{
print("visible")
}