Programowo utwórz widok UIView z gradientem kolorów


Odpowiedzi:


694

Cel C:

UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 50)];
CAGradientLayer *gradient = [CAGradientLayer layer];

gradient.frame = view.bounds;
gradient.colors = @[(id)[UIColor whiteColor].CGColor, (id)[UIColor blackColor].CGColor];

[view.layer insertSublayer:gradient atIndex:0];

Szybki:

let view = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 50))
let gradient = CAGradientLayer()

gradient.frame = view.bounds
gradient.colors = [UIColor.white.cgColor, UIColor.black.cgColor]

view.layer.insertSublayer(gradient, at: 0)

Informacje : użyj startPoint i endPoint, aby zmienić kierunek gradientu .

Jeśli dodano do niego jakiekolwiek inne widoki UIView(takie jak a UILabel), możesz rozważyć ustawienie koloru tła tych elementów UIView, aby [UIColor clearColor]widok gradientu był wyświetlany zamiast koloru tła dla widoków podrzędnych. Korzystanie clearColorma niewielki spadek wydajności.


13
+1 za najkrótszą ilość kodu spośród wszystkich rozwiązań. Właśnie dodałem do mojego istniejącego kontrolera widoku, zmieniłem 2 odnośniki viewna self.viewi działało to jak urok :)
Ergin

4
Ważne jest również, gdzie dodać ten kod. Potrzebowałem następującego postu, aby to zadziałało: stackoverflow.com/a/20059268/1480518
Garrett Disco

13
To mi bardzo pomogło! Brakowało części CGColor wszędzie indziej! Dzięki
Gyfis

48
Nie zapomnij użyć „startPoint” i „endPoint”, aby zmienić kierunek gradientu. Wartości domyślne to (0,5, 0) i (0,5,1) - od góry do dołu.
Valentin Shamardin,

12
Kiedy mój UIView korzysta autoLayout, miałem również zadzwonić gradient.frame = view.boundsw viewDidAppear()aw didRotateFromInterfaceOrientation()albo gradient nie będzie odpowiedniej wielkości.
EricRobertBrewer

70

Możesz utworzyć niestandardową klasę GradientView:

Szybki 5

class GradientView: UIView {
    override open class var layerClass: AnyClass {
       return CAGradientLayer.classForCoder()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        let gradientLayer = layer as! CAGradientLayer
        gradientLayer.colors = [UIColor.white.cgColor, UIColor.black.cgColor]
    }
}

W scenorysie ustaw typ klasy na dowolny widok, który ma mieć tło gradientowe:

wprowadź opis zdjęcia tutaj

Jest to lepsze na następujące sposoby:

  • Nie trzeba ustawiać ramki CLayer
  • Używaj NSConstraintjak zwykle naUIView
  • Nie trzeba tworzyć podwarstw (mniejsze zużycie pamięci)

1
W Swift 3 layerClassnie jest już funkcją, jest właściwością, więc musisz ją zastąpić jak właściwość: stackoverflow.com/questions/28786597/… . Również trzeba wdrożyć override init(frame: CGRect), sprawdź tę odpowiedź: stackoverflow.com/questions/27374330/... . Dzięki!
LaloLoop,

Witaj @LaloLoop, dzięki za komentarz! Zaktualizowałem kod. Nie sądzę jednak, że muszę to zmienić init(frame:CGRect). Przetestowałem zaktualizowany kod i działa dobrze.
Yuchen Zhong,

@Yuchen Zhong Awaria w IB, ale działa w aplikacji. Jakieś powody, dla których nie jest renderowany w IB, jeśli @IBDesignablejest używany?
Alexander Volkov

@AlexanderVolkov Prawdopodobnie jakiś inny problem? Używaliśmy go w taki sposób w scenorysie i nie wydaje się, aby miał to jakiś problem. Możesz debugować scenorys i zobaczyć, która linia go zawiesza.
Yuchen Zhong

1
Najlepsze rozwiązanie!
Baran Emre

40

Spróbuj To działało jak urok dla mnie,

