Jacob Van Brunt
Jacob Van Brunt

Reputation: 188

Removing Arranged Subviews from UIStackView Crashes App

I have a UIStackView in IB and I am removing and adding subviews in viewDidLoad. When removing subviews it crashes.

[self.headerStackView.arrangedSubviews each:^(UIView *subview) {
    [self.headerStackView removeArrangedSubview:subview];
    [subview removeConstraints:subview.constraints];
    [subview removeFromSuperview];
}];

Debugger:

2016-11-29 12:35:18.568137 RocheUnregulated[2211:937474] [LayoutConstraints] View hierarchy unprepared for constraint.
Constraint: <NSLayoutConstraint:0x17429ec80 'UISV-spacing' H:[EntryItemInfoView:0x10fe10840]-(15)-[EntryItemInfoView:0x10fd85e50]   (active)>
Container hierarchy: 
<UIStackView: 0x10fe14160; frame = (125 12; 183 51); opaque = NO; autoresize = RM+BM; layer = <CATransformLayer: 0x1700351c0>>
 | <EntryItemInfoView: 0x10fd85e50; frame = (66 0; 51 51); autoresize = RM+BM; layer = <CALayer: 0x1702308e0>>
 |    | <UIStackView: 0x10fd86570; frame = (10 10; 31 31); opaque = NO; autoresize = RM+BM; layer = <CATransformLayer: 0x170230b00>>
 |    |    | <UILabel: 0x10fd86730; frame = (0 0; 31 20.5); text = '45'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x170480f00>>
 |    |    | <UILabel: 0x10fd86c50; frame = (0 20.5; 31 10.5); text = 'grams'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x170299410>>
 | <EntryItemInfoView: 0x10fd86ed0; frame = (132 0; 51 51); autoresize = RM+BM; layer = <CALayer: 0x170230ac0>>
 |    | <UIStackView: 0x10fd62a00; frame = (10 10; 31 31); opaque = NO; autoresize = RM+BM; layer = <CATransformLayer: 0x170230b60>>
 |    |    | <UILabel: 0x10fd870d0; frame = (0 0; 31 20.5); text = '10'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x170481d10>>
 |    |    | <UILabel: 0x10fd87350; frame = (0 20.5; 31 10.5); text = 'units'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x170481090>>
View not found in container hierarchy: <EntryItemInfoView: 0x10fe10840; frame = (0 0; 51 51); autoresize = RM+BM; layer = <CALayer: 0x17422ad20>>
That view's superview: NO SUPERVIEW

Upvotes: 8

Views: 9372

Answers (4)

Ali &#199;olak
Ali &#199;olak

Reputation: 11

When removing arranged subviews from a UIStackView, simply calling removeArrangedSubview(_:) is not enough. The view remains in the hierarchy, and constraints might cause crashes. Here’s a safe way to remove all arranged subviews properly.

Solution

import UIKit

/// An extension for safely removing all arranged subviews from a UIStackView.
extension UIStackView {
    
    /// Removes all arranged subviews from the stack view and returns them in an array.
    /// - Returns: An array of removed `UIView` elements.
    @discardableResult
    func removeAllArrangedSubviews() -> [UIView] {
        let removedSubviews = arrangedSubviews.reduce(into: [UIView]()) { (result, subview) in
            self.removeArrangedSubview(subview) // Detach from arrangedSubviews
            NSLayoutConstraint.deactivate(subview.constraints) // Deactivate constraints
            subview.removeFromSuperview() // Remove from superview
            result.append(subview)
        }
        return removedSubviews
    }
}

Usage

stackView.removeAllArrangedSubviews()

Upvotes: 0

Robert
Robert

Reputation: 241

I tried many solutions from stackoverflow, but none of them worked for me. My App still crashes when I removed all subviews from my stack view.

But changing the stackview's distribution worked for me:

stackView.distribution = .fillProportionally // <- crash
stackView.distribution = .fill // <- work

Upvotes: 1

Ken Franklin
Ken Franklin

Reputation: 71

[self.headerStackView.arrangedSubviews each:^(UIView *subview) {
    [self.headerStackView removeArrangedSubview:subview]; // <-- Removes Constraints as well
    [subview removeConstraints:subview.constraints];//  <--- Constraints will be invalid or non existing
    [subview removeFromSuperview];
}];

Try this:

while let first = stackView.arrangedSubviews.first {
        stackView.removeArrangedSubview(first) 
        first.removeFromSuperview()
}

Upvotes: 4

Max Chuquimia
Max Chuquimia

Reputation: 7854

This function seems to fix it for me:

extension UIStackView {

    func safelyRemoveArrangedSubviews() {

        // Remove all the arranged subviews and save them to an array
        let removedSubviews = arrangedSubviews.reduce([]) { (sum, next) -> [UIView] in
            self.removeArrangedSubview(next)
            return sum + [next]
        }

        // Deactive all constraints at once
        NSLayoutConstraint.deactivate(removedSubviews.flatMap({ $0.constraints }))

        // Remove the views from self
        removedSubviews.forEach({ $0.removeFromSuperview() })
    }
}

Upvotes: 15

Related Questions