Flipping The Positions of Items in a UIstackview

Flipping the positions of items in a UIStackView

Create a layout with 2 UIViews in a UIStackView like so:

layout

Create an outlet for your UIStackView to your UIViewController. Call addArrangedSubview on your UIStackView in viewDidLoad to change the order. See snippet below.

import UIKit

class ViewController: UIViewController {
@IBOutlet weak var stackView: UIStackView!

override func viewDidLoad() {
super.viewDidLoad()

// Switches order of the stackView
stackView.addArrangedSubview(self.stackView.subviews[0])
}
}

This should be the result:

Sample Image

The addArrangedSubview takes the given view and adds it to the end of the arrangedSubviews array. In our case we take the red view at index[0] and add it on the end (right) of the UIStackView.

Stackview exchange or change order of views

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

UIstackview how to make sure two child view have different propotional size

Theres no one set of constraints that will do what you want so you need to set up the constraints for both states and set those constraints isActive state true or false depending on whether your stack is in a horizontal or vertical state.

Via Storyboard

You need:

  • One proportional height constraint for the vertical case. Notice how the Installed which is the isActive property box is checked for the selected constraint. The other constraint is dimmed out as Installed/isActive is set to false.

Sample Image

  • One proportional width constraint for the horizontal case.

Sample Image

When you wish to flip, switch the isActive flag on those constraints.

class ViewController: UIViewController {

@IBOutlet weak var stack: UIStackView!
@IBOutlet weak var viewOne: UIView!
@IBOutlet weak var viewTwo: UIView!

@IBOutlet weak var vModeHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var hModeWidthContraint: NSLayoutConstraint!

var isVertical = true {
didSet {
UIView.animate(withDuration: 1.0) {
self.toggle()
}
}
}

func toggle() {
vModeHeightConstraint.isActive = false
hModeWidthContraint.isActive = false
stack.axis = isVertical ? .vertical:.horizontal
stack.layoutIfNeeded()

vModeHeightConstraint.isActive = isVertical
hModeWidthContraint.isActive = !isVertical
stack.layoutIfNeeded()
}

@IBAction func toggleModeAction(_ sender: Any) {
isVertical.toggle()
}

}

You can set up the constraints programmatically. How to do this is easy to find on SO.

You will see a bunch of Autolayout errors during the change. Those errors can be filed under "UIKit isn't perfect".

Typically this sort of change is triggered by a device rotation in func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) but you haven't said when you want the change to occur.

How to use Auto Layout to move other views when a view is hidden?

It is possible, but you'll have to do a little extra work. There are a couple conceptual things to get out of the way first:

  • Hidden views, even though they don't draw, still participate in Auto Layout and usually retain their frames, leaving other related views in their places.
  • When removing a view from its superview, all related constraints are also removed from that view hierarchy.

In your case, this likely means:

  • If you set your left view to be hidden, the labels stay in place, since that left view is still taking up space (even though it's not visible).
  • If you remove your left view, your labels will probably be left ambiguously constrained, since you no longer have constraints for your labels' left edges.

What you need to do is judiciously over-constrain your labels. Leave your existing constraints (10pts space to the other view) alone, but add another constraint: make your labels' left edges 10pts away from their superview's left edge with a non-required priority (the default high priority will probably work well).

Then, when you want them to move left, remove the left view altogether. The mandatory 10pt constraint to the left view will disappear along with the view it relates to, and you'll be left with just a high-priority constraint that the labels be 10pts away from their superview. On the next layout pass, this should cause them to expand left until they fill the width of the superview but for your spacing around the edges.

One important caveat: if you ever want your left view back in the picture, not only do you have to add it back into the view hierarchy, but you also have to reestablish all its constraints at the same time. This means you need a way to put your 10pt spacing constraint between the view and its labels back whenever that view is shown again.

How do you right align a horizontal UIStackView?

UIStackViews align according to the user's text direction, i.e. left aligned for English and other Roman script languages, or right aligned for languages such as Hebrew.

In my opinion, changing this for layout reasons may be a misuse of the text direction APIs, and a bit of a hack, but with that in mind:

You can change the direction for a particular view in interface builder, using the Semantic drop down, which has options for 'Force Left-to-Right' and 'Force Right-to-Left', which will change the direction they pop to but also the order they are shown in. So you will have to reverse the order of the elements in your stack view

Semantic selector in IB

Or you can do it in code, using the view's semanticContentAttribute

stackView.semanticContentAttribute = .forceRightToLeft

Adding 'n' number of collection view to UIStackView in swift

I have added 3 collectionView inside the UIStackView(Vertical stack). The whole view can be scrolled vertically and the collection items can be scrolled either horizontally or vertically.

class HomePageViewController: UIViewController,HeaderViewDelegate {

var scrollView : UIScrollView!
var stackView : UIStackView!
var responseFullData : JsonFullDataResponse!

if responseFullData?.menu?.count != nil{
print("menu has data")

var menuCollection : MenuCollectionView = MenuCollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout.init())
menuCollection.backgroundColor = .white
menuCollection.menuData = (self.responseFullData.menu)!
menuCollection.dataSource = menuCollection.self
menuCollection.delegate = menuCollection.self
createCollectionViews(collectionViewClass: menuCollection, identifier: "MenuCell",height : 175)
}
if responseFullData?.hongbei?.count != nil {
print("hongbei has data")
var hongbeiCollection : HongbeiCollectionView = HongbeiCollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout.init())

hongbeiCollection.dataSource = hongbeiCollection
hongbeiCollection.delegate = hongbeiCollection
hongbeiCollection.allowsSelection = true
hongbeiCollection.backgroundColor = .white
hongbeiCollection.hongbeiData = (self.responseFullData.hongbei)!

addHeaderView(headerTitle : "Hongbei")
createCollectionViews(collectionViewClass: hongbeiCollection, identifier: "HongbeiCell",height : 150)

}
if responseFullData?.freeZone?.count != nil {
print("freezone has data")
var freezoneCollection : FreeZoneCollectionView = FreeZoneCollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout.init())
freezoneCollection.dataSource = freezoneCollection.self
freezoneCollection.delegate = freezoneCollection.self
freezoneCollection.backgroundColor = .white
freezoneCollection.freeZoneData = (self.responseFullData.freeZone)!
addHeaderView(headerTitle : "FreeZone")
createCollectionViews(collectionViewClass: freezoneCollection, identifier: "FreeZoneCell",height : 150)
}