Cel C

Mam ustawiony RGB gradientu kolor tła do UIView

   UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0,0,320,35)];
   CAGradientLayer *gradient = [CAGradientLayer layer];
   gradient.frame = view.bounds;
   gradient.startPoint = CGPointZero;
   gradient.endPoint = CGPointMake(1, 1);
   gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithRed:34.0/255.0 green:211/255.0 blue:198/255.0 alpha:1.0] CGColor],(id)[[UIColor colorWithRed:145/255.0 green:72.0/255.0 blue:203/255.0 alpha:1.0] CGColor], nil];
   [view.layer addSublayer:gradient];

wprowadź opis zdjęcia tutaj

AKTUALIZACJA: - Swift3 +

Kod : -

 var gradientView = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 35))
 let gradientLayer:CAGradientLayer = CAGradientLayer()
 gradientLayer.frame.size = self.gradientView.frame.size
 gradientLayer.colors = 
 [UIColor.white.cgColor,UIColor.red.withAlphaComponent(1).cgColor] 
//Use diffrent colors
 gradientView.layer.addSublayer(gradientLayer)

wprowadź opis zdjęcia tutaj

Możesz dodać punkt początkowy i końcowy koloru gradientu.

  gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
  gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)

wprowadź opis zdjęcia tutaj

Aby uzyskać więcej szczegółowych informacji, patrz CAGradientLayer Doc

Mam nadzieję, że to jest pomoc dla kogoś.


36

To jest moje zalecane podejście.

Aby promować wielokrotne użycie, powiedziałbym, że należy utworzyć kategorię CAGradientLayeri dodać pożądane gradienty jako metody klasowe. Określ je w headerpliku w następujący sposób:

#import <QuartzCore/QuartzCore.h>

@interface CAGradientLayer (SJSGradients)

+ (CAGradientLayer *)redGradientLayer;
+ (CAGradientLayer *)blueGradientLayer;
+ (CAGradientLayer *)turquoiseGradientLayer;
+ (CAGradientLayer *)flavescentGradientLayer;
+ (CAGradientLayer *)whiteGradientLayer;
+ (CAGradientLayer *)chocolateGradientLayer;
+ (CAGradientLayer *)tangerineGradientLayer;
+ (CAGradientLayer *)pastelBlueGradientLayer;
+ (CAGradientLayer *)yellowGradientLayer;
+ (CAGradientLayer *)purpleGradientLayer;
+ (CAGradientLayer *)greenGradientLayer;

@end

Następnie w pliku implementacji określ każdy gradient za pomocą następującej składni:

+ (CAGradientLayer *)flavescentGradientLayer
{
    UIColor *topColor = [UIColor colorWithRed:1 green:0.92 blue:0.56 alpha:1];
    UIColor *bottomColor = [UIColor colorWithRed:0.18 green:0.18 blue:0.18 alpha:1];

    NSArray *gradientColors = [NSArray arrayWithObjects:(id)topColor.CGColor, (id)bottomColor.CGColor, nil];
    NSArray *gradientLocations = [NSArray arrayWithObjects:[NSNumber numberWithInt:0.0],[NSNumber numberWithInt:1.0], nil];

    CAGradientLayer *gradientLayer = [CAGradientLayer layer];
    gradientLayer.colors = gradientColors;
    gradientLayer.locations = gradientLocations;

    return gradientLayer;
}

Następnie po prostu zaimportuj tę kategorię do swojej ViewControllerlub innej wymaganej subclassi użyj jej w następujący sposób:

CAGradientLayer *backgroundLayer = [CAGradientLayer purpleGradientLayer];
backgroundLayer.frame = self.view.frame;
[self.view.layer insertSublayer:backgroundLayer atIndex:0];

gdzie należy umieścić kod, aby go użyć? jaka metoda cyklu życia? viewdidappear sprawia, że ​​pojawia się o sekundę za późno. viewdidload i viewdidappear powodują, że nie działa podczas obracania. viewdidlayoutsubviews działa lepiej dla rotacji, ale nadal wygląda na nieco niespokojną.
Adam Johns

