How do you set a background color for a stack view? For that matter how do overlay foreground or other decorator views over a stack view? If you have used UIStackView
you probably know that it is not a normal subclass of UIView
. Setting its backgroundColor
has no effect. What we can do is add subviews that are not managed by the stack view.
Last updated: Nov 10, 2022
Starting with iOS 14, you can set the backgroundColor
directly for a UIStackView
. See Stack View Background Color in iOS 14 for details.
Non-rendering View
From the header file for UIStackView
:
UIStackView is a non-rendering subclass of UIView, intended for managing layout of its subviews.
So the reason backgroundColor
has no effect for a stack view is that it is never drawn (rendered).
Using Un-arranged Subviews
A stack view manages the layout of views added to its arrangedSubviews
array. It is easy to forget that like every UIView
it also has a subviews
array. In fact the stack view makes sure that arrangedSubviews
is a subset of subviews
. When you use addArrangedSubview
the stack view adds the view to arrangedSubviews
but also checks and adds the view to subviews
if it is not already there.
It is perfectly ok for us to add extra subviews to a stack view that are not part of arrangedSubviews
. The stack view will not add constraints for these views - we can do that ourselves. Perfect for things like background or overlay views.
The order of the subviews
array sets the order from background to foreground of the displayed views (regardless of whether the views are also in arrangedSubviews
).
The Setup
Here is my setup so we can try this out. I embedded two images in a horizontal stack view which is then embedded in a vertical stack view containing a switch:
I will skip the details but I have pinned the root stack view to the top, leading and trailing margins of the super view. I also connected outlets in the view controller:
class RevealViewController: UIViewController {
@IBOutlet private weak var rootStackView: UIStackView!
@IBOutlet private weak var imageStackView: UIStackView!
@IBOutlet private weak var revealSwitch: UISwitch!
Adding a Background View
As a first step we can add a background to the root stack view. Let’s create a purple background view with rounded corners:
private lazy var backgroundView: UIView = {
let view = UIView()
view.backgroundColor = .purple
view.layer.cornerRadius = 10.0
return view
}()
To make it appear as the background we add it to the subviews
array of the root stack view at index 0. That puts it behind the arranged views of the stack view. We also need constraints to pin it to the edges of the stack view. I have a short method to do that:
private func pinBackground(_ view: UIView, to stackView: UIStackView) {
view.translatesAutoresizingMaskIntoConstraints = false
stackView.insertSubview(view, at: 0)
view.pin(to: stackView)
}
Don’t forget to disable translatesAutoresizingMaskIntoConstraints
for the background view. I am using a small extension on UIView
to pin a view to another view:
public extension UIView {
public func pin(to view: UIView) {
NSLayoutConstraint.activate([
leadingAnchor.constraint(equalTo: view.leadingAnchor),
trailingAnchor.constraint(equalTo: view.trailingAnchor),
topAnchor.constraint(equalTo: view.topAnchor),
bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
}
I call the pinBackground
from viewDidLoad
:
override func viewDidLoad() {
super.viewDidLoad()
pinBackground(backgroundView, to: rootStackView)
}
Here is what our root stack view looks like with a rounded purple background:
Adding an Overlay or Foreground View
We don’t need to stop with a background view. We can add views at any position in subviews
. For a foreground view use addSubview
to add the view at the end of the list:
private func pinForeground(_ view: UIView, to stackView: UIStackView) {
view.translatesAutoresizingMaskIntoConstraints = false
stackView.addSubview(view)
view.pin(to: stackView)
}
Let’s have a yellow cover view:
private lazy var coverView: UIView = {
let view = UIView()
view.backgroundColor = .yellow
return view
}()
and pin it to the image stack view:
override func viewDidLoad() {
super.viewDidLoad()
pinBackground(backgroundView, to: rootStackView)
pinForeground(coverView, to: imageStackView)
}
The yellow view is in the foreground of the image stack view so the two image views are no longer visible:
Let’s hook up the switch action to toggle the alpha property of the cover view to reveal the two image views that are underneath. For extra fun we can animate the change:
@IBAction func revealAction(_ sender: UISwitch) {
UIView.animate(withDuration: 0.25) {
self.configureReveal()
}
}
private func configureReveal() {
coverView.alpha = revealSwitch.isOn ? 0 : 1.0
}
The final version of viewDidLoad
to also configure the cover view:
override func viewDidLoad() {
super.viewDidLoad()
pinBackground(backgroundView, to: rootStackView)
pinForeground(coverView, to: imageStackView)
configureReveal()
}
The final effect is not going to win any design prizes but I hope you get idea:
Get the Code
- RevealStack - Xcode project containing the full code from this post
Learn More
I dig deeper into stack views and how to use them to build adaptive layouts in my book: