How to change the background color of UIStackView?
Asked Answered
L

22

186

I tried to change the UIStackView background from clear to white in Storyboard inspector, but when simulating, the background color of the stack view still has a clear color.
How can I change the background color of a UIStackView?

Layamon answered 19/1, 2016 at 4:6 Comment(4)
this is one of those rare cases on SO, where the answers are simply totally wrong. you just add a ... background view. it's very simple. example article explaining it: useyourloaf.com/blog/stack-view-background-colorReagan
@Reagan It worked! Can you add it as an answer too?Detumescence
hi @AbSin - the answers of kurrodu and MariánČerný are perfect.Reagan
Haha, this is great, non-rendering element has a background color propertyLycian
S
322

You can't do this – UIStackView is a non-drawing view, meaning that drawRect() is never called and its background color is ignored. If you desperately want a background color, consider placing the stack view inside another UIView and giving that view a background color.

Reference from HERE.

EDIT:

You can add a subView to UIStackView as mentioned HERE or in this answer (below) and assign a color to it. Check out below extension for that:

extension UIStackView {
    func addBackground(color: UIColor) {
        let subView = UIView(frame: bounds)
        subView.backgroundColor = color
        subView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        insertSubview(subView, at: 0)
    }
}

And you can use it like:

stackView.addBackground(color: .red)
Swordcraft answered 19/1, 2016 at 4:10 Comment(7)
this is quite wrong. it's a rare case where Paul's article on HackingWithSwift is totally incorrect. you simply add another view (which is not one of the arranged views) and that's the answer.Reagan
here's an article that explains it, it is trivial: useyourloaf.com/blog/stack-view-background-colorReagan
Then what is the use of stackview, can't we simply add our views inside another view and give it constraints to align it either horizontally or vertically and that way totally avoid stackview. Isn't it ridiculous to have views inside stackview and that stackview inside UIView and then add this UIView in our component.Marylynnmarylynne
At least, drawRect() is called. You can simply test by subclassing UIStackViewFucus
Great answer. I've expanded it a little bit, because I have several themes in my app so the background color might change. In order to prevent adding a subview each time the color changes, I change the tag of the subview to 123, then each time the function is called I first check if one of the subviews has tag 123 like this if let backgroundView = self.subviews.first(where: {$0.tag == 123}) {. If so, I change the background color of that view.Jadwiga
How to set the background color of the stackView if the stackView is in UITableViewCell ?Phagocyte
in iOS14 you can set the background color useyourloaf.com/blog/stack-view-background-color-in-ios-14Quintuplicate
P
48

I do it like this:

@IBDesignable
class StackView: UIStackView {
   @IBInspectable private var color: UIColor?
    override var backgroundColor: UIColor? {
        get { return color }
        set {
            color = newValue
            self.setNeedsLayout() // EDIT 2017-02-03 thank you @BruceLiu
        }
    }

    private lazy var backgroundLayer: CAShapeLayer = {
        let layer = CAShapeLayer()
        self.layer.insertSublayer(layer, at: 0)
        return layer
    }()
    override func layoutSubviews() {
        super.layoutSubviews()
        backgroundLayer.path = UIBezierPath(rect: self.bounds).cgPath
        backgroundLayer.fillColor = self.backgroundColor?.cgColor
    }
}

Works like a charm

Piers answered 27/9, 2016 at 8:54 Comment(8)
This does not work for me when used in a storyboard, including the updated answer with backgroundColor get/set methods. :-/ (iOS 10.2.1, Xcode 8.2.1, on iPad)Acetate
Apologies, I figured it out. Interface Builder left out the "module" field below the class so didn't load it. Fix that, then set background in view controller's viewDidLoad fixed it.Acetate
also make your class IBDesignable -> @IBDesignable class StackView Which will allow you to design on Story board. I dont care much for adding a background at run time, but it is very hard to design the stackview when it has no color on story boardLabdanum
@Reagan Care to explain as to Why it's a bad approach?Piers
perhaps Very Bad is a bit harsh @Piers :) :) but there's no need to play with the layers, since you just add another view (but not an Arranged one)Reagan
@Reagan Id say a CALayer is better for this purpose than an entire UIView which is basically just a wrapper for CALayerPiers
@Piers i made it IBDesignable Hope you Like it : )Swoosh
@Swoosh IBInspectable cannot be private nor of type Optional ;)Piers
R
39

