¿Cómo cambiar el color de fondo de UIStackView?

159

Intenté cambiar el UIStackViewfondo de claro a blanco en el inspector de Storyboard, pero al simular, el color de fondo de la vista de pila todavía tiene un color claro.
¿Cómo puedo cambiar el color de fondo de a UIStackView?

LukasTong
fuente
2
Este es uno de esos casos raros en SO, donde las respuestas son simplemente totalmente incorrectas . solo agrega una ... vista de fondo. es muy simple. artículo de ejemplo que lo explica: useyourloaf.com/blog/stack-view-background-color
Fattie
@Fattie ¡Funcionó! ¿Puedes agregarlo como respuesta también?
absin
hola @AbSin: las respuestas de kurrodu y MariánČerný son perfectas.
Fattie
Jaja, esto es genial, el elemento no renderizado tiene una propiedad de color de fondo
Brad Thomas

Respuestas:

289

No puede hacer esto: UIStackViewes una vista sin dibujo, lo que significa que drawRect()nunca se llama y se ignora su color de fondo. Si desea desesperadamente un color de fondo, considere colocar la vista de pila dentro de otra UIViewy darle a esa vista un color de fondo.

Referencia desde AQUÍ .

EDITAR:

Puede agregar una subvista UIStackViewcomo se menciona AQUÍ o en esta respuesta (a continuación) y asignarle un color. Echa un vistazo a continuación extensionpara eso:

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

Y puedes usarlo como:

stackView.addBackground(color: .red)
Dharmesh Kheni
fuente
12
Esto está bastante mal . Es un caso raro donde el artículo de Paul sobre HackingWithSwift es totalmente incorrecto . simplemente agrega otra vista (que no es una de las vistas organizadas ) y esa es la respuesta.
Fattie
11
Aquí hay un artículo que lo explica, es trivial: useyourloaf.com/blog/stack-view-background-color
Fattie
1
Entonces, ¿cuál es el uso de stackview? ¿No podemos simplemente agregar nuestras vistas dentro de otra vista y darle restricciones para alinearlo horizontal o verticalmente y así evitar totalmente stackview? ¿No es ridículo tener vistas dentro de stackview y esa stackview dentro de UIView y luego agregar esta UIView en nuestro componente?
Shivam Pokhriyal
Al menos, drawRect()se llama. Simplemente puede probar subclasificandoUIStackView
khcpietro
Gran respuesta. Lo expandí un poco, porque tengo varios temas en mi aplicación, por lo que el color de fondo puede cambiar. Para evitar agregar una subvista cada vez que cambia el color, cambio la etiqueta de la subvista a 123, luego, cada vez que se llama a la función, primero verifico si una de las subvistas tiene la etiqueta 123 como esta if let backgroundView = self.subviews.first(where: {$0.tag == 123}) {. Si es así, cambio el color de fondo de esa vista.
guido
47

Lo hago así:

@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
    }
}

Funciona de maravilla

Arbitur
fuente
Esto no funciona para mí cuando se usa en un guión gráfico, incluida la respuesta actualizada con los métodos get / set de backgroundColor. : - / (iOS 10.2.1, Xcode 8.2.1, en iPad)
webjprgm
Disculpas, lo descubrí. Interface Builder omitió el campo "módulo" debajo de la clase, así que no lo cargó. Solucione eso, luego configure el fondo en la vista del controlador viewDidLoad lo arregló.
webjprgm
1
también haga que su clase sea IBDesignable -> @IBDesignable class StackViewLo que le permitirá diseñar en Storyboard . No me importa mucho agregar un fondo en tiempo de ejecución, pero es muy difícil diseñar la vista de pila cuando no tiene color en el
storyboard
@Fattie Care para explicar por qué es un mal enfoque?
Arbitur
quizás Very Bad es un poco duro @Arbitur :) :) pero no hay necesidad de jugar con las capas, ya que solo agregas otra vista (pero no una Arreglada)
Fattie
34

UIStackViewes un elemento que no se representa y, como tal, no se dibuja en la pantalla. Esto significa que cambiar backgroundColoresencialmente no hace nada. Si desea cambiar el color de fondo, simplemente agregue un UIViewcomo subvista (que no está organizado) como a continuación:

extension UIStackView {

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

}
Marián Černý
fuente
1
Esta respuesta también es perfecta. personalmente puse las cuatro restricciones (como en la respuesta de kurrodu)
Fattie
1
Suponiendo que no cambie el color de fondo, está bien. Pero si llama a este método varias veces, las vistas de los tiempos anteriores aún permanecen. Además, no hay forma de eliminar fácilmente el color de fondo con este método.
keji
11

Quizás la forma más fácil, más legible y menos hacky sería incrustar el UIStackViewen UIViewy establecer el color de fondo a la vista.

Y no olvide configurar correctamente las restricciones de diseño automático entre esas dos vistas ... ;-)

MonsieurDart
fuente
2
Sí, pero ¿dónde está la diversión en eso? :-)
webjprgm
1
La mejor solución con diferencia: funciona solo en el generador de interfaces, sin una línea de código
Vladimir Grigorov
10

Pitiphong es correcto, para obtener una vista de pila con un color de fondo, haga algo como lo siguiente ...

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

  stackView.insertSubview(bg, at: 0)

