UIView가 사용자에게 표시되는지 확인 하시겠습니까?


78

내 정보 UIView가 사용자에게 표시 되는지 여부를 결정할 수 있습니까?

내보기로 추가 subview에 여러 번 Tab Bar Controller.

이보기의 각 인스턴스에는보기 NSTimer를 업데이트하는가 있습니다.

그러나 사용자에게 표시되지 않는보기를 업데이트하고 싶지 않습니다.

이것이 가능한가?

감사


가능하다면, .window (mahboudz에 의해) 확인하는 대답이 .superview기술적으로 정확하지 않고 저에게 버그를 일으켰 기 때문에 가장 많은 찬성 투표를 한 대답 (walkingbrad에 의해) 에 대해 선택한 대답을 업데이트하는 것을 고려 하시겠습니까 ? .
Albert Renshaw

답변:


78

다음 사항을 확인할 수 있습니다.

  • view.hidden을 확인하면 숨겨집니다.
  • 뷰 계층 구조에 있습니다. view.superview != nil
  • 보기의 경계를 확인하여 화면에 있는지 확인할 수 있습니다.

내가 생각할 수있는 유일한 다른 것은 당신의 견해가 다른 사람들 뒤에 묻혀서 그 이유로 보이지 않는 경우입니다. 당신은 그들이 당신의 시야를 가리는지 확인하기 위해 이후에 오는 모든 견해를 검토해야 할 수도 있습니다.


나는이 방법을했지만 여기에 솔루션 : 감사합니다 (+1) 게시하는 것을 잊었다
jantimon

모호성 검사를 어떻게 수행 했습니까?
Greg Combs

1
쉽지 않습니다. 모든 불투명 한 하위 뷰의 경계를 확인하고 다음 하위 하위 뷰에 대해 확인하기 위해 가려지지 않은 뷰 부분을 추적합니다. 더 쉽게하기 위해 몇 개의 점을 정의하고 해당 점이 표시되는 경우보기가 있다고 가정 할 수 있습니다. 표시되는 모든 포인트가 뷰가 표시됨을 나타내는 지 또는 보이는 단일 포인트가 요구 사항을 충족하는지 여부를 선택할 수 있습니다.
mahboudz 2011-08-04

세 번째 것은 UIView가 스크롤 뷰 안에있는 다른 UIView 안에 있더라도 작동합니까?
Radu Simionescu

4
나는 알파를 점검 목록에 추가 할 수
줄리안 크롤

120

여기서 끝나는 다른 사람을 위해 :

UIView의 오히려 검사보다 화면 어딘가에 있는지 확인하려면 superview != nil,이 있는지 확인하는 것이 좋습니다 window != nil. 전자의 경우 뷰에 수퍼 뷰가 있지만 수퍼 뷰가 화면에 없을 수 있습니다.

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

물론 그것이 있는지 hidden또는 alpha > 0.

NSTimer보기가 표시되지 않는 동안 실행하는 것을 원하지 않는 경우 가능하면 이러한보기를 수동으로 숨기고보기가 숨겨 질 때 타이머가 중지되도록해야합니다. 그러나 나는 당신이 무엇을하는지 전혀 확신하지 못합니다.


ay caramba! 나는 순환 애니메이션 죽일 필요 그냥 뭐
안톤 Tropashko을

2
투표해서 미안하지만 작동하지 않습니다. 내가 할 때 po [self.view recursiveDescription]내 하위보기는보기 계층 구조에 표시되지만 view.window항상 nil입니다.
Logicsaurus 렉스

@LogicsaurusRex 현재 창에 표시되지 않는 뷰 계층 구조를 가질 수 있습니다 (따라서 원래 질문에 다시 대답 한 이유). 여기에 요약 한 경우 self.view에도 a window가 nil 이라고 생각합니다 . 그럴까요? (늦은 답변 죄송합니다)
walkingbrad

내가 당신에게 와인 한 병을 살 것)
Bartłomiej Semańczyk

