web-dev-qa-db-fra.com

Déterminer si UIView est visible pour l'utilisateur?

est-il possible de déterminer si mon UIView est visible ou non pour l'utilisateur?

Ma vue est ajoutée en tant que subview plusieurs fois dans un Tab Bar Controller.

Chaque instance de cette vue a un NSTimer qui met à jour la vue.

Cependant, je ne veux pas mettre à jour une vue qui n'est pas visible pour l'utilisateur.

Est-ce possible?

Merci

66
jantimon

Vous pouvez vérifier si:

  • il est caché, en vérifiant view.hidden
  • il est dans la hiérarchie des vues, en cochant view.superview != nil
  • vous pouvez vérifier les limites d'une vue pour voir si elle est à l'écran

La seule autre chose à laquelle je peux penser, c'est si votre vue est enfouie derrière les autres et ne peut pas être vue pour cette raison. Vous devrez peut-être parcourir toutes les vues qui suivent pour voir si elles obscurcissent votre vue.

73
mahboudz

Pour toute autre personne qui se retrouve ici:

Pour déterminer si une UIView est à l'écran quelque part, plutôt que de vérifier superview != nil, il vaut mieux vérifier si window != nil. Dans le premier cas, il est possible que la vue ait une vue d'ensemble mais que la vue d'ensemble ne soit pas à l'écran:

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

Bien sûr, vous devez également vérifier s'il s'agit de hidden ou s'il a un alpha > 0.

En ce qui concerne le fait de ne pas vouloir que votre NSTimer s'exécute alors que la vue n'est pas visible, vous devez masquer ces vues manuellement si possible et faire arrêter la minuterie lorsque la vue est masquée. Cependant, je ne suis pas du tout sûr de ce que vous faites.

100
walkingbrad

Cela déterminera si le cadre d'une vue est dans les limites de tous ses superviews (jusqu'à la vue racine). Un cas d'utilisation pratique consiste à déterminer si une vue enfant est (au moins partiellement) visible dans une vue de défilement.

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

Améliorations potentielles:

  • Respectez alpha et hidden.
  • Respectez clipsToBounds, car une vue peut dépasser les limites de sa vue d'ensemble si elle est fausse.
18
John Gibb

La solution qui a fonctionné pour moi a été de vérifier d'abord si la vue a une fenêtre, puis de parcourir les superviews et de vérifier si:

  1. la vue n'est pas cachée.
  2. la vue est dans ses limites de superviews.

Semble bien fonctionner jusqu'à présent.

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
}
15
AlexGordon

Si vous voulez vraiment savoir si une vue est visible pour l'utilisateur, vous devez prendre en compte les éléments suivants:

  • La fenêtre de la vue n'est-elle pas nulle et égale à la fenêtre la plus haute
  • La vue et tous ses superviews sont-elles alpha> = 0,01 (valeur de seuil également utilisée par UIKit pour déterminer si elle doit gérer les touches) et non masquée
  • Le z-index (valeur d'empilement) de la vue est-il supérieur à celui des autres vues de la même hiérarchie.
  • Même si l'indice z est inférieur, il peut être visible si d'autres vues du dessus ont une couleur d'arrière-plan transparente, alpha 0 ou sont masquées.

En particulier, la couleur d'arrière-plan transparente des vues de devant peut poser un problème à vérifier par programme. La seule façon d'être vraiment sûr est de faire un instantané programmatique de la vue pour la vérifier et la différencier dans son cadre avec l'instantané de tout l'écran. Cela ne fonctionnera cependant pas pour les vues qui ne sont pas suffisamment distinctives (par exemple, entièrement blanches).

Pour l'inspiration, voir la méthode isViewVisible dans le projet iOS Calabash-server

3

Dans viewWillAppear, définissez une valeur "isVisible" sur true, dans viewWillDisappear, définissez-la sur false. La meilleure façon de savoir pour une sous-vue UITabBarController, fonctionne également pour les contrôleurs de navigation.

2
BadPirate

Cela peut vous aider à déterminer si votre UIView est la vue la plus haute. Peut être utile:

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
}
1
teradyl

Si vous utilisez une propriété de vue cachée, alors:

view.hidden (Objective C) ou view.isHidden (Swift) est une propriété en lecture/écriture. Vous pouvez donc facilement lire ou écrire

Pour Swift 3.

if(view.isHidden){
   print("Hidden")
}else{
   print("visible")
}
0

Solution testée.

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

essaye ça:

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
Feng Chengjing