331

When my app gets back to its root view controller, in the viewDidAppear: method I need to remove all subviews.

How can I do this?

aheze
  • 24,434
  • 8
  • 68
  • 125
Ian Vink
  • 66,960
  • 104
  • 341
  • 555

15 Answers15

573

Edit: With thanks to cocoafan: This situation is muddled up by the fact that NSView and UIView handle things differently. For NSView (desktop Mac development only), you can simply use the following:

[someNSView setSubviews:[NSArray array]];

For UIView (iOS development only), you can safely use makeObjectsPerformSelector: because the subviews property will return a copy of the array of subviews:

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

Thank you to Tommy for pointing out that makeObjectsPerformSelector: appears to modify the subviews array while it is being enumerated (which it does for NSView, but not for UIView).

Please see this SO question for more details.

Note: Using either of these two methods will remove every view that your main view contains and release them, if they are not retained elsewhere. From Apple's documentation on removeFromSuperview:

If the receiver’s superview is not nil, this method releases the receiver. If you plan to reuse the view, be sure to retain it before calling this method and be sure to release it as appropriate when you are done with it or after adding it to another view hierarchy.

Community
  • 1
  • 1
e.James
  • 116,942
  • 41
  • 177
  • 214
  • 9
    Are you sure this is safe? It mutates the list while iterating it, and I'm unable to find a definitive statement in Apple's documentation. – Tommy Mar 08 '11 at 02:42
  • 8
    @Tommy: That is a good point. Some Googling turned up the answer: `UIView` returns a *copy* of the `subviews` mutable array, so this code just works. Completely different story on the desktop, where the same code will throw an exception. See http://stackoverflow.com/questions/4665179/behavior-difference-between-uiview-subviews-and-nsview-subviews – e.James Mar 08 '11 at 16:48
  • @Tommy: I have updated my answer accordingly. Thank you for catching that! – e.James Mar 08 '11 at 16:57
  • 3
    UIView does not respond to setSubviews:, does it? – cocoafan Mar 16 '11 at 17:04
  • @cocoafan: It looks like you are right. You can only set the subviews from within *subclasses* of `UIView`. It is `NSView` that allows setting them externally. – e.James Mar 16 '11 at 17:38
  • Couldn't find a way to do this in Swift – Van Du Tran Apr 11 '15 at 04:42
  • 4
    the Xamarin way : someUIView.Subviews.All(p => p.RemoveFromSuperview); – Benoit Jadinon Jun 02 '15 at 19:14
  • Good answer, Slightly different way: create a UIView category and implement some -(void)removeSubviews method. Same mechanism, but prettier code. – AzaFromKaza Dec 24 '15 at 05:23
  • 3
    @BenoitJadinon - won't compile - you appear to mean abusing All to perform ForEach, so `someUIView.Subviews.All( v => { v.RemoveFromSuperview(); return true; } );`. IMHO cleaner to say what you mean: `someUIView.Subviews.ToList().ForEach( v => v.RemoveFromSuperview() );`. – ToolmakerSteve Jul 13 '16 at 16:16
174

Get all the subviews from your root controller and send each a removeFromSuperview:

NSArray *viewsToRemove = [self.view subviews];
for (UIView *v in viewsToRemove) {
    [v removeFromSuperview];
}
Matthew McGoogan
  • 6,805
  • 3
  • 18
  • 13
  • +1 and thank you. I should have also used `self.view` as you have. – e.James Jan 28 '10 at 16:32
  • 2
    why not!? `for (UIView *v in [self.view subviews])` its easier – Frade Jun 18 '14 at 11:31
  • 4
    @Frade It's much clearer and more verbose the way he did it. Verbose and readability > saving keystrokes – taylorcressy Aug 21 '14 at 17:10
  • 34
    @taylorcressy You should have said "readability is more important than saving keystrokes" instead of "readability > saving keystrokes" and then your comment would be more readable. :-) – arlomedia Jan 14 '15 at 19:46
  • 1
    Let's not forget about the fact that if [self.view subviews] performs any calculations under the hood, putting it directly in the for loop could cause those calculations to be performed over and over again. Declaring it before the loop ensures they are only performed once. – Brian Sachetta Jul 17 '18 at 14:38
143

In Swift you can use a functional approach like this:

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

As a comparison, the imperative approach would look like this:

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

These code snippets only work in iOS / tvOS though, things are a little different on macOS.

Jeehut
  • 20,202
  • 8
  • 59
  • 80
  • 4
    ```(subviews as [UIView]).map { $0.removeFromSuperview() }``` – DeFrenZ Jan 02 '15 at 13:13
  • 11
    it's not functional since a function returns a value and this just discards the result of the `.map`. this is a pure side effect and is better handled like this: `view.subviews.forEach() { $0.removeFromSuperview() }` – Martin Algesten Nov 24 '15 at 05:37
  • 1
    You are right, Martin, I agree with you. I just didn't know there was a forEach() method on Arrays. When was it added or did I just oversee it? I've updated my answer! – Jeehut Nov 25 '15 at 18:30
  • 1
    I am so lazy that even if I knew how to clear subviews, I still came here to copy/paste your snippet and put a +1 – Vilmir Aug 04 '19 at 09:10
14

If you want to remove all the subviews on your UIView (here yourView), then write this code at your button click:

[[yourView subviews] makeObjectsPerformSelector: @selector(removeFromSuperview)];
Jeehut
  • 20,202
  • 8
  • 59
  • 80
