Feststellen, ob UIView für den Benutzer sichtbar ist?

78

Kann festgestellt werden, ob meine UIViewfür den Benutzer sichtbar ist oder nicht?

Meine Ansicht wird subviewmehrmals in eine hinzugefügt Tab Bar Controller.

Jede Instanz dieser Ansicht verfügt über eine NSTimer, die die Ansicht aktualisiert.

Ich möchte jedoch keine Ansicht aktualisieren, die für den Benutzer nicht sichtbar ist.

Ist das möglich?

Vielen Dank

jantimon
quelle
Wenn Sie können, würden Sie in Betracht ziehen, Ihre ausgewählte Antwort auf die mit den meisten positiven Stimmen zu .window aktualisieren, dh die Antwort, die überprüft (von walkbrad), da die Antwort, die überprüft wird .superview(von mahboudz), technisch nicht korrekt ist und Fehler für mich verursacht hat .
Albert Renshaw

Antworten:

77

Sie können überprüfen, ob:

  • Es wird ausgeblendet, indem Sie view.hidden aktivieren
  • Es befindet sich in der Ansichtshierarchie, indem überprüft wird view.superview != nil
  • Sie können die Grenzen einer Ansicht überprüfen, um festzustellen, ob sie auf dem Bildschirm angezeigt wird

Das einzige andere, woran ich denken kann, ist, wenn Ihre Sicht hinter anderen verborgen ist und aus diesem Grund nicht gesehen werden kann. Möglicherweise müssen Sie alle nachfolgenden Ansichten durchgehen, um festzustellen, ob sie Ihre Ansicht verdecken.

Mahboudz
quelle
Ich habe das so gemacht, aber vergessen, die Lösung hier zu posten :) Danke (+1)
jantimon
Wie haben Sie Ihre Dunkelheitsprüfung durchgeführt?
Greg Combs
1
Nicht einfach. Überprüfen Sie die Grenzen aller undurchsichtigen untergeordneten Ansichten und verfolgen Sie Teile Ihrer Ansicht, die nicht verdeckt sind, um sie mit der nächsten untergeordneten Unteransicht zu vergleichen. Zur Vereinfachung möchten Sie möglicherweise einige Punkte definieren. Wenn diese Punkte sichtbar sind, nehmen Sie an, dass Ihre Ansicht so ist. Sie können wählen, ob alle sichtbaren Punkte ein Hinweis darauf sind, dass Ihre Ansicht sichtbar ist, oder ob ein einzelner sichtbarer Punkt Ihren Anforderungen entspricht.
Mahboudz
Das dritte funktioniert auch, wenn sich UIView in einem anderen UIView befindet, das sich in einer Bildlaufansicht befindet.
Radu Simionescu
4
Ich würde der Liste hinzufügen, die ein Alpha prüft
Julian Król
120

Für alle anderen, die hier landen:

Um festzustellen, ob ein UIView irgendwo auf dem Bildschirm angezeigt wird, anstatt zu überprüfen superview != nil, sollten Sie überprüfen, ob window != nil. Im ersteren Fall ist es möglich, dass die Ansicht eine Übersicht hat, die Übersicht jedoch nicht auf dem Bildschirm angezeigt wird:

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

Natürlich sollten Sie auch prüfen, ob es eine ist hiddenoder ob es eine hat alpha > 0.

NSTimerWenn Sie nicht möchten, dass Sie ausgeführt werden, während die Ansicht nicht sichtbar ist, sollten Sie diese Ansichten nach Möglichkeit manuell ausblenden und den Timer anhalten lassen, wenn die Ansicht ausgeblendet ist. Ich bin mir jedoch überhaupt nicht sicher, was Sie tun.

Walkingbrad
quelle
ay caramba! genau das, was ich brauchte, um die zyklischen Animationen zu töten
Anton Tropashko
2
Entschuldigung, ich habe dafür gestimmt, es funktioniert nicht. Wenn ich das mache, werden po [self.view recursiveDescription]meine Unteransichten in der Ansichtshierarchie angezeigt, aber sie view.windowsind immer gleich Null.
Logicsaurus Rex
@LogicsaurusRex Sie können eine Ansichtshierarchie haben, die derzeit nicht im Fenster angezeigt wird (daher habe ich die Frage ursprünglich erneut beantwortet). In dem Fall, den Sie hier skizziert haben, stelle ich mir vor, dass dies self.viewauch eine windowNull ist. Ist das der Fall? (Entschuldigung für die späte Antwort)
Walkingbrad
Ich würde dir eine Flasche Wein kaufen;)
Bartłomiej Semańczyk
22