UIStackView is a non-rendering element, and as such, it does not get drawn on the screen. This means that changing backgroundColor essentially does nothing. If you want to change the background color, just add a UIView to it as a subview (that is not arranged) like below:

extension UIStackView {

    func addBackground(color: UIColor) {
        let subview = UIView(frame: bounds)
        subview.backgroundColor = color
        subview.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        insertSubview(subview, at: 0)
    }

}
Rusk answered 17/10, 2017 at 7:7 Comment(2)
this answer is also perfect. personally I put in all four constraints (as in the answer of kurrodu )Reagan
Assuming you don’t change the background color this is fine. But if you call this method multiple times the views from the previous times still remain. Also there is no way to easily remove the background color with this method.Sauls
H
21

It's worth pointing out that starting with iOS 14, UIStackViews do render background colours. You can either set the background of the UIStackView from the Storyboard with the Background property.

enter image description here

Or in code with:

if #available(iOS 14.0, *) {
    stackView.backgroundColor = .green
} else {
    // Fallback for older versions of iOS
}
Hydrastine answered 17/9, 2020 at 0:44 Comment(0)
H
15

Maybe the easiest, more readable and less hacky way would be to embed the UIStackView into a UIView and set the background color to the view.

And don't forget to configure properly the Auto Layout constraints between those two views… ;-)

Herrle answered 12/2, 2017 at 15:35 Comment(2)
Yes but where's the fun in that? :-)Acetate
The best solution by far - it works in Interface builder alone, without a line of codeElishaelision
G
11

Pitiphong is correct, to get a stackview with a background color do something like the following...

  let bg = UIView(frame: stackView.bounds)
  bg.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  bg.backgroundColor = UIColor.red

  stackView.insertSubview(bg, at: 0)

This will give you a stackview whose contents will be placed on a red background.

To add padding to the stackview so the contents aren't flush with the edges, add the following in code or on the storyboard...

  stackView.isLayoutMarginsRelativeArrangement = true
  stackView.layoutMargins = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
Galagalactagogue answered 16/2, 2017 at 18:2 Comment(4)
I needed to use stackView.insertSubview(bg, belowSubview: stackView.arrangedSubviews[0]) Otherwise the coloured view was placed on top of the stack.Etherege
this answer is almost correct, but wrong - you just use insert at zero.Reagan
@Etherege - you simply use insert at zero.Reagan
Note sample code edited to do insert at 0 such that view is last in the view hierarchy. (I did say do something like the following...)Galagalactagogue
W
9

TL;DR: The official way to do this is by adding an empty view into stack view using addSubview: method and set the added view background instead.

The explanation: UIStackView is a special UIView subclass that only do the layout not drawing. So many of its properties won't work as usual. And since UIStackView will layout its arranged subviews only, this mean that you can simply add it a UIView with addSubview: method, set its constraints and background color. This is the official way to achieve what you want quoted from WWDC session

Wainscoting answered 15/2, 2017 at 17:49 Comment(0)
S
4

This works for me in Swift 3 and iOS 10:

let stackView = UIStackView()
let subView = UIView()
subView.backgroundColor = .red
subView.translatesAutoresizingMaskIntoConstraints = false
stackView.addSubview(subView) // Important: addSubview() not addArrangedSubview()

// use whatever constraint method you like to 
// constrain subView to the size of stackView.
subView.topAnchor.constraint(equalTo: stackView.topAnchor).isActive = true
subView.bottomAnchor.constraint(equalTo: stackView.bottomAnchor).isActive = true
subView.leftAnchor.constraint(equalTo: stackView.leftAnchor).isActive = true
subView.rightAnchor.constraint(equalTo: stackView.rightAnchor).isActive = true

// now add your arranged subViews...
stackView.addArrangedSubview(button1)
stackView.addArrangedSubview(button2)
Salomesalomi answered 15/5, 2017 at 23:0 Comment(0)
R
3

In iOS10, @Arbitur's answer needs a setNeedsLayout after color is set. This is the change which is needed:

override var backgroundColor: UIColor? {
    get { return color }
    set { 
        color = newValue
        setNeedsLayout()
    }
}
Reversion answered 22/12, 2016 at 6:46 Comment(1)
Depends on how you set things up, if you set the backgroundColor before you add it to a superview it works on ios10 and ios9, however, if you updated the backgroundColor after its layoutSubviews() function was called it simply wouldnt update the backgroundColor of the backgroundLayer meaning no visual update. Fixed now, thanks for pointing it out.Piers
O
3

Here is a brief overview for adding a Stack view Background Color.

class RevealViewController: UIViewController {

    @IBOutlet private weak var rootStackView: UIStackView!

Creating 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.

private func pinBackground(_ view: UIView, to stackView: UIStackView) {
    view.translatesAutoresizingMaskIntoConstraints = false
    stackView.insertSubview(view, at: 0)
    view.pin(to: stackView)
}

Add constraints to pin the backgroundView to the edges of the stack view, by using a small extension on UIView.

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)
      ])
  }
}

call the pinBackground from viewDidLoad

override func viewDidLoad() {
  super.viewDidLoad()
  pinBackground(backgroundView, to: rootStackView)
}

Reference from: HERE

Odetteodeum answered 19/7, 2017 at 8:43 Comment(0)
H
2

Xamarin, C# version:

var stackView = new UIStackView { Axis = UILayoutConstraintAxis.Vertical };

UIView bg = new UIView(stackView.Bounds);
bg.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
bg.BackgroundColor = UIColor.White;
stackView.AddSubview(bg);
Heartsick answered 23/8, 2018 at 9:44 Comment(0)
S
2

You could make a small extension of UIStackView

extension UIStackView {
    func setBackgroundColor(_ color: UIColor) {
        let backgroundView = UIView(frame: .zero)
        backgroundView.backgroundColor = color
        backgroundView.translatesAutoresizingMaskIntoConstraints = false
        self.insertSubview(backgroundView, at: 0)
        NSLayoutConstraint.activate([
            backgroundView.topAnchor.constraint(equalTo: self.topAnchor),
            backgroundView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
            backgroundView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
            backgroundView.trailingAnchor.constraint(equalTo: self.trailingAnchor)
            ])
    }
}

Usage:

yourStackView.setBackgroundColor(.black)
Substrate answered 26/1, 2019 at 21:43 Comment(0)
H
0
UIStackView *stackView;
UIView *stackBkg = [[UIView alloc] initWithFrame:CGRectZero];
stackBkg.backgroundColor = [UIColor redColor];
[self.view insertSubview:stackBkg belowSubview:stackView];
stackBkg.translatesAutoresizingMaskIntoConstraints = NO;
[[stackBkg.topAnchor constraintEqualToAnchor:stackView.topAnchor] setActive:YES];
[[stackBkg.bottomAnchor constraintEqualToAnchor:stackView.bottomAnchor] setActive:YES];
[[stackBkg.leftAnchor constraintEqualToAnchor:stackView.leftAnchor] setActive:YES];
[[stackBkg.rightAnchor constraintEqualToAnchor:stackView.rightAnchor] setActive:YES];
Harner answered 29/3, 2017 at 22:3 Comment(0)
M
0

Subclass UIStackView

class CustomStackView : UIStackView {

private var _bkgColor: UIColor?
override public var backgroundColor: UIColor? {
    get { return _bkgColor }
    set {
        _bkgColor = newValue
        setNeedsLayout()
    }
}

private lazy var backgroundLayer: CAShapeLayer = {
    let layer = CAShapeLayer()
    self.layer.insertSublayer(layer, at: 0)
    return layer
}()

override public func layoutSubviews() {
    super.layoutSubviews()
    backgroundLayer.path = UIBezierPath(rect: self.bounds).cgPath
    backgroundLayer.fillColor = self.backgroundColor?.cgColor
}
}

Then in your class

yourStackView.backgroundColor = UIColor.lightGray
Misbeliever answered 12/10, 2018 at 14:20 Comment(0)
T
0

You can insert a sublayer to StackView, it works to me:

@interface StackView ()
@property (nonatomic, strong, nonnull) CALayer *ly;
@end

@implementation StackView

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        _ly = [CALayer new];
        [self.layer addSublayer:_ly];
    }
    return self;
}

- (void)setBackgroundColor:(UIColor *)backgroundColor {
    [super setBackgroundColor:backgroundColor];
    self.ly.backgroundColor = backgroundColor.CGColor;
}

- (void)layoutSubviews {
    self.ly.frame = self.bounds;
    [super layoutSubviews];
}

@end
Tenpin answered 8/11, 2018 at 7:9 Comment(0)
S
0

I am little bit sceptical in Subclassing UI components. This is how I am using it,

struct CustomAttributeNames{
        static var _backgroundView = "_backgroundView"
    }

extension UIStackView{

var backgroundView:UIView {
        get {
            if let view = objc_getAssociatedObject(self, &CustomAttributeNames._backgroundView) as? UIView {
                return view
            }
            //Create and add
            let view = UIView(frame: .zero)
            view.translatesAutoresizingMaskIntoConstraints = false
            insertSubview(view, at: 0)
            NSLayoutConstraint.activate([
              view.topAnchor.constraint(equalTo: self.topAnchor),
              view.leadingAnchor.constraint(equalTo: self.leadingAnchor),
              view.bottomAnchor.constraint(equalTo: self.bottomAnchor),
              view.trailingAnchor.constraint(equalTo: self.trailingAnchor)
            ])

            objc_setAssociatedObject(self,
                                     &CustomAttributeNames._backgroundView,
                                     view,
                                     objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)

            return view
        }
    }
}

And this is the usage,

stackView.backgroundView.backgroundColor = .white
stackView.backgroundView.layer.borderWidth = 2.0
stackView.backgroundView.layer.borderColor = UIColor.red.cgColor
stackView.backgroundView.layer.cornerRadius = 4.0

Note: With this approach, if you want to set border, you have to set layoutMargins on the stackView so that the border is visible.

Springclean answered 11/3, 2019 at 8:19 Comment(0)
M
0

You can't add background to stackview. But what you can do is adding stackview in a view and then set background of view this will get the job done. *It will not gonna interrupt the flows of stackview. Hope this will help.

Minny answered 1/10, 2019 at 9:30 Comment(0)
B
0

We can have a custom class StackView like this:

class StackView: UIStackView {
    lazy var backgroundView: UIView = {
        let otherView = UIView()
        addPinedSubview(otherView)
        return otherView
    }()
}

extension UIView {
    func addPinedSubview(_ otherView: UIView) {
        addSubview(otherView)
        otherView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            otherView.trailingAnchor.constraint(equalTo: trailingAnchor),
            otherView.topAnchor.constraint(equalTo: topAnchor),
            otherView.heightAnchor.constraint(equalTo: heightAnchor),
            otherView.widthAnchor.constraint(equalTo: widthAnchor),
        ])
    }
}

And it can be used like this:

let stackView = StackView()
stackView.backgroundView.backgroundColor = UIColor.lightGray

