Supprimer toutes les sous-vues?

316

Lorsque mon application revient à son contrôleur de vue racine, dans la viewDidAppear:méthode, je dois supprimer toutes les sous-vues.

Comment puis-je faire ceci?

Ian Vink
la source

Réponses:

569

Edit: Avec merci à cocoafan : Cette situation est embrouillé par le fait que NSViewet UIViewgérer les choses différemment. Pour NSView(développement Mac de bureau uniquement), vous pouvez simplement utiliser ce qui suit:

[someNSView setSubviews:[NSArray array]];

Pour UIView(développement iOS uniquement), vous pouvez l'utiliser en toute sécurité makeObjectsPerformSelector:car la subviewspropriété renverra une copie du tableau de sous-vues:

[[someUIView subviews]
 makeObjectsPerformSelector:@selector(removeFromSuperview)];

Merci à Tommy d' avoir signalé que cela makeObjectsPerformSelector:semble modifier le subviewstableau pendant son énumération (ce qu'il fait pour NSView, mais pas pour UIView).

Veuillez consulter cette question SO pour plus de détails.

Remarque: L' utilisation de l'une de ces deux méthodes supprimera toutes les vues que contient votre vue principale et les libérera si elles ne sont pas conservées ailleurs. De la documentation d'Apple sur removeFromSuperview :

Si la vue d'ensemble du récepteur n'est pas nulle, cette méthode libère le récepteur. Si vous prévoyez de réutiliser la vue, assurez-vous de la conserver avant d'appeler cette méthode et assurez-vous de la libérer selon vos besoins lorsque vous en avez terminé ou après l'avoir ajoutée à une autre hiérarchie de vues.

e.James
la source
9
Êtes-vous sûr que c'est sûr? Il mute la liste tout en l'itérant, et je ne trouve pas de déclaration définitive dans la documentation d'Apple.
Tommy
8
@Tommy: C'est un bon point. Certains googleurs ont trouvé la réponse: UIViewrenvoie une copie du subviewstableau mutable, donc ce code fonctionne. Histoire complètement différente sur le bureau, où le même code lèvera une exception. Voir stackoverflow.com/questions/4665179/…
e.James
3
UIView ne répond pas à setSubviews:, n'est-ce pas?
cocoafan
4
la manière Xamarin: someUIView.Subviews.All (p => p.RemoveFromSuperview);
Benoit Jadinon
3
@BenoitJadinon - ne compilera pas - vous semblez vouloir abuser de All pour exécuter ForEach, donc someUIView.Subviews.All( v => { v.RemoveFromSuperview(); return true; } );. À mon humble avis propre à dire ce que vous entendez: someUIView.Subviews.ToList().ForEach( v => v.RemoveFromSuperview() );.
ToolmakerSteve
173

Obtenez toutes les sous-vues de votre contrôleur racine et envoyez à chacune un removeFromSuperview:

NSArray *viewsToRemove = [self.view subviews];
for (UIView *v in viewsToRemove) {
    [v removeFromSuperview];
}
Matthew McGoogan
la source
+1 et merci. J'aurais aussi dû utiliser self.viewcomme vous l'avez fait.
e.James
2
pourquoi pas!? for (UIView *v in [self.view subviews])c'est plus facile
Frade
4
@Frade La façon dont il l'a fait est beaucoup plus claire et verbeuse. Verbose et lisibilité> sauvegarde des frappes
taylorcressy
33
@taylorcressy Vous auriez dû dire que "la lisibilité est plus importante que l'enregistrement des frappes" au lieu de "lisibilité> enregistrement des frappes" et votre commentaire serait alors plus lisible. :-)
arlomedia
1
N'oublions pas que si [self.view subviews] effectue des calculs sous le capot, le placer directement dans la boucle for pourrait entraîner la répétition de ces calculs. Le déclarer avant la boucle garantit qu'ils ne sont exécutés qu'une seule fois.
Brian Sachetta
116

Dans Swift, vous pouvez utiliser une approche fonctionnelle comme celle-ci:

view.subviews.forEach { $0.removeFromSuperview() }

À titre de comparaison, l' approche impérative ressemblerait à ceci:

for subview in view.subviews {
    subview.removeFromSuperview()
}

