21

Stackview containing array of textfield is embedded in scrollview. I want to change the order of the text fields on some actions. The way to remove and add the textfield results in distorted view. I also tested by removing from scrollview. Normal stackview too the exchange does not appear properly. I am using indexes to change :


stackView.removeArrangedSubview(localityTF) stackView.insertArrangedSubview(localityTF, at: 2)

Ankish Jain
  • 11,305
  • 5
  • 36
  • 34

5 Answers5

32

It is strange behaviour, but problem with the autolayout system, that should be updated before you can add localityTF back. Also don't forget that removeArrangedSubview does not remove the view from subviews array:

[self.stackView removeArrangedSubview:_label1];
[self.stackView setNeedsLayout];
[self.stackView layoutIfNeeded];

[self.stackView insertArrangedSubview:_label1 atIndex:2];   
[self.stackView setNeedsLayout];
Andrew Romanov
  • 4,774
  • 3
  • 25
  • 40
  • Thanks a ton. Works great. Between curious to know , how did you figure this solution out. @Andrew – Ankish Jain Dec 26 '16 at 17:41
  • 1
    Then I tried remove and attach subview, I saw exception about constraints (then I attached the view back). I know that the Autolayout engine needs layout if you change constraints. After that I tried to add layout invocations and all has fixed. – Andrew Romanov Dec 27 '16 at 04:14
  • 1
    I don't think you need to remove it first...[ insertArrangedSubview:atIndex:] will automatically rearrange the view if it's already in there. – steven Aug 24 '20 at 03:56
8

This is my solution in Swift 3. It can also be used without defining the view in the stackview.

Here I am changing the locations of the views of a Stackview with two views:

if let myView = stackView.subviews.first {
    stackView.removeArrangedSubview(myView)
    stackView.setNeedsLayout()
    stackView.layoutIfNeeded()

    stackView.insertArrangedSubview(myView, at: 1)
    stackView.setNeedsLayout()
}
Hilalkah
  • 945
  • 15
  • 37
4

Try this bunch of code for replacing views in UIStackView, without refreshing any layouts.

if let objView = stackView.arrangedSubviews.first {
        objView.removeFromSuperview()
}
stackView.insertArrangedSubview(yourView, at: 0)
Mushrankhan
  • 749
  • 9
  • 12
1

If you use horizontal axis:

stackView.semanticContentAttribute = .forceRightToLeft

If you use vertical axis:

import UIKit

enum VerticalSemanticContentAttribute {
    case forceTopToBottom, forceBottomToTop
}

final class VerticalStackView: UIStackView {
     var verticalSemanticContentAttribute:  VerticalSemanticContentAttribute = .forceTopToBottom {
        didSet {
             guard oldValue != verticalSemanticContentAttribute else { return }
        
             reverseArrangedSubviews()
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
    
        axis = .vertical
    }

    required init(coder: NSCoder) {
        super.init(coder: coder)
    
        axis = .vertical
    }
}

// MARK: - Private functions
private extension VerticalStackView {
    func reverseArrangedSubviews() {
        let totalCount = arrangedSubviews.count
    
        for (index, view) in arrangedSubviews.enumerated().reversed() where index != 0 { // we don't need to reposition the last item. It's already in the right position
            removeArrangedSubview(view)
            view.removeFromSuperview()
            setNeedsLayout()
            layoutIfNeeded()
        
            let insertIndex = totalCount - index - 1
            insertArrangedSubview(view, at: insertIndex)
            setNeedsLayout()
            layoutIfNeeded()
        }
    }
}
Hopreeeenjust
  • 257
  • 4
  • 5
0

This solution perfectly works. Call it every time and it will change the first stack element view with the last and vice versa.

  if let first = stackView.subviews.first, let last = stackView.subviews.last {
    stackView.subviews.forEach { $0.removeFromSuperview() }
    stackView.insertArrangedSubview(last, at: 0)
    stackView.insertArrangedSubview(first, at: 1)
    stackView.setNeedsLayout()
    stackView.layoutIfNeeded()
  }
Mihail Salari
  • 1,471
  • 16
  • 17