Osobiście to włożyłemviewDidLoad
Sam Fischer,

1
Po włożeniu viewDidLoadnie działało, jeśli aplikacja została uruchomiona w orientacji poziomej. Moim rozwiązaniem było poszerzenie ramki warstwy tła niż ramki view.frame, aby skompensować inne orientacje.
Adam Johns

2
viewDidLoad działa, wystarczy ustawić AutoResizingMask za pomocą [backgroundLayer setAutoresizingMask: UIViewAutoresizingFLEXWidth | UIViewAutoresizingF FlexibleHeight];
ekscrypto

21

Ponieważ potrzebowałem tylko jednego rodzaju gradientu w mojej aplikacji, stworzyłem podklasę UIView i wstępnie skonfigurowałem warstwę gradientu przy inicjalizacji za pomocą stałych kolorów. Inicjatory UIView wywołują metodę configureGradientLayer , która konfiguruje CAGradientLayer:

DDGradientView.h:

#import <UIKit/UIKit.h>

@interface DDGradientView : UIView {

}
@end

DDGradientView.m:

#import "DDGradientView.h"

@implementation DDGradientView

// Change the views layer class to CAGradientLayer class
+ (Class)layerClass
{
    return [CAGradientLayer class];
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if(self) {
        [self configureGradientLayer];
    }
    return self;
}

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if(self) {
        [self configureGradientLayer];
    }
    return self;
}

// Make custom configuration of your gradient here   
- (void)configureGradientLayer {
    CAGradientLayer *gLayer = (CAGradientLayer *)self.layer;
    gLayer.colors = [NSArray arrayWithObjects:(id)[[UIColor whiteColor] CGColor], (id)[[UIColor lightGrayColor] CGColor], nil];
}
@end

Nazwa metody initGradientLayerjest podobna do inicjalizatora. Nie wydaje mi się, żeby to było odpowiednie.
DawnSong,

@DawnSong Dobra uwaga, zmieniłem go na „configureGradientLayer”.
blauzahn

12

Szybkie wdrożenie:

var gradientLayerView: UIView = UIView(frame: CGRectMake(0, 0, view.bounds.width, 50))
var gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = gradientLayerView.bounds
gradient.colors = [UIColor.grayColor().CGColor, UIColor.clearColor().CGColor]
gradientLayerView.layer.insertSublayer(gradient, atIndex: 0)
self.view.layer.insertSublayer(gradientLayerView.layer, atIndex: 0)

11

Rozszerzyłem nieco przyjętą odpowiedź, używając funkcji rozszerzenia Swift oraz wyliczenia.

No i jeśli używasz Storyboard jak to zrobić, upewnij się, aby zadzwonić gradientBackground(from:to:direction:)w viewDidLayoutSubviews()lub później.

Szybki 3

enum GradientDirection {
    case leftToRight
    case rightToLeft
    case topToBottom
    case bottomToTop
}

extension UIView {
    func gradientBackground(from color1: UIColor, to color2: UIColor, direction: GradientDirection) {
        let gradient = CAGradientLayer()
        gradient.frame = self.bounds
        gradient.colors = [color1.cgColor, color2.cgColor]

        switch direction {
        case .leftToRight:
            gradient.startPoint = CGPoint(x: 0.0, y: 0.5)
            gradient.endPoint = CGPoint(x: 1.0, y: 0.5)
        case .rightToLeft:
            gradient.startPoint = CGPoint(x: 1.0, y: 0.5)
            gradient.endPoint = CGPoint(x: 0.0, y: 0.5)
        case .bottomToTop:
            gradient.startPoint = CGPoint(x: 0.5, y: 1.0)
            gradient.endPoint = CGPoint(x: 0.5, y: 0.0)
        default:
            break
        }

        self.layer.insertSublayer(gradient, at: 0)
    }
}

10

Zaimplementowałem to szybko z rozszerzeniem:

Szybki 3

