Jak zmienić kolor tła UIStackView?

158

Próbowałem zmienić UIStackViewtło z przezroczystego na białe w Inspektorze scenorysów, ale podczas symulacji kolor tła widoku stosu nadal ma wyraźny kolor.
Jak mogę zmienić kolor tła UIStackView?

LukasTong
źródło
2
jest to jeden z tych rzadkich przypadków na SO, gdzie odpowiedzi są po prostu całkowicie błędne . wystarczy dodać ... widok tła. to jest bardzo proste. przykładowy artykuł wyjaśniający to: useyourloaf.com/blog/stack-view-background-color
Fattie
@Fattie Działało! Czy możesz to również dodać jako odpowiedź?
absin
cześć @AbSin - odpowiedzi kurrodu i MariánČerný są doskonałe.
Fattie
Haha, to świetnie, element nie renderujący ma właściwość koloru tła
Brad Thomas,

Odpowiedzi:

289

Nie możesz tego zrobić - UIStackViewjest widokiem innym niż rysunkowy, co oznacza, że drawRect()nigdy nie jest wywoływany, a jego kolor tła jest ignorowany. Jeśli bardzo potrzebujesz koloru tła, rozważ umieszczenie widoku stosu wewnątrz innego UIViewi nadanie temu widokowi koloru tła.

Odniesienie z TUTAJ .

EDYTOWAĆ:

Możesz dodać subView do, UIStackViewjak wspomniano TUTAJ lub w tej odpowiedzi (poniżej) i przypisać mu kolor. Sprawdź poniżej extension:

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

Możesz go używać tak, jak:

stackView.addBackground(color: .red)
Dharmesh Kheni
źródło
12
to jest całkiem złe . jest to rzadki przypadek, w którym artykuł Paula na temat HackingWithSwift jest całkowicie błędny . po prostu dodajesz inny widok (który nie jest jednym z widoków ułożonych ) i to jest odpowiedź.
Fattie
11
oto artykuł, który to wyjaśnia, jest trywialny: useyourloaf.com/blog/stack-view-background-color
Fattie
1
W takim razie jaki jest pożytek z widoku stosu, czy nie możemy po prostu dodać naszych widoków do innego widoku i nałożyć na niego ograniczeń, aby wyrównać go w poziomie lub w pionie, a w ten sposób całkowicie uniknąć widoku stosu. Czy to nie śmieszne mieć widoki w stackview i ten stackview w UIView, a następnie dodać ten UIView do naszego komponentu.
Shivam Pokhriyal
Przynajmniej drawRect()jest nazywany. Możesz po prostu przetestować podklasęUIStackView
khcpietro
Świetna odpowiedź. Trochę go rozszerzyłem, ponieważ mam kilka motywów w mojej aplikacji, więc kolor tła może się zmienić. Aby nie dopuścić do dodawania widoku podrzędnego za każdym razem, gdy zmienia się kolor, zmieniam znacznik widoku podrzędnego na 123, a następnie za każdym razem, gdy wywoływana jest funkcja, najpierw sprawdzam, czy któryś z podglądów podrzędnych ma taki tag 123 if let backgroundView = self.subviews.first(where: {$0.tag == 123}) {. Jeśli tak, zmieniam kolor tła tego widoku.
guido
47

Robię to tak:

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

Działa jak marzenie

Arbitur
źródło
Nie działa to w przypadku mnie, gdy jest używane w scenorysie, w tym zaktualizowanej odpowiedzi z metodami get / set backgroundColor. : - / (iOS 10.2.1, Xcode 8.2.1, na iPadzie)
webjprgm
Przepraszam, zrozumiałem. Interface Builder pominął pole „module” poniżej klasy, więc nie załadował go. Napraw to, a następnie ustaw tło w widoku kontrolera viewDidLoad naprawił to.
webjprgm
1
spraw, by Twoja klasa była IBDesignable -> @IBDesignable class StackViewCo pozwoli ci projektować na Story Board. Nie zależy mi zbytnio na dodawaniu tła w czasie wykonywania, ale bardzo trudno jest zaprojektować widok stosu, gdy nie ma on koloru na planszy
FlowUI. SimpleUITesting.com
@Fattie Care, aby wyjaśnić, dlaczego jest to złe podejście?
Arbitur
być może Very Bad jest trochę surowe @Arbitur :) :) ale nie ma potrzeby bawić się warstwami, ponieważ dodajesz tylko inny widok (ale nie aranżowany)
Fattie
34

UIStackViewnie jest elementem renderującym i jako taki nie jest rysowany na ekranie. Oznacza to, że zmiana w backgroundColorzasadzie nic nie robi. Jeśli chcesz zmienić kolor tła, po prostu dodaj UIViewdo niego widok podrzędny (który nie jest ułożony), jak poniżej:

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ý
źródło
1
ta odpowiedź jest również doskonała. osobiście postawiłem na wszystkie cztery ograniczenia (jak w odpowiedzi kurrodu)
Fattie
1
Zakładając, że nie zmieniasz koloru tła, jest to w porządku. Ale jeśli wywołasz tę metodę wiele razy, widoki z poprzednich czasów nadal pozostają. Nie ma również możliwości łatwego usunięcia koloru tła tą metodą.
keji
11

Być może najłatwiejszym, bardziej czytelnym i mniej hakerskim sposobem byłoby osadzenie elementu UIStackVieww a UIViewi ustawienie koloru tła widoku.

I nie zapomnij odpowiednio skonfigurować ograniczeń automatycznego układu między tymi dwoma widokami… ;-)

MonsieurDart
źródło
2
Tak, ale gdzie w tym zabawa? :-)
webjprgm
1
Zdecydowanie najlepsze rozwiązanie - działa w samym konstruktorze interfejsów, bez linii kodu
Vladimir Grigorov
10