Mohd Rahib
  • 167
  • 1
  • 3
  • 12
    Welcome to Stack Overflow! Would you consider adding some narrative to explain why this code works, and what makes it an answer to the question? This would be very helpful to the person asking the question, and anyone else who comes along. Additionally, the already-accepted answer includes code that is essentially the same as this. – Andrew Barber May 04 '13 at 21:19
  • 5
    How could this help more so than the accepted answer: It's identical. Why write this? – Rambatino May 29 '14 at 19:06
9

This does only apply to OSX since in iOS a copy of the array is kept

When removing all the subviews, it is a good idea to start deleting at the end of the array and keep deleting until you reach the beginning. This can be accomplished with this two lines of 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();
}

or (less efficient, but more readable)

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

NOTE

You should NOT remove the subviews in normal order, since it may cause a crash if a UIView instance is deleted before the removeFromSuperview message has been sent to all objects of the array. (Obviously, deleting the last element would not cause a crash)

Therefore, the code

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

should NOT be used.

Quote from Apple documentation about makeObjectsPerformSelector:

Sends to each object in the array the message identified by a given selector, starting with the first object and continuing through the array to the last object.

(which would be the wrong direction for this purpose)

Daniel
  • 20,420
  • 10
  • 92
  • 149
  • Can you please make an example of what you are referring to ? Don't know what are you referring to as "element" And how would this elements be removed before calling removeFromSuperView ? – the Reverend May 26 '15 at 22:13
  • But how can an instance of UIView be deleted while calling this method ? Do you mean removed from the subview array ? – the Reverend Jun 02 '15 at 21:21
  • When `removeFromSuperview` finishes, the UIView will be removed from the array, and if there are no other living instances with a strong relation to the UIView, the UIView will also be deleted. This may cause an out of bound exception. – Daniel Jun 03 '15 at 08:22
  • 1
    Gotcha! Thank you. I think you are getting a copy of the subviews array on IOS. In any case, it would be a good idea to make a copy yourself if you want to remove subviews. – the Reverend Jun 03 '15 at 15:29
  • @simpleBob - did you read the comments written in 2011 on the accepted answer? According to those comments, on iOS, `[yourView subviews]` returns a COPY of the array, therefore is safe. (NOTE that on OSX, what you say is correct.) – ToolmakerSteve Jul 13 '16 at 17:02
  • Cannot use mutating member on immutable value: 'subviews' is a get-only property – JBarros35 Jul 10 '21 at 20:22
6

Try this way swift 2.0

view.subviews.forEach { $0.removeFromSuperview() }
William Hu
  • 15,423
  • 11
  • 100
  • 121
  • 2
    Don't you see the date answer date i am earlier? Why not paste my answer link into that answer? – William Hu Jan 27 '16 at 10:56
  • 1
    Right... the answer was posted before yours however the `forEach` based solution was added after yours, I missed that. Apologies. – Cristik Jan 27 '16 at 12:02
5
view.subviews.forEach { $0.removeFromSuperview() }
MAGiGO
  • 599
  • 5
  • 13
4

In objective-C, go ahead and create a category method off of the UIView class.

- (void)removeAllSubviews
{
    for (UIView *subview in self.subviews)
        [subview removeFromSuperview];
}
Rickster
  • 870
  • 10
  • 17
4

Use the Following code to remove all subviews.

for (UIView *view in [self.view subviews]) 
{
 [view removeFromSuperview];
}
Shahzaib Maqbool
  • 1,479
  • 16
  • 25
3

Using Swift UIView extension:

extension UIView {
    func removeAllSubviews() {
        for subview in subviews {
            subview.removeFromSuperview()
        }
    }
}
mixel
  • 25,177
  • 13
  • 126
  • 165
1

In order to remove all subviews Syntax :

- (void)makeObjectsPerformSelector:(SEL)aSelector;

Usage :

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

This method is present in NSArray.h file and uses NSArray(NSExtendedArray) interface

Jayprakash Dubey
  • 35,723
  • 18
  • 170
  • 177
1

If you're using Swift, it's as simple as:

subviews.map { $0.removeFromSuperview }

It's similar in philosophy to the makeObjectsPerformSelector approach, however with a little more type safety.

lmirosevic
  • 15,787
  • 13
  • 70
  • 116
  • 1
    This is semantically incorrect, `map` should not result in side effects. Plus, the same result can be achieved via `forEach`. – Cristik Oct 26 '18 at 12:41
0

For ios6 using autolayout I had to add a little bit of code to remove the constraints too.

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)];

I'm sure theres a neater way to do this, but it worked for me. In my case I could not use a direct [tagview removeConstraints:tagview.constraints] as there were constraints set in XCode that were getting cleared.

Michael Anderson
  • 70,661
  • 7
  • 134
  • 187
0

In monotouch / xamarin.ios this worked for me:

SomeParentUiView.Subviews.All(x => x.RemoveFromSuperview);
Daniele D.
  • 2,624
  • 3
  • 36
  • 42
-10

In order to remove all subviews from superviews:

NSArray *oSubView = [self subviews];
for(int iCount = 0; iCount < [oSubView count]; iCount++)
{
    id object = [oSubView objectAtIndex:iCount];
    [object removeFromSuperview];
    iCount--;
}
Pravin
  • 1
  • 2
  • Couple of major mistakes here @Pravin. First, you'd need 'object' to be defined as a UIView* otherwise you'd get a compiler error with [object removeFromSuperview]. Second, your for loop is already decrementing iCount so you are skipping an extra one with your iCount-- line. And finally, there are two working and correct approaches above and yours is neither more elegant nor faster. – amergin Apr 26 '14 at 14:38
  • 5
    each iteration you do `iCount++` and `iCount--`, leaving the index the same, so it will be an infinite loop if `[oSubView count]>0`. This is definitely buggy and **NOT USABLE** code. – Daniel May 22 '14 at 15:06