Cependant, ces extraits de code ne fonctionnent que sur iOS / tvOS , les choses sont un peu différentes sur macOS.

Jeehut
la source
4
(subviews as [UIView]).map { $0.removeFromSuperview() }
DeFrenZ
8
ce n'est pas fonctionnel car une fonction retourne une valeur et cela ne fait qu'écarter le résultat du .map. ceci est un effet secondaire pur et est mieux géré comme ceci:view.subviews.forEach() { $0.removeFromSuperview() }
Martin Algesten
1
Tu as raison, Martin, je suis d'accord avec toi. Je ne savais tout simplement pas qu'il y avait une méthode forEach () sur les tableaux. Quand a-t-il été ajouté ou l'ai-je simplement supervisé? J'ai mis à jour ma réponse!
Jeehut
1
Je suis tellement paresseux que même si je savais comment effacer les sous-vues, je suis quand même venu ici pour copier / coller votre extrait de
code
13

Si vous souhaitez supprimer toutes les sous-vues sur votre UIView (ici yourView), alors écrivez ce code à votre bouton, cliquez sur:

[[yourView subviews] makeObjectsPerformSelector: @selector(removeFromSuperview)];
Mohd Rahib
la source
12
Bienvenue dans Stack Overflow! Envisageriez-vous d'ajouter un récit pour expliquer pourquoi ce code fonctionne et ce qui en fait une réponse à la question? Ce serait très utile à la personne qui pose la question et à toute autre personne qui se présente. En outre, la réponse déjà acceptée inclut un code essentiellement identique à celui-ci.
Andrew Barber
5
Comment cela pourrait-il aider plus que la réponse acceptée: elle est identique. Pourquoi écrire ça?
Rambatino
8

Cela ne s'applique qu'à OSX car dans iOS, une copie du tableau est conservée

Lors de la suppression de toutes les sous-vues, il est judicieux de commencer la suppression à la fin du tableau et de continuer à supprimer jusqu'au début. Cela peut être accompli avec ces deux lignes de code:

for (int i=mySuperView.subviews.count-1; i>=0; i--)
        [[mySuperView.subviews objectAtIndex:i] removeFromSuperview];

SWIFT 1.2

for var i=mySuperView.subviews.count-1; i>=0; i-- {
    mySuperView.subviews[i].removeFromSuperview();
}

ou (moins efficace, mais plus lisible)

for subview in mySuperView.subviews.reverse() {
    subview.removeFromSuperview()
}

REMARQUE

Vous ne devez PAS supprimer les sous-vues dans l'ordre normal, car cela peut provoquer un blocage si une instance UIView est supprimée avant que le removeFromSuperviewmessage ait été envoyé à tous les objets du tableau. (Évidemment, la suppression du dernier élément ne provoquerait pas de plantage)

Par conséquent, le code