extension UIView {
    func addGradientWithColor(color: UIColor) {
        let gradient = CAGradientLayer()
        gradient.frame = self.bounds
        gradient.colors = [UIColor.clear.cgColor, color.cgColor]

        self.layer.insertSublayer(gradient, at: 0)
    }
}

Swift 2.2

extension UIView {
    func addGradientWithColor(color: UIColor) {
        let gradient = CAGradientLayer()
        gradient.frame = self.bounds
        gradient.colors = [UIColor.clearColor().CGColor, color.CGColor]

        self.layer.insertSublayer(gradient, atIndex: 0)
    }
}

Nie, mogę ustawić gradient dla każdego widoku w ten sposób:

myImageView.addGradientWithColor(UIColor.blue)

Wywołanie Swift3 musi wyglądać następująco: myImageView.addGradientWithColor (kolor: UIColor.blue)
mgyky,

8

Szybkie podejście

Ta odpowiedź opiera się na powyższych odpowiedziach i zapewnia implementację rozwiązania problemu niewłaściwego zastosowania gradientu podczas obrotu. Spełnia ten problem, zmieniając warstwę gradientu na kwadrat, dzięki czemu obrót we wszystkich kierunkach daje prawidłowy gradient. Podpis funkcji zawiera argument wariacyjny Swift, który pozwala przekazać tyle CGColorRef (CGColor), ile potrzeba (patrz przykładowe użycie). Podano również przykład rozszerzenia Swift, dzięki czemu można zastosować gradient do dowolnego UIView.

   func configureGradientBackground(colors:CGColorRef...){

        let gradient: CAGradientLayer = CAGradientLayer()
        let maxWidth = max(self.view.bounds.size.height,self.view.bounds.size.width)
        let squareFrame = CGRect(origin: self.view.bounds.origin, size: CGSizeMake(maxWidth, maxWidth))
        gradient.frame = squareFrame

        gradient.colors = colors
        view.layer.insertSublayer(gradient, atIndex: 0)
    }

Używać:

in viewDidLoad ...

  override func viewDidLoad() {
        super.viewDidLoad()
        configureGradientBackground(UIColor.redColor().CGColor, UIColor.whiteColor().CGColor)
  }

Implementacja rozszerzenia

extension CALayer {


    func configureGradientBackground(colors:CGColorRef...){

        let gradient = CAGradientLayer()

        let maxWidth = max(self.bounds.size.height,self.bounds.size.width)
        let squareFrame = CGRect(origin: self.bounds.origin, size: CGSizeMake(maxWidth, maxWidth))
        gradient.frame = squareFrame

        gradient.colors = colors

        self.insertSublayer(gradient, atIndex: 0)
    }

}

Przykład zastosowania rozszerzenia:

 override func viewDidLoad() {
        super.viewDidLoad()

        self.view.layer.configureGradientBackground(UIColor.purpleColor().CGColor, UIColor.blueColor().CGColor, UIColor.whiteColor().CGColor)
 }

Co oznacza, że ​​tło gradientowe można teraz zastosować do dowolnego UIControl, ponieważ wszystkie kontrolki są UIViews (lub podklasą), a wszystkie UIViews mają CALayers.

Szybki 4

Implementacja rozszerzenia

extension CALayer {
    public func configureGradientBackground(_ colors:CGColor...){

        let gradient = CAGradientLayer()

        let maxWidth = max(self.bounds.size.height,self.bounds.size.width)
        let squareFrame = CGRect(origin: self.bounds.origin, size: CGSize(width: maxWidth, height: maxWidth))
        gradient.frame = squareFrame

        gradient.colors = colors

        self.insertSublayer(gradient, at: 0)
    }    
}

Przykład zastosowania rozszerzenia:

override func viewDidLoad() {
    super.viewDidLoad()

    self.view.layer.configureGradientBackground(UIColor.purple.cgColor, UIColor.blue.cgColor, UIColor.white.cgColor)
}

5

