Overlapping Views in UIStackView
Asked Answered
B

9

26

I have an horizontal stack view that I added arranged sub views to it (7 cells). Each one of the cells in the stack has a circular badge that exceeds the view boundaries (negative constraint). When running, as you can see below, each cell is on top of the badge of the previous cell. I would like to change the order so that the badges will be fully visible.

enter image description here

Tried playing with the Z index but it didn't help as the layers are somehow not flat as you can see in the following view hierarchy:

enter image description here

Any idea or suggestion how to do it?

Thanks.

Backwoods answered 16/1, 2016 at 12:17 Comment(1)
You can also do this by setting a clear background on the individual box subview, but use a white background for the stack view.Earthy
B
18

The documentation for the UIStackView class tells that:

The order of the subviews array defines the Z-order of the subviews. If the views overlap, subviews with a lower index appear behind subviews with a higher index.

which is exactly what I experienced in the question. To over come my specific case I did the trick of changing the semantic of the view to be RTL as can bee seen here:

enter image description here

This is not covering the a generic case because a device may have been set a RTL language in its global settings. For the generic case I guess I will need to check the interface semantic and force an opposite direction to my stack view.

P.S. It's kind of hack so any ideas about how to reorder the Z-order of subviews in a different way will be most welcome!

Backwoods answered 18/1, 2016 at 6:55 Comment(0)
F
21

This Swift solution reverses the stack view subview order using the UIView method sendSubviewToBack(_:) (docs here):

@IBOutlet weak var stackView: UIStackView!

for view in stackView.arrangedSubviews {
    stackView.sendSubviewToBack(view)
}

As we advance through the stack view's arranged views (from bottom to top), the later (i.e. higher) views get pushed to the bottom, thereby reversing the order :-)


Something for 2022:

Even Swiftier...

@IBOutlet weak var stackView: UIStackView!

stackView.arrangedSubviews.forEach({ stackView.sendSubviewToBack($0) })
Frigidarium answered 8/11, 2017 at 10:41 Comment(0)
B
18

The documentation for the UIStackView class tells that:

The order of the subviews array defines the Z-order of the subviews. If the views overlap, subviews with a lower index appear behind subviews with a higher index.

which is exactly what I experienced in the question. To over come my specific case I did the trick of changing the semantic of the view to be RTL as can bee seen here:

enter image description here

This is not covering the a generic case because a device may have been set a RTL language in its global settings. For the generic case I guess I will need to check the interface semantic and force an opposite direction to my stack view.

P.S. It's kind of hack so any ideas about how to reorder the Z-order of subviews in a different way will be most welcome!

Backwoods answered 18/1, 2016 at 6:55 Comment(0)
L
5

Since iOS 9.0 it's possible to set the z-order and arrangement of stack view subviews independently. Use the new arrangedSubviews property to specify the subviews arrangement along the stack view’s axis. And use subviews property to specify subviews z-order based on their index in the array.

The stack view ensures that its arrangedSubviews property is always a subset of its subviews property.

Documentation for arrangedSubviews

Leatherworker answered 1/5, 2017 at 4:58 Comment(2)
for a quick example of changing the view z order but not the arranged order, you can do something like this stackView.insertSubview(stackView.arrangedSubviews[0], at: 1) which will swap the 1st and 2nd items draw order but not their displayed orderLampedusa
It looks like it will insert a duplicatedView in your subviews to meSuboceanic
O
2

Below is a simple extension on UIStackView to reverse the z-index of the UIStackView's subviews and optionally require layout:

extension UIStackView {

  func reverseSubviewsZIndex(setNeedsLayout: Bool = true) {
    let stackedViews = self.arrangedSubviews

    stackedViews.forEach {
      self.removeArrangedSubview($0)
      $0.removeFromSuperview()
    }

    stackedViews.reversed().forEach(addSubview(_:))
    stackedViews.forEach(addArrangedSubview(_:))

    if setNeedsLayout {
      stackedViews.forEach { $0.setNeedsLayout() }
    }
  }

}
Origami answered 25/10, 2017 at 21:20 Comment(1)
Amazing! Thanks a mil!Comeback
Y
1

An other idea could be to set background color of the subviews (7 cells) to default (fully transparent). And only set the background of the stack view itself to white.

I have a similar issue but where every subview has to have a specific background. And in my case, your hack might help… 😉 Thanks for it!

Yoshieyoshiko answered 11/2, 2017 at 16:17 Comment(0)
C
1

Programmatically when you add the subview, add height and width constant constraints. In the end call StackView's setNeedsLayout. It will calculate and try to adjust the views with spacing

After various attempts i am able to make it horizontally

 stackView.leadingAnchor.constraint(equalTo: stackScrollView.leadingAnchor).isActive = true
 stackView.trailingAnchor.constraint(equalTo: stackScrollView.trailingAnchor).isActive = true
 stackView.bottomAnchor.constraint(equalTo: stackScrollView.bottomAnchor).isActive = true
 stackView.heightAnchor.constraint(equalTo: stackScrollView.heightAnchor).isActive = true

 stackView.distribution = .equalSpacing
 stackView.spacing = 5
 stackView.axis = .horizontal
 stackView.alignment = .fill


 for i in 0 ..< images.count {
     let photoView = UIButton.init(frame: CGRect(x: 0, y: 0, width: 85, height: 85))

     // set button image
     photoView.translatesAutoresizingMaskIntoConstraints = false
     photoView.heightAnchor.constraint(equalToConstant: photoView.frame.height).isActive = true
     photoView.widthAnchor.constraint(equalToConstant: photoView.frame.width).isActive = true

     stackView.addArrangedSubview(photoView)
 }

 stackView.setNeedsLayout()
Couching answered 27/1, 2019 at 8:57 Comment(0)
U
1

I was looking to prevent overlapping all together. I tried the sendSubviewToBack method, but all that did was shift the overlap to the other side, unsurprisingly.

In my case, this was caused by an image view on my subview being set to scaleToFill which meant some of the image was actually overlapping the next view. My solution to this was, in my subview class (the view that was going into the stack view), was to simply enable mask to bounds

view.maskToBounds = true

This means that it just crops out anything that is hanging outside.

Unlay answered 22/7, 2020 at 9:50 Comment(0)
F
0

In my case UIStackView was inside UITableViewCell and I was pulling my hair out why cells overlapped. Here is the cure for prepareForReuse():

extension UIStackView
{
  func wipeClean() {
    arrangedSubviews.forEach {
      removeArrangedSubview($0)
      // removeArrangedSubview does not removeFromSuperview
      // hence do the explicit
      $0.removeFromSuperview() }
  }
}

About two days wasted cause removeArrangedSubview does not imply removeFromSuperview.

L for Logic: the view does not participate in stack layout, but is shown onscreen. I'd be interested where Apple thinks this might ever be useful.

Floater answered 27/8, 2021 at 10:37 Comment(0)
B
0

For me I had written this code:

stackView.translatesAutoresizingMaskIntoConstraints = true

so I just removed that line of code and I was good to go

Baxie answered 24/7, 2022 at 21:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.