[[someUIView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];

ne doit PAS être utilisé.

Citation de la documentation Apple sur makeObjectsPerformSelector :

Envoie à chaque objet du tableau le message identifié par un sélecteur donné, en commençant par le premier objet et en continuant à travers le tableau jusqu'au dernier objet.

(ce qui serait la mauvaise direction à cet effet)

Daniel
la source
Pouvez-vous faire un exemple de ce à quoi vous faites référence? Je ne sais pas de quoi vous parlez comme "élément". Et comment ces éléments seraient-ils supprimés avant d'appeler removeFromSuperView?
le Révérend
Mais comment supprimer une instance d'UIView lors de l'appel de cette méthode? Voulez-vous dire supprimé du tableau de sous-vues?
le révérend
Une fois removeFromSuperviewterminé, l'UIView sera supprimé du tableau, et s'il n'y a pas d'autres instances vivantes ayant une relation forte avec l'UIView, l'UIView sera également supprimée. Cela peut provoquer une exception hors limite.
Daniel
1
Je t'ai eu! Je vous remercie. Je pense que vous obtenez une copie du tableau des sous-vues sur IOS. Dans tous les cas, ce serait une bonne idée de faire une copie vous-même si vous souhaitez supprimer des sous-vues.
le révérend
@simpleBob - avez-vous lu les commentaires écrits en 2011 sur la réponse acceptée? Selon ces commentaires, sur iOS, [yourView subviews]retourne une COPIE du tableau, est donc sûr. (NOTEZ que sur OSX, ce que vous dites est correct.)
ToolmakerSteve
4

Essayez de cette façon Swift 2.0

view.subviews.forEach { $0.removeFromSuperview() }
William Hu
la source
2
Ne voyez-vous pas la date réponse date que je suis plus tôt? Pourquoi ne pas coller mon lien de réponse dans cette réponse?
William Hu
1
Bon ... la réponse a été postée avant la vôtre, mais la forEachsolution basée a été ajoutée après la vôtre, cela m'a manqué. Mes excuses.
Cristik
4

Utilisez le code suivant pour supprimer toutes les sous-vues.

for (UIView *view in [self.view subviews]) 
{
 [view removeFromSuperview];
}
Shahzaib Maqbool
la source
2

Utilisation de l' UIViewextension Swift :

extension UIView {
    func removeAllSubviews() {
        for subview in subviews {
            subview.removeFromSuperview()
        }
    }
}
mixel
la source
2

Dans l'objectif-C, allez-y et créez une méthode de catégorie hors de la classe UIView.

- (void)removeAllSubviews
{
    for (UIView *subview in self.subviews)
        [subview removeFromSuperview];
}
Rickster
la source
2
view.subviews.forEach { $0.removeFromSuperview() }
MAGiGO
la source
2
Bien que ce code puisse répondre à la question, fournir un contexte supplémentaire sur la manière et / ou la raison pour laquelle il résout le problème améliorerait la valeur à long terme de la réponse.
Nic3500
1

Afin de supprimer toutes les sous-vues Syntaxe:

- (void)makeObjectsPerformSelector:(SEL)aSelector;

Utilisation:

[self.View.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];

Cette méthode est présente dans le fichier NSArray.h et utilise l'interface NSArray (NSExtendedArray)

Jayprakash Dubey
la source
1

Si vous utilisez Swift, c'est aussi simple que:

subviews.map { $0.removeFromSuperview }

La philosophie est similaire à l' makeObjectsPerformSelectorapproche, mais avec un peu plus de sécurité de type.

lms
la source
Ceci est sémantiquement incorrect, mapne devrait pas entraîner d'effets secondaires. De plus, le même résultat peut être obtenu via forEach.
Cristik
0

Pour ios6 utilisant la mise en page automatique, j'ai dû ajouter un peu de code pour supprimer également les contraintes.

NSMutableArray * constraints_to_remove = [ @[] mutableCopy] ;
for( NSLayoutConstraint * constraint in tagview.constraints) {
    if( [tagview.subviews containsObject:constraint.firstItem] ||
       [tagview.subviews containsObject:constraint.secondItem] ) {
        [constraints_to_remove addObject:constraint];
    }
}
[tagview removeConstraints:constraints_to_remove];

[ [tagview subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];

Je suis sûr qu'il y a une façon plus nette de le faire, mais cela a fonctionné pour moi. Dans mon cas, je ne pouvais pas utiliser un direct [tagview removeConstraints:tagview.constraints]car il y avait des contraintes définies dans XCode qui étaient effacées.

Michael Anderson
la source
0

Dans monotouch / xamarin.ios, cela a fonctionné pour moi:

SomeParentUiView.Subviews.All(x => x.RemoveFromSuperview);
Daniele D.
la source
-10

Pour supprimer toutes les sous-vues des super-vues:

NSArray *oSubView = [self subviews];
for(int iCount = 0; iCount < [oSubView count]; iCount++)
{
    id object = [oSubView objectAtIndex:iCount];
    [object removeFromSuperview];
    iCount--;
}
Pravin
la source
Quelques erreurs majeures ici @Pravin. Tout d'abord, vous auriez besoin que «objet» soit défini comme UIView * sinon vous obtiendriez une erreur de compilation avec [object removeFromSuperview]. Deuxièmement, votre boucle for décrémente déjà iCount donc vous en sautez une supplémentaire avec votre ligne iCount--. Et enfin, il y a deux approches correctes et efficaces ci-dessus et la vôtre n'est ni plus élégante ni plus rapide.
amergin
5
chaque itération que vous faites iCount++et iCount--, en laissant l'index le même, ce sera donc une boucle infinie si [oSubView count]>0. C'est définitivement un code bogué et NON UTILISABLE .
Daniel