25

이것은 뷰의 프레임이 모든 수퍼 뷰의 경계 내에 있는지 (루트 뷰까지) 결정합니다. 실제 사용 사례 중 하나는 스크롤 뷰 내에서 하위 뷰가 (적어도 부분적으로) 표시되는지 확인하는 것입니다.

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뷰가 거짓 인 경우 수퍼 뷰의 경계를 초과 할 수 있으므로 존중하십시오 .

완전한. 스크롤보기의 내용으로 스택보기가 있으며 array 된 Subview 중 (적어도 부분적으로) 표시되는 것이 정확히 내가 찾던 것입니다.
Craig Pickering

19

나를 위해 일한 해결책은 먼저 뷰에 창이 있는지 확인한 다음 수퍼 뷰를 반복하고 다음 사항을 확인하는 것입니다.

  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
}

멋진 대답입니다. 이것은 내가 말할 수있는 UITableView 및 UIScrollView에서 실패 할 수 있습니다.
Reid

3

보기가 사용자에게 표시되는지 진정으로 알고 싶습니다. 다음 사항을 고려해야합니다.

  • 보기의 창이 nil이 아니고 가장 상단의 창과 동일합니까?
  • 뷰이며 모든 수퍼 뷰 알파> = 0.01 (UIKit에서 터치를 처리해야하는지 여부를 결정하는 데 사용되는 임계 값)이며 숨겨지지 않습니다.
  • 동일한 계층 구조에서 다른보기보다 높은보기의 Z- 색인 (누적 값)입니다.
  • Z- 색인이 더 낮더라도 상단의 다른보기에 투명한 배경색 인 알파 0이 있거나 숨겨져 있으면 볼 수 있습니다.

특히 전면 뷰의 투명한 배경색은 프로그래밍 방식으로 확인하는 데 문제가 될 수 있습니다. 진정으로 확신하는 유일한 방법은 전체 화면의 스냅 샷과 함께 프레임 내에서 확인하고 비교하기 위해 뷰의 프로그래밍 방식 스냅 샷을 만드는 것입니다. 그러나 충분히 뚜렷하지 않은 뷰 (예 : 완전히 흰색)에는 작동하지 않습니다.

영감을 얻으려면 iOS Calabash-server 프로젝트의 isViewVisible 메소드를 참조하십시오.


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
}

2

viewWillAppear에서 "isVisible"값을 true로 설정하고 viewWillDisappear에서 false로 설정합니다. UITabBarController 하위보기를 알 수있는 가장 좋은 방법은 탐색 컨트롤러에서도 작동합니다.


예, 이것이 유일한 신뢰할 수있는 방법 인 것 같습니다
Anton

2

@Audrey M.과 ​​@John Gibb의 솔루션을 벤치마킹했습니다.
그리고 @Audrey M. 그의 방식이 더 잘 수행되었습니다 (10 번).
그래서 나는 그것을 관찰 가능하게 만들기 위해 그것을 사용했습니다.

UIView가 표시 될 때 알림을 받기 위해 RxSwift Observable을 만들었습니다.
배너 '보기'이벤트를 트리거하려는 경우 유용 할 수 있습니다.

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

다음과 같이 사용하십시오.

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

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
}

0

또 다른 유용한 방법은 didMoveToWindow() 예제입니다. 뷰 컨트롤러를 푸시하면 이전 뷰 컨트롤러의 뷰가이 메서드를 호출합니다. self.window != nil내부를 확인 didMoveToWindow()하면보기가 화면에 나타나거나 사라지는 지 여부를 알 수 있습니다.


-3

숨겨진 속성을 사용하는 경우 다음을 수행하십시오.

view.hidden (objective C) 또는 view.isHidden (swift)은 읽기 / 쓰기 속성입니다. 따라서 쉽게 읽고 쓸 수 있습니다.

신속한 3.0 용

if(view.isHidden){
   print("Hidden")
}else{
   print("visible")
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.