Determine if UIView is visible to the user?

前端 未结 12 1758
不知归路
不知归路 2020-12-07 15:21

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

My View is added as subview several times into a Tab Bar

相关标签:
12条回答
  • 2020-12-07 16:00

    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)
            .flatMap { [base] _ in
                return Observable.just(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)
    }
    
    0 讨论(0)
  • 2020-12-07 16:06

    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
    }
    
    0 讨论(0)
  • 2020-12-07 16:07

    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.

    0 讨论(0)
  • 2020-12-07 16:11

    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")
    }
    
    0 讨论(0)
  • 2020-12-07 16:12

    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.

    0 讨论(0)
  • 2020-12-07 16:12

    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

    0 讨论(0)
提交回复
热议问题