func createCollectionViews(collectionViewClass : UICollectionView, identifier : String,height : CGFloat ){

collectionViewClass.widthAnchor.constraint(equalToConstant: self.view.frame.width).isActive = true
collectionViewClass.heightAnchor.constraint(equalToConstant: height).isActive = true
collectionViewClass.register(UINib.init(nibName: identifier, bundle: nil), forCellWithReuseIdentifier: identifier)
collectionViewClass.showsHorizontalScrollIndicator = false
collectionViewClass.showsVerticalScrollIndicator = false

stackView.addArrangedSubview(collectionViewClass)

}

func addHeaderView(headerTitle : String){
let headerView = HeaderView.instanceFromNib() as! HeaderView
headerView.widthAnchor.constraint(equalToConstant: self.view.frame.width).isActive = true
headerView.heightAnchor.constraint(equalToConstant: 20).isActive = true
headerView.headerLabel.text = headerTitle
headerView.frame = headerView.frame.offsetBy(dx: 20, dy: 0)
headerView.delegate = self
stackView.addArrangedSubview(headerView)

}

}

class MenuCollectionView: UICollectionView,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {

var menuData : [MenuData]!

override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
super.init(frame: frame, collectionViewLayout: layout)

}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int{
print("Menu")
return menuData.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MenuCell", for: indexPath) as! MenuCell

cell.menuLabel.text = menuData[indexPath.item].title!

return cell
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
return CGSize(width: (collectionView.frame.width * 0.175), height: 75)
}

I have a separate class for each collectionView in the stack, where I handle the data source and delegate methods. So I can have any number for collection items I want in each collectionView.

UIView with top space Constraint to UIStackView not displayed below Stackview

The solution was faily simple.
Setting a frame for my programatically-added-views was wrong, instead I had to use constraints and StackView's "addArrangedSubView" Method:

for (NoteTodoEntity* entity in _note.todos) {
NotinaryTodoView* view = [[[NSBundle mainBundle] loadNibNamed:@"NotinaryTodoView" owner:self options:nil] objectAtIndex:0];
[view.heightAnchor constraintEqualToConstant:40].active = true;
[view.widthAnchor constraintEqualToConstant:self.view.frame.size.width].active = true;
view.currentEntity = entity;
[view.todoDeleteButton addTarget:self action:@selector(todoActionPressed:) forControlEvents:UIControlEventTouchUpInside];
[_todoContainer addArrangedSubview:view];
}

Removing views from vertical stackview

The views in a UIStackView are already an indexed array, accessible via the stack view's .arrangedSubviews property.

So, suppose you have 8 label/field pairs, they will be indexed like this:

Sample Image

If you want to remove the 4th pair (Label 4 and Field 4), you can do this:

stackView.arrangedSubviews[6].removeFromSuperview()

That removes "Label 4" (remember, array indexes are zero-based).

Now, "Field 4" is at [6], so just call it again:

stackView.arrangedSubviews[6].removeFromSuperview()

That said, depending on what you're doing, you may find it easier to ADD your label/field pairs at runtime.

Something along these lines:

    let labels: [String] = [
"First name",
"Last name",
"Age",
"Favorite color",
"Favorite food",
"Favorite animal",
]

// based on selection from previous view controller,
// let's show First name, Last name, Fav color and animal
let pairsToShow: [Int] = [0, 1, 3, 5]

pairsToShow.forEach { idx in
let v = UILabel()
v.text = labels[idx]
v.backgroundColor = .yellow
stackView.addArrangedSubview(v)
let t = UITextField()
t.borderStyle = .roundedRect
stackView.addArrangedSubview(t)
}

and we end up with this:

Sample Image



Related Topics



Leave a reply



Submit