Comment changer la couleur de fond d'UIStackView?

159

J'ai essayé de changer l' UIStackViewarrière - plan de clair à blanc dans l'inspecteur Storyboard, mais lors de la simulation, la couleur d'arrière-plan de la vue de la pile a toujours une couleur claire.
Comment puis-je changer la couleur d'arrière-plan d'un UIStackView?

LukasTong
la source
2
c'est l'un de ces rares cas sur SO, où les réponses sont tout simplement totalement fausses . vous ajoutez simplement une vue en arrière-plan c'est très simple. exemple d'article l'expliquant: useyourloaf.com/blog/stack-view-background-color
Fattie
@Fattie Ça a marché! Pouvez-vous aussi l'ajouter comme réponse?
absin
salut @AbSin - les réponses de kurrodu et MariánČerný sont parfaites.
Fattie
Haha, c'est génial, l'élément sans rendu a une propriété de couleur d'arrière-plan
Brad Thomas

Réponses:

289

Vous ne pouvez pas faire cela - UIStackViewest une vue sans dessin, ce qui signifie qu'elle drawRect()n'est jamais appelée et que sa couleur d'arrière-plan est ignorée. Si vous voulez désespérément une couleur d'arrière-plan, envisagez de placer la vue de pile dans une autre UIViewet de donner à cette vue une couleur d'arrière-plan.

Référence de ICI .

ÉDITER:

Vous pouvez ajouter une sous-vue à UIStackViewcomme mentionné ICI ou dans cette réponse (ci-dessous) et lui attribuer une couleur. Découvrez ci-dessous extensionpour cela:

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

Et vous pouvez l'utiliser comme:

stackView.addBackground(color: .red)
Dharmesh Kheni
la source
12
c'est tout à fait faux . c'est un cas rare où l'article de Paul sur HackingWithSwift est totalement incorrect . vous ajoutez simplement une autre vue (qui n'est pas l' une des vues arrangées ) et c'est la réponse.
Fattie
11
voici un article qui l'explique, c'est trivial: useyourloaf.com/blog/stack-view-background-color
Fattie
1
Alors quelle est l'utilité de stackview, ne pouvons-nous pas simplement ajouter nos vues dans une autre vue et lui donner des contraintes pour l'aligner horizontalement ou verticalement et ainsi éviter totalement stackview. N'est-il pas ridicule d'avoir des vues dans stackview et cette stackview dans UIView, puis d'ajouter cette UIView dans notre composant.
Shivam Pokhriyal
Au moins, drawRect()est appelé. Vous pouvez simplement tester en sousUIStackView
classant
Très bonne réponse. Je l'ai un peu développé, car j'ai plusieurs thèmes dans mon application, donc la couleur d'arrière-plan peut changer. Afin d'éviter d'ajouter une sous-vue à chaque fois que la couleur change, je change la balise de la sous-vue en 123, puis chaque fois que la fonction est appelée, je vérifie d'abord si l'une des sous-vues a une balise 123 comme celle-ci if let backgroundView = self.subviews.first(where: {$0.tag == 123}) {. Si tel est le cas, je change la couleur d'arrière-plan de cette vue.
guido
47

Je fais ça comme ça:

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

Fonctionne comme un charme

Arbitur
la source
Cela ne fonctionne pas pour moi lorsqu'il est utilisé dans un storyboard, y compris la réponse mise à jour avec les méthodes get / set backgroundColor. : - / (iOS 10.2.1, Xcode 8.2.1, sur iPad)
webjprgm
Toutes mes excuses, je l'ai compris. Interface Builder a omis le champ "module" sous la classe et ne l'a donc pas chargé. Corrigez cela, puis définissez l'arrière-plan dans viewDidLoad du contrôleur de vue.
webjprgm
1
faites également de votre classe IBDesignable -> @IBDesignable class StackViewCe qui vous permettra de concevoir sur Story board. Je ne me soucie pas beaucoup de l'ajout d'un arrière-plan au moment de l'exécution, mais il est très difficile de concevoir le stackview quand il n'a pas de couleur sur le
storyboard
@Fattie Care pour expliquer pourquoi c'est une mauvaise approche?
Arbitur
peut-être que Very Bad est un peu dur @Arbitur :) :) mais il n'est pas nécessaire de jouer avec les calques, car vous ajoutez simplement une autre vue (mais pas une vue arrangée)
Fattie
34

UIStackViewest un élément sans rendu, et en tant que tel, il n'est pas dessiné à l'écran. Cela signifie que changer backgroundColorne fait essentiellement rien. Si vous souhaitez changer la couleur d'arrière-plan, ajoutez-y simplement un UIViewsous-vue (qui n'est pas organisé) comme ci-dessous:

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ý
la source
1
cette réponse est également parfaite. personnellement, je mets les quatre contraintes (comme dans la réponse de kurrodu)
Fattie
1
En supposant que vous ne modifiez pas la couleur d'arrière-plan, c'est très bien. Mais si vous appelez cette méthode plusieurs fois, les vues des temps précédents restent toujours. De plus, il n'y a aucun moyen de supprimer facilement la couleur d'arrière-plan avec cette méthode.
keji
11

Le moyen le plus simple, le plus lisible et le moins piraté serait peut-être d'intégrer le UIStackViewdans un UIViewet de définir la couleur d'arrière-plan de la vue.

Et n'oubliez pas de configurer correctement les contraintes de mise en page automatique entre ces deux vues… ;-)

MonsieurDart
la source
2
Oui mais où est le plaisir là-dedans? :-)
webjprgm
1
La meilleure solution de loin - cela fonctionne dans le constructeur d'interface seul, sans ligne de code
Vladimir Grigorov
10

