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

잠재적 개선 :

  • 존경 alpha과 hidden.
  • 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.