To czego szukasz CAGradientLayer. Każda UIViewma warstwę - do tej warstwy można dodawać podwarstwy, tak jak można dodawać widoki podrzędne. Jednym konkretnym typem jest to CAGradientLayer, w którym dajesz mu tablicę kolorów do stopniowania.

Jednym z przykładów jest to proste opakowanie dla widoku gradientu:

http://oleb.net/blog/2010/04/obgradientview-a-simple-uiview-wrapper-for-cagradientlayer/

Zauważ, że musisz dołączyć ramę QuartZCore, aby uzyskać dostęp do wszystkich części warstw UIView.


4

Swift 4:

Prawidłowo pokazuje gradient w IB:

@IBDesignable public class GradientView: UIView {

    override open class var layerClass: AnyClass {
        return CAGradientLayer.classForCoder()
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        configureGradientLayer()
    }

    public override init(frame: CGRect) {
        super.init(frame: frame)
        configureGradientLayer()
    }

    func configureGradientLayer() {
        let gradientLayer = layer as! CAGradientLayer
        gradientLayer.colors = [UIColor(hex: 0x003399).cgColor, UIColor(hex: 0x00297b).cgColor]
    }
}

4
extension UIView {

    func applyGradient(isVertical: Bool, colorArray: [UIColor]) {
        if let sublayers = layer.sublayers {
            sublayers.filter({ $0 is CAGradientLayer }).forEach({ $0.removeFromSuperlayer() })
        }

        let gradientLayer = CAGradientLayer()
        gradientLayer.colors = colorArray.map({ $0.cgColor })
        if isVertical {
            //top to bottom
            gradientLayer.locations = [0.0, 1.0]
        } else {
            //left to right
            gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
            gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
        }

        backgroundColor = .clear
        gradientLayer.frame = bounds
        layer.insertSublayer(gradientLayer, at: 0)
    }

}

STOSOWANIE

someView.applyGradient(isVertical: true, colorArray: [.green, .blue])

3

Prosty szybki widok oparty na wersji Yuchen

class GradientView: UIView {
    override class func layerClass() -> AnyClass { return CAGradientLayer.self }

    lazy var gradientLayer: CAGradientLayer = {
        return self.layer as! CAGradientLayer
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

}

Następnie możesz użyć gradientLayer po inicjalizacji w ten sposób ...

someView.gradientLayer.colors = [UIColor.whiteColor().CGColor, UIColor.blackColor().CGColor]

3

Moim rozwiązaniem jest utworzenie UIViewpodklasy z CAGradientLayerdostępną właściwością tylko do odczytu. Umożliwi to dostosowanie gradientu tak, jak chcesz i nie musisz samodzielnie obsługiwać zmian układu. Implementacja podklasy:

@interface GradientView : UIView

@property (nonatomic, readonly) CAGradientLayer *gradientLayer;

@end

@implementation GradientView

+ (Class)layerClass
{
    return [CAGradientLayer class];
}

- (CAGradientLayer *)gradientLayer
{
    return (CAGradientLayer *)self.layer;
}

@end

Stosowanie:

self.iconBackground = [GradientView new];
[self.background addSubview:self.iconBackground];
self.iconBackground.gradientLayer.colors = @[(id)[UIColor blackColor].CGColor, (id)[UIColor whiteColor].CGColor];
self.iconBackground.gradientLayer.startPoint = CGPointMake(1.0f, 1.0f);
self.iconBackground.gradientLayer.endPoint = CGPointMake(0.0f, 0.0f);

3

Dobrze jest zadzwonić do powyższych rozwiązań, aby zaktualizować warstwę w Internecie

viewDidLayoutSubviews

aby poprawnie zaktualizować widoki


2

SWIFT 3

Aby dodać warstwę gradientu do swojego widoku

  • Zwiąż swój punkt widokowy

    @IBOutlet var YOURVIEW : UIView!
  • Zdefiniuj CAGradientLayer ()

    var gradient = CAGradientLayer()
  • Oto kod, który musisz napisać w viewDidLoad

    YOURVIEW.layoutIfNeeded()