Pitiphong ma rację, aby uzyskać widok stosu z kolorem tła, wykonaj coś takiego ...

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

  stackView.insertSubview(bg, at: 0)

W ten sposób otrzymasz widok stosu, którego zawartość zostanie umieszczona na czerwonym tle.

Aby dodać dopełnienie do widoku stosu, aby zawartość nie była równo z krawędziami, dodaj następujący kod w kodzie lub w serii ujęć ...

  stackView.isLayoutMarginsRelativeArrangement = true
  stackView.layoutMargins = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
Michael Long
źródło
2
Musiałem użyć stackView.insertSubview (bg, belowSubview: stackView.arrangedSubviews [0]) W przeciwnym razie kolorowy widok został umieszczony na szczycie stosu.
iCyberPaul
ta odpowiedź jest prawie poprawna, ale zła - po prostu używasz wstawiania na zero.
Fattie
1
@iCyberPaul - po prostu używasz wstawiania na zero.
Fattie
Zwróć uwagę na edytowany przykładowy kod w celu wstawienia go w pozycji 0, tak aby widok był ostatni w hierarchii widoków. (Powiedziałem, zrób coś takiego ...)
Michael Long
8

TL; DR: Oficjalnym sposobem na to jest dodanie pustego widoku do widoku stosu za pomocą addSubview:metody i zamiast tego ustawienie dodanego widoku tła.

Wyjaśnienie: UIStackView to specjalna podklasa UIView, która wykonuje tylko układ, a nie rysuje. Tak wiele jego właściwości nie będzie działać jak zwykle. A ponieważ UIStackView będzie układał tylko swoje uporządkowane podglądy, oznacza to, że możesz po prostu dodać do niego UIView z addSubview:metodą, ustawić jego ograniczenia i kolor tła. To jest oficjalny sposób na osiągnięcie tego, co chcesz cytować z sesji WWDC

Pitiphong Phongpattranont
źródło
3

To działa dla mnie w Swift 3 i 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
źródło
3

W iOS10 odpowiedź @ Arbitur wymaga setNeedsLayout po ustawieniu koloru. Oto zmiana, która jest potrzebna:

override var backgroundColor: UIColor? {
    get { return color }
    set { 
        color = newValue
        setNeedsLayout()
    }
}
BruceLiu
źródło
Zależy jak ustawić rzeczy, jeśli ustawisz backgroundColorprzed dodaniem go do superviewdziała na ios10 i ios9 jednak, jeśli zaktualizowane backgroundColorpo jego layoutSubviews()funkcja została wywołana po prostu nie byłby Aktualizacja backgroundColorz backgroundLayerco oznacza brak wizualnych aktualizacji. Naprawiono teraz, dziękuję za wskazanie tego.
Arbitur
2

Oto krótkie omówienie dodawania koloru tła widoku stosu.

class RevealViewController: UIViewController {

    @IBOutlet private weak var rootStackView: UIStackView!

Tworzenie widoku tła z zaokrąglonymi narożnikami

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

Aby pojawił się jako tło, dodajemy go do tablicy subviews widoku stosu głównego pod indeksem 0. To umieszcza go za ułożonymi widokami widoku stosu.

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

Dodaj ograniczenia, aby przypiąć backgroundView do krawędzi widoku stosu, używając małego rozszerzenia w 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)
      ])
  }
}

zadzwoń pinBackgroundzviewDidLoad

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

Odniesienie z: TUTAJ

kurrodu
źródło
2

Możesz zrobić małe rozszerzenie 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)
            ])
    }
}

Stosowanie:

yourStackView.setBackgroundColor(.black)
Nhon Nguyen
źródło
1

Wersja platformy Xamarin, 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
źródło
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
źródło
0

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

Następnie w twojej klasie

yourStackView.backgroundColor = UIColor.lightGray
Zeeshan
źródło
0

Możesz wstawić podwarstwę do StackView, dla mnie działa:

@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
źródło
0

Jestem trochę sceptyczny w kwestii tworzenia podklas komponentów interfejsu użytkownika. Tak go używam,

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

I to jest użycie,

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

Uwaga: W przypadku tego podejścia, jeśli chcesz ustawić obramowanie, musisz ustawić layoutMarginsw stackView, aby obramowanie było widoczne.

nieskończona pętla
źródło
0

Nie możesz dodać tła do widoku stosu. Ale co możesz zrobić, to dodać widok stosu w widoku, a następnie ustawić tło widoku, aby wykonać zadanie. * Nie zakłóci to przepływu widoku stosu. Mam nadzieję, że to pomoże.

Noman Haroon
źródło
0

Możemy mieć taką klasę niestandardową StackView:

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

Można go używać w następujący sposób:

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

Jest to nieco lepsze niż dodanie funkcji rozszerzenia, func addBackground(color: UIColor)jak sugerują inni. Widok tła jest leniwy, więc nie zostanie utworzony, dopóki nie zadzwonisz stackView.backgroundView.backgroundColor = .... Wielokrotne ustawienie / zmiana koloru tła nie spowoduje wstawienia wielu widoków podrzędnych do widoku stosu.

Yuchen Zhong
źródło
0

Jeśli chcesz sterować z poziomu samego projektanta, dodaj to rozszerzenie do widoku stosu

 @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
źródło
-1

Możesz to zrobić tak:

stackView.backgroundColor = UIColor.blue

Zapewniając rozszerzenie, aby zastąpić 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
źródło
musisz ustawić poprawną pozycję widoku podrzędnego, użyj wstawki na
Fattie
-1

Wypróbuj BoxView . Jest podobny do UIStackView, ale renderuje i obsługuje więcej opcji układu.

Vladimir
źródło