Esto le dará una vista de pila cuyo contenido se colocará sobre un fondo rojo.

Para agregar relleno a la vista de pila para que los contenidos no estén alineados con los bordes, agregue lo siguiente en el código o en el guión gráfico ...

  stackView.isLayoutMarginsRelativeArrangement = true
  stackView.layoutMargins = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
Michael Long
fuente
2
Necesitaba usar stackView.insertSubview (bg, belowSubview: stackView.arrangedSubviews [0]) De lo contrario, la vista en color se colocaba encima de la pila.
iCyberPaul
esta respuesta es casi correcta, pero incorrecta : solo usa insertar en cero.
Fattie
1
@iCyberPaul: simplemente usa insertar en cero.
Fattie
Observe el código de muestra editado para insertar en 0 de modo que la vista sea la última en la jerarquía de vistas. (Dije que haga algo como lo siguiente ...)
Michael Long
8

TL; DR: La forma oficial de hacerlo es agregando una vista vacía a la vista de pila usando el addSubview:método y estableciendo el fondo de la vista agregada.

La explicación: UIStackView es una subclase especial de UIView que solo hace que el diseño no se dibuje. Muchas de sus propiedades no funcionarán como de costumbre. Y dado que UIStackView diseñará solo sus subvistas organizadas, esto significa que simplemente puede agregarle un UIView con addSubview:método, establecer sus restricciones y color de fondo. Esta es la forma oficial de lograr lo que quiere citado de la sesión de WWDC

Pitiphong Phongpattranont
fuente
3

Esto funciona para mí en Swift 3 y 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)
Murray Sagal
fuente
3

En iOS10, la respuesta de @ Arbitur necesita un setNeedsLayout después de configurar el color. Este es el cambio que se necesita:

override var backgroundColor: UIColor? {
    get { return color }
    set { 
        color = newValue
        setNeedsLayout()
    }
}
BruceLiu
fuente
Depende de cómo configure las cosas, si configura el backgroundColorantes de agregarlo a un superviewfunciona en ios10 y ios9, sin embargo, si actualizó el backgroundColordespués de layoutSubviews()que se llamara su función, simplemente no actualizaría backgroundColorel backgroundLayersignificado de ninguna actualización visual. Solucionado ahora, gracias por señalarlo.
Arbitur
2

Aquí hay una breve descripción general para agregar una vista de pila Color de fondo.

class RevealViewController: UIViewController {

    @IBOutlet private weak var rootStackView: UIStackView!

Crear vista de fondo con esquinas redondeadas

private lazy var backgroundView: UIView = {
    let view = UIView()
    view.backgroundColor = .purple
    view.layer.cornerRadius = 10.0
    return view
}()

Para que aparezca como fondo, lo agregamos a la matriz de subvistas de la vista de pila raíz en el índice 0. Eso lo coloca detrás de las vistas organizadas de la vista de pila.

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

Agregue restricciones para anclar el backgroundView a los bordes de la vista de la pila, utilizando una pequeña extensión en 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)
      ])
  }
}

llamar al pinBackgrounddeviewDidLoad

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

Referencia de: AQUÍ

kurrodu
fuente
2

Podrías hacer una pequeña extensión de 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)
            ])
    }
}

Uso:

yourStackView.setBackgroundColor(.black)
Nhon Nguyen
fuente
1

Xamarin, versión C #:

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);
zuko
fuente
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];
Solodyashkin romano
fuente
0

Subclase 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
}
}

Luego en tu clase

yourStackView.backgroundColor = UIColor.lightGray
Zeeshan
fuente
0

Puede insertar una subcapa en StackView, me funciona:

@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
wlgemini
fuente
0

Soy un poco escéptico en Subclasificar los componentes de la interfaz de usuario. Así es como lo estoy usando,

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

Y este es el uso,

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

Nota: con este enfoque, si desea establecer el borde, debe establecerlo layoutMarginsen stackView para que el borde sea visible.

Bucle infinito
fuente
0

No puede agregar fondo a stackview. Pero lo que puede hacer es agregar stackview en una vista y luego establecer el fondo de vista, esto hará el trabajo. * No va a interrumpir los flujos de stackview. Espero que esto ayude.

Noman Haroon
fuente
0

Podemos tener una clase personalizada StackViewcomo esta:

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

Y se puede usar así:

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

Esto es un poco mejor que agregar una función de extensión func addBackground(color: UIColor)como lo sugieren otros. La vista de fondo es diferida, por lo que no se creará hasta que realmente llame stackView.backgroundView.backgroundColor = .... Y configurar / cambiar el color de fondo varias veces no resultará en múltiples subvistas insertadas en la vista de pila.

Yuchen Zhong
fuente
0

Si desea controlar desde el propio diseñador, agregue esta extensión a la vista de pila

 @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)
     }
 }
Milind Chaudhary
fuente
-1

Podrías hacerlo así:

stackView.backgroundColor = UIColor.blue

Al proporcionar una extensión para anular el 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
        }

    }

}
nmdias
fuente
tiene que establecer la posición correcta de la subvista, use insertar en
Fattie
-1

Prueba BoxView . Es similar a UIStackView, pero está renderizando y admite más opciones de diseño.

Vladimir
fuente