Flipping the positions of items in a UIStackView
Create a layout with 2 UIViews
in a UIStackView
like so:
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:
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 theisActive
property box is checked for the selected constraint. The other constraint is dimmed out asInstalled
/isActive
is set to false.
- One proportional width constraint for the horizontal case.
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?
UIStackView
s 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
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:
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:
Related Topics
Programmatically Place Partial Image Over Another in UIview Using Swift 3
Swiftui: Changing Default Command Menus on Macos
Convert Single File to Swift 3 in Xcode 8
Shorthand for Wrapping a Swift Variable in an Optional
Accessibility Custom Actions Aren't Announced in Swift
Combine Sink: Ignore Receivevalue, Only Completion Is Needed
Enum Named 'Type' in Nested Class Fails
How to Completely Remove All Xcode Program and Cache Files
Swift Selector with Default Argument
iOS 8 Sdk, Swift, Mapkit Drawing a Route
Custom Radix Columns (+Special Characters)
Bridgetoobjectivec Not Available on Swift Beta 5
Is There Any Difference at All Between Suffix(From:) and Dropfirst(_:)
Decrypted String Always Returning Null
Member Operator '==' Must Have at Least One Argument of Type
Using Getters and Setters to Modify Values W/O Subclassing in Swift
Swift Cannot Convert The Expression's Type 'Void' to Type 'string!'
How to Change Orientation for Avcapturemoviefileoutput in Swift