    gradient.startPoint = CGPoint(x: CGFloat(0), y: CGFloat(1)) gradient.endPoint = CGPoint(x: CGFloat(1), y: CGFloat(0)) gradient.frame = YOURVIEW.bounds gradient.colors = [UIColor.red.cgColor, UIColor.green.cgColor] gradient.colors = [ UIColor(red: 255.0/255.0, green: 56.0/255.0, blue: 224.0/255.0, alpha: 1.0).cgColor,UIColor(red: 86.0/255.0, green: 13.0/255.0, blue: 232.0/255.0, alpha: 1.0).cgColor,UIColor(red: 16.0/255.0, green: 173.0/255.0, blue: 245.0/255.0, alpha: 1.0).cgColor] gradient.locations = [0.0 ,0.6 ,1.0] YOURVIEW.layer.insertSublayer(gradient, at: 0)


1
Zastosowałem to podejście, zmieniając „widok z góry” na nazwę mojego IBoutletu dla mojego widoku. Usunąłem wiersz „SearchView”.
jessi

Dobry plan - być może należy również pamiętać, że można użyć dowolnego z ustawień gradientu. Kolorów. Jeśli więc skomentujesz prosty gradient.colors przy użyciu nazwanych kolorów interfejsu użytkownika, obraz będzie taki sam, ale jeśli skomentujesz inny, gradient koloru będzie wtedy polegał na kolorze czerwonym do zielonego. Użytkownicy mogą zrobić jedno z nich.
jessi

1

W Swift 3.1 dodałem to rozszerzenie do UIView

import Foundation
import UIKit
import CoreGraphics


extension UIView {
    func gradientOfView(withColours: UIColor...) {

        var cgColours = [CGColor]()

        for colour in withColours {
            cgColours.append(colour.cgColor)
        }
        let grad = CAGradientLayer()
        grad.frame = self.bounds
        grad.colors = cgColours
        self.layer.insertSublayer(grad, at: 0)
    }
}

z którym wtedy dzwonię

    class OverviewVC: UIViewController {

        override func viewDidLoad() {
            super.viewDidLoad()

            self.view.gradientOfView(withColours: UIColor.red,UIColor.green, UIColor.blue)

        }
}

0

Zaimplementowałem to w moim kodzie.

UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.view.frame.size.width, 31.0f)];
view1.backgroundColor = [UIColor clearColor];
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = view1.bounds;
UIColor *topColor = [UIColor colorWithRed:132.0/255.0 green:222.0/255.0 blue:109.0/255.0 alpha:1.0];
UIColor *bottomColor = [UIColor colorWithRed:31.0/255.0 green:150.0/255.0 blue:99.0/255.0 alpha:1.0];
gradient.colors = [NSArray arrayWithObjects:(id)[topColor CGColor], (id)[bottomColor CGColor], nil];


[view1.layer insertSublayer:gradient atIndex:0];

Teraz widzę gradient w moim widoku.


0

Aby nadać kolor gradientu UIView (szybki 4.2)

func makeGradientLayer(`for` object : UIView, startPoint : CGPoint, endPoint : CGPoint, gradientColors : [Any]) -> CAGradientLayer {
        let gradient: CAGradientLayer = CAGradientLayer()
        gradient.colors = gradientColors
        gradient.locations = [0.0 , 1.0]
        gradient.startPoint = startPoint
        gradient.endPoint = endPoint
        gradient.frame = CGRect(x: 0, y: 0, w: object.frame.size.width, h: object.frame.size.height)
        return gradient
    }

Jak używać

let start : CGPoint = CGPoint(x: 0.0, y: 1.0)
let end : CGPoint = CGPoint(x: 1.0, y: 1.0)

let gradient: CAGradientLayer = makeGradientLayer(for: cell, startPoint: start, endPoint: end, gradientColors: [
                    UIColor(red:0.92, green:0.07, blue:0.4, alpha:1).cgColor,
                    UIColor(red:0.93, green:0.11, blue:0.14, alpha:1).cgColor
                    ])

self.vwTemp.layer.insertSublayer(gradient, at: 0)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.