Pitiphong est correct, pour obtenir une vue de pile avec une couleur d'arrière-plan, faites quelque chose comme ce qui suit ...

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

  stackView.insertSubview(bg, at: 0)

Cela vous donnera une vue de la pile dont le contenu sera placé sur un fond rouge.

Pour ajouter un remplissage à la vue de la pile afin que le contenu ne soit pas aligné avec les bords, ajoutez ce qui suit dans le code ou sur le storyboard ...

  stackView.isLayoutMarginsRelativeArrangement = true
  stackView.layoutMargins = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
Michael Long
la source
2
J'avais besoin d'utiliser stackView.insertSubview (bg, belowSubview: stackView.arrangedSubviews [0]) Sinon, la vue colorée était placée au-dessus de la pile.
iCyberPaul
cette réponse est presque correcte, mais fausse - vous utilisez simplement insérer à zéro.
Fattie
1
@iCyberPaul - vous utilisez simplement insérer à zéro.
Fattie
Notez l'exemple de code modifié pour effectuer une insertion à 0 de sorte que la vue soit la dernière dans la hiérarchie des vues. (J'ai dit de faire quelque chose comme ce qui suit ...)
Michael Long
8

TL; DR: La façon officielle de le faire est d'ajouter une vue vide dans la vue de la pile à l'aide de la addSubview:méthode et de définir à la place l'arrière-plan de la vue ajoutée.

L'explication: UIStackView est une sous-classe UIView spéciale qui ne fait que la mise en page et non le dessin. Tant de ses propriétés ne fonctionneront pas comme d'habitude. Et comme UIStackView ne mettra en page que ses sous-vues organisées, cela signifie que vous pouvez simplement lui ajouter un UIView avec addSubview:méthode, définir ses contraintes et sa couleur d'arrière-plan. C'est le moyen officiel de réaliser ce que vous voulez cité lors de la session WWDC

Pitiphong Phongpattranont
la source
3

Cela fonctionne pour moi dans Swift 3 et 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
la source
3

Dans iOS10, la réponse de @ Arbitur nécessite un setNeedsLayout une fois la couleur définie. Voici le changement nécessaire:

override var backgroundColor: UIColor? {
    get { return color }
    set { 
        color = newValue
        setNeedsLayout()
    }
}
BruceLiu
la source
Cela dépend de la façon dont vous configurez les choses, si vous définissez le backgroundColoravant de l'ajouter à un, superviewcela fonctionne sur ios10 et ios9, cependant, si vous avez mis à jour backgroundColoraprès que sa layoutSubviews()fonction a été appelée, cela ne mettrait tout simplement pas à jour backgroundColorla backgroundLayersignification aucune mise à jour visuelle. Corrigé maintenant, merci de l'avoir signalé.
Arbitur
2

Voici un bref aperçu de l'ajout d'une couleur d'arrière-plan de la vue Pile.

class RevealViewController: UIViewController {

    @IBOutlet private weak var rootStackView: UIStackView!

Création d'une vue d'arrière-plan avec des coins arrondis

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

Pour le faire apparaître comme arrière-plan, nous l'ajoutons au tableau de sous-vues de la vue de pile racine à l'index 0. Cela le place derrière les vues arrangées de la vue de pile.

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

Ajoutez des contraintes pour épingler le backgroundView aux bords de la vue de pile, en utilisant une petite extension sur 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)
      ])
  }
}

appeler le pinBackgrounddeviewDidLoad

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

Référence de: ICI

Kurrodu
la source
2

Xamarin, version 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
la source
2

Vous pourriez faire une petite extension 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)
            ])
    }
}

Usage:

yourStackView.setBackgroundColor(.black)
Nhon Nguyen
la source
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];
Roman Solodyashkin
la source
0

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

Puis dans ta classe

yourStackView.backgroundColor = UIColor.lightGray
Zeeshan
la source
0

Vous pouvez insérer une sous-couche dans StackView, cela fonctionne pour moi:

@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
la source
0

Je suis un peu sceptique quant au sous-classement des composants de l'interface utilisateur. C'est comme ça que je l'utilise,

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

Et c'est l'usage,

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

Remarque: Avec cette approche, si vous souhaitez définir une bordure, vous devez la définir layoutMarginssur stackView afin que la bordure soit visible.

boucle infinie
la source
0

Vous ne pouvez pas ajouter d'arrière-plan à stackview. Mais ce que vous pouvez faire, c'est ajouter stackview dans une vue, puis définir l'arrière-plan de la vue, cela fera le travail. * Cela n'interrompra pas les flux de stackview. J'espère que cela aidera.

Noman Haroon
la source
0

Nous pouvons avoir une classe personnalisée StackViewcomme celle-ci:

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

Et il peut être utilisé comme ceci:

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

C'est légèrement mieux que d'ajouter une fonction d'extension func addBackground(color: UIColor)comme suggéré par d'autres. La vue d'arrière-plan est paresseuse de sorte qu'elle ne sera pas créée tant que vous n'appelerez pas stackView.backgroundView.backgroundColor = .... Et la définition / modification de la couleur d'arrière-plan plusieurs fois n'entraînera pas l'insertion de plusieurs sous-vues dans la vue de la pile.

Yuchen Zhong
la source
0

Si vous souhaitez contrôler à partir du concepteur lui-même, ajoutez cette extension à la vue de la pile

 @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
la source
-1

Vous pouvez le faire comme ceci:

stackView.backgroundColor = UIColor.blue

En fournissant une extension pour remplacer 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
la source
vous devez définir la position correcte de la sous-vue, utilisez insert at
Fattie
-1

Essayez BoxView . Il est similaire à UIStackView, mais il est rendu et prend en charge plus d'options de mise en page.

Vladimir
la source