This is slightly better than adding an extension function func addBackground(color: UIColor) as suggested by others. The background view is lazy so that it won't be created until you actually call stackView.backgroundView.backgroundColor = .... And setting/changing the background color multiple times won't result in multiple subviews being inserted in the stack view.

Bronchia answered 10/11, 2019 at 0:48 Comment(0)
M
0

If you want to control from designer itself , add this extension to stack view

 @IBInspectable var customBackgroundColor: UIColor?{
     get{
        return backgroundColor
     }
     set{
        backgroundColor = newValue
         let subview = UIView(frame: bounds)
         subview.backgroundColor = newValue
         subview.autoresizingMask = [.flexibleWidth, .flexibleHeight]
         insertSubview(subview, at: 0)
     }
 }
Musicale answered 26/4, 2020 at 12:19 Comment(0)
R
0

There's good answers but i found them not complete so here is my version based on best of them:

    /// This extension addes missing background color to stack views on iOS 13 and earlier
extension UIStackView {
    
    private struct CustomAttributeNames {
        static var _backgroundView = "_backgroundView"
    }
    
    @IBInspectable var customBackgroundColor: UIColor? {
        get { backgroundColor }
        set { setBackgroundColor(newValue) }
    }
    
    var backgroundView: UIView {
        if let view = objc_getAssociatedObject(self, &CustomAttributeNames._backgroundView) as? UIView {
            return view
        }
        
        let view = UIView(frame: bounds)
        view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        
        insertSubview(view, at: 0)
        
        objc_setAssociatedObject(self,
                                 &CustomAttributeNames._backgroundView,
                                 view,
                                 objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        
        return view
    }
    
    func setBackgroundColor(_ color: UIColor?) {
        backgroundColor = color
        backgroundView.backgroundColor = color
    }
}
Resolutive answered 1/7, 2021 at 11:45 Comment(0)
L
-1

You could do it like this:

stackView.backgroundColor = UIColor.blue

By providing an extension to override the backgroundColor:

extension UIStackView {

    override open var backgroundColor: UIColor? {

        get {
            return super.backgroundColor
        }

        set {

            super.backgroundColor = newValue

            let tag = -9999
            for view in subviews where view.tag == tag {
                view.removeFromSuperview()
            }

            let subView = UIView()
            subView.tag = tag
            subView.backgroundColor = newValue
            subView.translatesAutoresizingMaskIntoConstraints = false
            self.addSubview(subView)
            subView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
            subView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
            subView.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
            subView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
        }

    }

}
Lukewarm answered 4/7, 2017 at 11:57 Comment(1)
you have to set the correct position of the subview, use insert atReagan
S
-1

The explanation from the Apple documentation is that a stack view is never itself rendered in iOS 13 - it’s purpose is to manage its arranged subviews:

The UIStackView is a nonrendering subclass of UIView; that is, it does not provide any user interface of its own. Instead, it just manages the position and size of its arranged views. As a result, some properties (like backgroundColor) have no effect on the stack view.

You could fix this by creating an extension just for fixing the background color in iOS 13 or below:

import UIKit

extension UIStackView {
    // MARK: Stored properties

    private enum Keys {
        static var backgroundColorView: UInt8 = 0
    }

    private var backgroundColorView: UIView? {
        get {
            objc_getAssociatedObject(self, &Keys.backgroundColorView) as? UIView
        }
        set {
            objc_setAssociatedObject(self, &Keys.backgroundColorView, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    override open var backgroundColor: UIColor? {
        didSet {
            // UIKit already support setting background color in iOS 14 or above
            guard #available(iOS 14.0, *) else {
                // fix setting background color directly to stackview by add a background view
                if backgroundColorView == nil {
                    let backgroundColorView = UIView(frame: bounds)
                    backgroundColorView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
                    insertSubview(backgroundColorView, at: 0)
                    self.backgroundColorView = backgroundColorView
                }
                backgroundColorView?.backgroundColor = backgroundColor
                return
            }
        }
    }
}
Screwdriver answered 26/6, 2022 at 13:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.