Dadurch wird bestimmt, ob sich der Rahmen einer Ansicht innerhalb der Grenzen aller ihrer Übersichten befindet (bis zur Stammansicht). Ein praktischer Anwendungsfall besteht darin, zu bestimmen, ob eine untergeordnete Ansicht (zumindest teilweise) in einer Bildlaufansicht sichtbar ist.

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

Ältere schnelle Versionen

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

Mögliche Verbesserungen:

  • Respekt alphaund hidden.
  • Respekt clipsToBounds, da eine Ansicht die Grenzen ihrer Übersicht überschreiten kann, wenn sie falsch ist.
John Gibb
quelle
19

Die Lösung, die für mich funktioniert hat, bestand darin, zuerst zu überprüfen, ob die Ansicht ein Fenster hat, dann über Übersichten zu iterieren und zu überprüfen, ob:

  1. Die Ansicht ist nicht verborgen.
  2. Die Ansicht befindet sich innerhalb der Grenzen der Übersicht.

Scheint bisher gut zu funktionieren.

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
}
AlexGordon
quelle
Tolle Antwort. Dies kann in UITableView und UIScrollView fehlschlagen, soweit ich das beurteilen kann.
Reid
3

Wenn Sie wirklich wissen möchten, ob eine Ansicht für den Benutzer sichtbar ist, müssen Sie Folgendes berücksichtigen:

  • Ist das Fenster der Ansicht nicht gleich Null und entspricht dem obersten Fenster?
  • Ist die Ansicht und alle ihre Übersichten alpha> = 0,01 (Schwellenwert wird auch von UIKit verwendet, um zu bestimmen, ob sie mit Berührungen umgehen soll) und nicht ausgeblendet
  • Ist der Z-Index (Stapelwert) der Ansicht höher als bei anderen Ansichten in derselben Hierarchie?
  • Selbst wenn der Z-Index niedriger ist, kann er sichtbar sein, wenn andere Ansichten oben eine transparente Hintergrundfarbe (Alpha 0) haben oder ausgeblendet sind.

Insbesondere die transparente Hintergrundfarbe der vorderen Ansichten kann ein Problem darstellen, das programmgesteuert überprüft werden muss. Die einzige Möglichkeit, wirklich sicher zu sein, besteht darin, einen programmgesteuerten Schnappschuss der Ansicht zu erstellen, um sie zu überprüfen und innerhalb ihres Rahmens mit dem Schnappschuss des gesamten Bildschirms zu unterscheiden. Dies funktioniert jedoch nicht für Ansichten, die nicht unterscheidungskräftig genug sind (z. B. vollständig weiß).

Inspiration finden Sie unter der Methode isViewVisible im iOS Calabash-Server-Projekt

Werner Altewischer
quelle
3

Getestete Lösung.

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
}
Andrey M.
quelle
2

In viewWillAppear setzen Sie einen Wert "isVisible" auf true, in viewWillDisappear auf false. Der beste Weg, um für einen UITabBarController Unteransichten zu wissen, funktioniert auch für Navigations-Controller.

BadPirate
quelle
Ja, es scheint der einzig verlässliche Weg zu sein
Anton Tropashko
2

Ich habe sowohl @Audrey M. als auch @John Gibb ihre Lösungen verglichen.
Und @Audrey M. sein Weg schnitt besser ab (mal 10).
Also habe ich das benutzt, um es sichtbar zu machen.

Ich habe ein RxSwift Observable erstellt, um benachrichtigt zu werden, wenn das UIView sichtbar wird.
Dies kann nützlich sein, wenn Sie ein Banner-Ansichtsereignis auslösen möchten

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

Verwenden Sie es so:

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)
}
basvk
quelle
1

Auf diese Weise können Sie herausfinden, ob Ihre UIView die oberste Ansicht ist. Kann hilfreich sein:

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
}
Teradyl
quelle
0

Versuche dies:

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
}
Feng Chengjing
quelle
0

Eine weitere nützliche Methode ist didMoveToWindow() Beispiel: Wenn Sie den View Controller drücken, rufen Ansichten Ihres vorherigen View Controllers diese Methode auf. Wenn Sie self.window != nilin didMoveToWindow()überprüfen, können Sie feststellen, ob Ihre Ansicht auf dem Bildschirm angezeigt wird oder nicht.

George Sabanov
quelle
-3

Wenn Sie eine versteckte Ansichtseigenschaft verwenden, gehen Sie wie folgt vor:

view.hidden (Ziel C) oder view.isHidden (schnell) ist eine Lese- / Schreibeigenschaft. So können Sie leicht lesen oder schreiben

Für schnelle 3.0

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