Rimuovi tutti i vincoli che influiscono su un UIView


90

Ho una UIView che viene posizionata sullo schermo tramite diversi vincoli. Alcuni dei vincoli sono di proprietà della superview, altri sono di proprietà di altri antenati (ad esempio, forse la proprietà view di un UIViewController).

Voglio rimuovere tutti questi vecchi vincoli e posizionarli in un posto nuovo usando nuovi vincoli.

Come posso farlo senza creare un IBOutlet per ogni singolo vincolo e dover ricordare quale vista possiede detto vincolo?

Per elaborare, l'approccio ingenuo sarebbe quello di creare un gruppo di IBOutlet per ciascuno dei vincoli e quindi implicherebbe la chiamata di codice come:

[viewA removeConstraint:self.myViewsLeftConstraint];
[viewB removeConstraint:self.myViewsTopConstraint];
[viewB removeConstraint:self.myViewsBottomConstraint];
[self.view removeConstraint:self.myViewsRightConstraint];

Il problema con questo codice è che anche nel caso più semplice, avrei bisogno di creare 2 IBOutlet. Per layout complessi, questo potrebbe facilmente raggiungere 4 o 8 IBOutlet richiesti. Inoltre, dovrei assicurarmi che la mia richiesta di rimuovere il vincolo venga richiamata sulla visualizzazione corretta. Ad esempio, immagina che myViewsLeftConstraintsia di proprietà di viewA. Se dovessi chiamare accidentalmente [self.view removeConstraint:self.myViewsLeftConstraint], non accadrebbe nulla.

Nota: il metodo constraintsAffectingLayoutForAxis sembra promettente, ma è inteso solo a scopo di debug.


Aggiornamento: Molte delle risposte che sto ricevendo accordo con self.constraints, self.superview.constraintso qualche variante di quelli. Queste soluzioni non funzioneranno poiché quei metodi restituiscono solo i vincoli posseduti dalla vista, non quelli influenzano la vista.

Per chiarire il problema con queste soluzioni, considera questa gerarchia di visualizzazione:

  • Nonno
    • Padre
      • Me
        • Figlio
        • Figlia
      • Fratello
    • Zio

Ora immagina di creare i seguenti vincoli e di collegarli sempre al loro antenato comune più vicino:

  • C0: Me: stesso top di Son (di mia proprietà)
  • C1: Me: width = 100 (di mia proprietà)
  • C2: Io: stessa altezza del fratello (di proprietà del padre)
  • C3: Io: stesso top di Uncle (di proprietà del nonno)
  • C4: Io: stesso a sinistra del nonno (di proprietà del nonno)
  • C5: Brother: stesso a sinistra di Father (di proprietà di Father)
  • C6: Zio: stesso a sinistra del nonno (di proprietà del nonno)
  • C7: Figlio: stesso a sinistra di Daughter (di mia proprietà)

Ora immagina di voler rimuovere tutti i vincoli che interessano Me. Qualsiasi soluzione adeguata dovrebbe rimuovere [C0,C1,C2,C3,C4]e nient'altro.

Se uso self.constraints(dove il sé è Me), otterrò [C0,C1,C7], poiché questi sono gli unici vincoli posseduti da Me. Ovviamente non basterebbe rimuoverlo visto che manca [C2,C3,C4]. Inoltre, sta rimuovendo C7inutilmente.

Se uso self.superview.constraints(dove io sono io), otterrò [C2,C5], poiché quelli sono i vincoli posseduti da Padre. Ovviamente non possiamo rimuovere tutti questi poiché C5è completamente estraneo a Me.

Se uso grandfather.constraints, otterrò [C3,C4,C6]. Ancora una volta, non possiamo rimuoverli tutti poiché C6dovrebbero rimanere intatti.

L'approccio della forza bruta consiste nel fare un loop su ciascuno degli antenati della vista (inclusa se stessa) e vedere se firstItemo secondItemsono la vista stessa; in tal caso, rimuovere tale vincolo. Questo porterà a una soluzione corretta, tornando[C0,C1,C2,C3,C4] e solo quei vincoli.

Tuttavia, spero che esista una soluzione più elegante rispetto al dover scorrere l'intero elenco degli antenati.


Che ne dici di inserire un identificatore per tutti i vincoli che desideri rimuovere? In questo modo non è necessario mantenere uno sbocco per loro.
nsuinteger

Risposte:


71

Questo approccio ha funzionato per me:

@interface UIView (RemoveConstraints)

- (void)removeAllConstraints;

@end


@implementation UIView (RemoveConstraints)

- (void)removeAllConstraints
{
    UIView *superview = self.superview;
    while (superview != nil) {
        for (NSLayoutConstraint *c in superview.constraints) {
            if (c.firstItem == self || c.secondItem == self) {
                [superview removeConstraint:c];
            }
        }
        superview = superview.superview;
    }

    [self removeConstraints:self.constraints];
    self.translatesAutoresizingMaskIntoConstraints = YES;
}

@end

Al termine, l'esecuzione della visualizzazione rimane dov'era perché crea vincoli di ridimensionamento automatico. Quando non lo faccio la vista di solito scompare. Inoltre, non si limita a rimuovere i vincoli dalla superview, ma lo attraversa fino in fondo poiché potrebbero esserci vincoli che lo influenzano nelle viste degli antenati.


Versione Swift 4

extension UIView {
    
    public func removeAllConstraints() {
        var _superview = self.superview
        
        while let superview = _superview {
            for constraint in superview.constraints {
                
                if let first = constraint.firstItem as? UIView, first == self {
                    superview.removeConstraint(constraint)
                }
                
                if let second = constraint.secondItem as? UIView, second == self {
                    superview.removeConstraint(constraint)
                }
            }
            
            _superview = superview.superview
        }
        
        self.removeConstraints(self.constraints)
        self.translatesAutoresizingMaskIntoConstraints = true
    }
}

3
Questa dovrebbe essere la risposta ufficiale.
Jason Crocker

perché hai self.translatesAutoresizingMaskIntoConstraints = YES; ? non lo vuoi letteralmente mai per le visualizzazioni che stai impostando con vincoli?
lensovet

4
Sto rimuovendo i vincoli, quindi voglio utilizzare i vincoli di ridimensionamento automatico dopo aver rimosso gli altri vincoli per mantenerlo in posizione, senza quella riga nel mio codice la vista scomparirebbe
marchinram

Avevo bisogno di registrare tutti i vincoli su una vista per scopi di debug e sono stato in grado di modificare leggermente questa risposta per farlo. +1
chiliNUT

Ciò rimuoverà erroneamente C7. Tuttavia, dovrebbe essere facile da risolvere se rimuovi [self removeConstraints:self.constraints];e passi UIView *superview = self.superview;a UIView *superview = self;.
Senseful

50

L'unica soluzione che ho trovato finora è rimuovere la vista dalla sua superview:

[view removeFromSuperview]

Sembra che rimuova tutti i vincoli che influenzano il layout ed è pronto per essere aggiunto a una superview e avere nuovi vincoli allegati. Tuttavia, rimuoverà in modo errato anche le visualizzazioni secondarie dalla gerarchia e le eliminerà in modo [C7]errato.


È possibile utilizzare le proprietà firstItem e secondItem dei vincoli per verificare se si applicano alla visualizzazione e scorrere la gerarchia della visualizzazione per trovarli tutti. Penso che rimuovere e aggiungere nuovamente sia una soluzione migliore, però. Sarei interessato a vedere il tuo caso d'uso poiché sembra che questa sia una cosa molto brutta da fare. I tuoi vincoli potrebbero facilmente diventare completamente non validi poiché altre visualizzazioni potrebbero fare affidamento su tali vincoli per essere disposti correttamente.
bjtitus

@bjtitus: buon punto. Tuttavia, in questo caso specifico, sto riposizionando una visione che non ha nulla di dipendente da essa. Utilizza altre viste per sapere dove deve essere posizionato e non ci sono viste che lo utilizzano per sapere dove essere posizionato.
Senseful

Non l'unica soluzione, ma molto semplice. Nel mio caso d'uso ho appena rimosso la vista da superview e poi l'ho aggiunta di nuovo in seguito.
Marián Černý

49

Puoi rimuovere tutti i vincoli in una vista in questo modo:

self.removeConstraints(self.constraints)

MODIFICA: per rimuovere i vincoli di tutte le visualizzazioni secondarie, utilizza la seguente estensione in Swift:

extension UIView {
    func clearConstraints() {
        for subview in self.subviews {
            subview.clearConstraints()
        }
        self.removeConstraints(self.constraints)
    }
}

22
Il problema con questa soluzione è che rimuove solo i vincoli che questa vista possiede (ad esempio la sua larghezza e altezza). Non rimuove elementi come i vincoli iniziali e superiori.
Senseful

2
Non ho verificato il tuo commento sopra. Tuttavia, credo che questo funzionerebbe allo stesso modo o meglio di sopra, [NSLayoutConstraint disableateConstraints: self.constraints];
emdog4

2
Ciò soffrirebbe dello stesso problema della soluzione originale. self.constraintsrestituisce solo i vincoli che selfpossiede, non tutti i vincoli che influiscono self.
Senseful

1
Capisco cosa stai dicendo adesso. Nella mia risposta , rimuovo i vincoli dal contentView di UITableViewCell. Il metodo updateConstraints aggiungerebbe quindi vincoli per tutte le viste secondarie e reimposterà il layout. Nel mio primo commento sopra, avrei dovuto digitare self.view.constraints. Sia self.view che self.contentView sono in cima alla gerarchia di visualizzazione. Impostare translatesAutoresizingMasksToConstraints = YES su self.view o self.contentView è una cattiva idea. ;-). Non mi piacciono le chiamate a addSubview: nel mio metodo updateConstraints, la rimozione della vista dalla gerarchia sembra non necessaria.
emdog4

Ho appena aggiornato la domanda per mostrare perché una soluzione come questa non funziona per quello che sto cercando di fare. Supponendo che tu sia all'interno di un UITableViewCell, l'equivalente di contentView è Grandfather. Pertanto la tua soluzione rimuoverà in modo [C6]errato e non rimuoverà erroneamente [C0,C1,C2].
Senseful

20

Esistono due modi per ottenerlo in base alla documentazione per sviluppatori Apple

1. NSLayoutConstraint.deactivateConstraints

Questo è un metodo pratico che fornisce un modo semplice per disattivare una serie di vincoli con una chiamata. L'effetto di questo metodo è lo stesso dell'impostazione della proprietà isActive di ogni vincolo su false. In genere, l'utilizzo di questo metodo è più efficiente rispetto alla disattivazione di ogni vincolo individualmente.

// Declaration
class func deactivate(_ constraints: [NSLayoutConstraint])

// Usage
NSLayoutConstraint.deactivate(yourView.constraints)

2. UIView.removeConstraints(Deprecato per> = iOS 8.0)

Durante lo sviluppo per iOS 8.0 o versioni successive, utilizza il metodo disableateConstraint: della classe NSLayoutConstraint invece di chiamare direttamente il metodo removeConstraints. Il metodo disableConstraints: rimuove automaticamente i vincoli dalle viste corrette.

// Declaration
func removeConstraints(_ constraints: [NSLayoutConstraint])`

// Usage
yourView.removeConstraints(yourView.constraints)

Suggerimenti

L'utilizzo di Storyboards o XIBs può essere un tale problema nella configurazione dei vincoli come menzionato nel tuo scenario, devi creare IBOutlet per ognuno di quelli che desideri rimuovere. Anche così, la maggior parte delle volteInterface Builder crea più problemi di quanti ne risolva.

Pertanto, quando si hanno contenuti molto dinamici e diversi stati di visualizzazione, suggerirei:

  1. Creazione di visualizzazioni a livello di codice
  2. Disegnali e utilizza NSLayoutAnchor
  3. Aggiungi a un array ogni vincolo che potrebbe essere rimosso in seguito
  4. Cancellali ogni volta prima di applicare il nuovo stato

Codice semplice

private var customConstraints = [NSLayoutConstraint]()

private func activate(constraints: [NSLayoutConstraint]) {
    customConstraints.append(contentsOf: constraints)
    customConstraints.forEach { $0.isActive = true }
}

private func clearConstraints() {
    customConstraints.forEach { $0.isActive = false }
    customConstraints.removeAll()
}

private func updateViewState() {
    clearConstraints()

    let constraints = [
        view.leadingAnchor.constraint(equalTo: parentView.leadingAnchor),
        view.trailingAnchor.constraint(equalTo: parentView.trailingAnchor),
        view.topAnchor.constraint(equalTo: parentView.topAnchor),
        view.bottomAnchor.constraint(equalTo: parentView.bottomAnchor)
    ]

    activate(constraints: constraints)

    view.layoutIfNeeded()
}

Riferimenti

  1. NSLayoutConstraint
  2. UIView

2
Stai attento. Le visualizzazioni che specificano una dimensione del contenuto intrinseco avranno NSContentSizeLayoutConstraint aggiunto automaticamente da iOS. La rimozione di questo accedendo alla normale proprietà yourView.constraints interromperà il layout automatico su quelle viste.
TigerCoding

Il problema con questa soluzione è che rimuove solo i vincoli che questa vista possiede (ad esempio la sua larghezza e altezza). Non rimuove elementi come i vincoli iniziali e superiori.
Senseful

Sì, quello che ha detto Senseful. Sebbene questi metodi esistano, non risolvono da soli il problema su cui si pone la domanda.
GSnyder

18

In Swift:

import UIKit

extension UIView {

    /**
     Removes all constrains for this view
     */
    func removeConstraints() {

        let constraints = self.superview?.constraints.filter{
            $0.firstItem as? UIView == self || $0.secondItem as? UIView == self
        } ?? []

        self.superview?.removeConstraints(constraints)
        self.removeConstraints(self.constraints)
    }
}

Questo andrà in crash se la tua vista non ha una superview in "superview!". Si prega di racchiudere la parte in un "if let superview = superview" :)
Bersaelor

1
Ciò non rimuoverà tutti i vincoli. I vincoli prendono il genitore più vicino delle 2 viste interessate, quindi se crei un vincolo che non condivide la superview di questa vista, quel vincolo non verrà rimosso ...
vrwim

2
forse più corretto sarebbe confrontare l'identità if c.firstItem === selfinvece di lanciare as? UIViewe controllare l'uguaglianza con==
user1244109

Probabilmente non dovrebbe usare removeConstraints: da apple doc: // Il metodo removeConstraints sarà deprecato in una versione futura e dovrebbe essere evitato. Usa invece + [NSLayoutConstraint disableateConstraints:].
eonista

@eonist la risposta è di 3 anni fa. Invece di critiche dovresti modificarlo.
Alexander Volkov

5

Dettagli

  • Xcode 10.2.1 (10E1001), Swift 5

Soluzione

import UIKit

extension UIView {

    func removeConstraints() { removeConstraints(constraints) }
    func deactivateAllConstraints() { NSLayoutConstraint.deactivate(getAllConstraints()) }
    func getAllSubviews() -> [UIView] { return UIView.getAllSubviews(view: self) }

    func getAllConstraints() -> [NSLayoutConstraint] {
        var subviewsConstraints = getAllSubviews().flatMap { $0.constraints }
        if let superview = self.superview {
            subviewsConstraints += superview.constraints.compactMap { (constraint) -> NSLayoutConstraint? in
                if let view = constraint.firstItem as? UIView, view == self { return constraint }
                return nil
            }
        }
        return subviewsConstraints + constraints
    }

    class func getAllSubviews(view: UIView) -> [UIView] {
        return view.subviews.flatMap { [$0] + getAllSubviews(view: $0) }
    }
}

Utilizzo

print("constraints: \(view.getAllConstraints().count), subviews: \(view.getAllSubviews().count)")
view.deactivateAllConstraints()

1
È piuttosto carino.
eonista

2

Una soluzione rapida:

extension UIView {
  func removeAllConstraints() {
    var view: UIView? = self
    while let currentView = view {
      currentView.removeConstraints(currentView.constraints.filter {
        return $0.firstItem as? UIView == self || $0.secondItem as? UIView == self
      })
      view = view?.superview
    }
  }
}

È importante esaminare tutti i genitori, poiché i vincoli tra due elementi sono tenuti dagli antenati comuni, quindi semplicemente cancellare la superview come dettagliato in questa risposta non è abbastanza buono e potresti finire per avere brutte sorprese in seguito.


2

Basato sulle risposte precedenti (swift 4)

È possibile utilizzare immediateConstraints quando non si desidera eseguire la scansione di intere gerarchie.

extension UIView {
/**
 * Deactivates immediate constraints that target this view (self + superview)
 */
func deactivateImmediateConstraints(){
    NSLayoutConstraint.deactivate(self.immediateConstraints)
}
/**
 * Deactivates all constrains that target this view
 */
func deactiveAllConstraints(){
    NSLayoutConstraint.deactivate(self.allConstraints)
}
/**
 * Gets self.constraints + superview?.constraints for this particular view
 */
var immediateConstraints:[NSLayoutConstraint]{
    let constraints = self.superview?.constraints.filter{
        $0.firstItem as? UIView === self || $0.secondItem as? UIView === self
        } ?? []
    return self.constraints + constraints
}
/**
 * Crawls up superview hierarchy and gets all constraints that affect this view
 */
var allConstraints:[NSLayoutConstraint] {
    var view: UIView? = self
    var constraints:[NSLayoutConstraint] = []
    while let currentView = view {
        constraints += currentView.constraints.filter {
            return $0.firstItem as? UIView === self || $0.secondItem as? UIView === self
        }
        view = view?.superview
    }
    return constraints
}
}

2

Uso il seguente metodo per rimuovere tutti i vincoli da una vista:

file .h:

+ (void)RemoveContraintsFromView:(UIView*)view 
    removeParentConstraints:(bool)parent 
    removeChildConstraints:(bool)child;

File .m:

+ (void)RemoveContraintsFromView:(UIView *)view 
    removeParentConstraints:(bool)parent 
    removeChildConstraints:(bool)child
{
    if (parent) {
        // Remove constraints between view and its parent.
        UIView *superview = view.superview;
        [view removeFromSuperview];
        [superview addSubview:view];
    }

    if (child) {
        // Remove constraints between view and its children.
        [view removeConstraints:[view constraints]];
    }
}

Puoi anche leggere questo post sul mio blog per capire meglio come funziona dietro le quinte.

Se hai bisogno di un controllo più granulare, ti consiglio vivamente di passare a Masonry , una potente classe di framework che potresti usare ogni volta che devi gestire correttamente i vincoli a livello di programmazione.


2

L'approccio più semplice ed efficiente consiste nel rimuovere la vista da superView e aggiungerla di nuovo come sottoview. questo fa sì che tutti i vincoli delle sottoview vengano rimossi automaticamente


1

Con goalC

[self.superview.constraints enumerateObjectsUsingBlock:^(__kindof NSLayoutConstraint * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLayoutConstraint *constraint = (NSLayoutConstraint *)obj;
        if (constraint.firstItem == self || constraint.secondItem == self) {
            [self.superview removeConstraint:constraint];
        }
    }];
    [self removeConstraints:self.constraints];
}

0

Potresti usare qualcosa del genere:

[viewA.superview.constraints enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    NSLayoutConstraint *constraint = (NSLayoutConstraint *)obj;
    if (constraint.firstItem == viewA || constraint.secondItem == viewA) {
        [viewA.superview removeConstraint:constraint];
    }
}];

[viewA removeConstraints:viewA.constraints];

Fondamentalmente, questo viene enumerato su tutti i vincoli sulla superview di viewA e rimuove tutti i vincoli che sono correlati a viewA.

Quindi, la seconda parte rimuove i vincoli da viewA utilizzando l'array di vincoli di viewA.


1
Il problema con questa soluzione è che rimuove solo i vincoli di proprietà della superview. Non rimuove tutti i vincoli che influenzano la vista. (Ad esempio, potresti impostare facilmente dei vincoli sulla superview della superview, che non verrebbero rimossi utilizzando questa soluzione.)
Senseful

Ho appena aggiornato la domanda per mostrare perché questa soluzione non funzionerà. Questo è il più vicino all'essere corretto, poiché è il più vicino alla soluzione di forza bruta che ho menzionato sopra. Tuttavia, questa soluzione verrà rimossa in modo errato [C7]e non verrà rimossa in modo errato [C3,C4].
Senseful

potresti progettare il metodo per risalire la gerarchia. Ma questa soluzione è abbastanza buona, poiché dovresti comunque aggiungere dei vincoli sopra la superview immediata. IMO
eonista

0

(Al 31 luglio 2017)

SWIFT 3

self.yourCustomView.removeFromSuperview()
self.yourCustomViewParentView.addSubview(self.yourCustomView)

Obiettivo C

[self.yourCustomView removeFromSuperview];
[self.yourCustomViewParentView addSubview:self.yourCustomView];

Questo è il modo più semplice per rimuovere rapidamente tutti i vincoli esistenti su una UIView. Assicurati solo di aggiungere nuovamente l'UIView con i suoi nuovi vincoli o il nuovo frame in seguito =)


0

Utilizzo di una sequenza riutilizzabile

Ho deciso di affrontare questo problema in un modo più "riutilizzabile". Poiché la ricerca di tutti i vincoli che interessano una vista è la base per tutto quanto sopra, ho deciso di implementare una sequenza personalizzata che li restituisca tutti per me, insieme alle visualizzazioni proprietarie.

La prima cosa da fare è definire un'estensione su Arraysdi essa NSLayoutConstraintche restituisce tutti gli elementi che influenzano una vista specifica.

public extension Array where Element == NSLayoutConstraint {

    func affectingView(_ targetView:UIView) -> [NSLayoutConstraint] {

        return self.filter{

            if let firstView = $0.firstItem as? UIView,
                firstView == targetView {
                return true
            }

            if let secondView = $0.secondItem as? UIView,
                secondView == targetView {
                return true
            }

            return false
        }
    }
}

Quindi utilizziamo quell'estensione in una sequenza personalizzata che restituisce tutti i vincoli che influenzano quella vista, insieme alle viste che effettivamente li possiedono (che possono trovarsi ovunque nella gerarchia della vista)

public struct AllConstraintsSequence : Sequence {

    public init(view:UIView){
        self.view = view
    }

    public let view:UIView

    public func makeIterator() -> Iterator {
        return Iterator(view:view)
    }

    public struct Iterator : IteratorProtocol {

        public typealias Element = (constraint:NSLayoutConstraint, owningView:UIView)

        init(view:UIView){
            targetView  = view
            currentView = view
            currentViewConstraintsAffectingTargetView = currentView.constraints.affectingView(targetView)
        }

        private let targetView  : UIView
        private var currentView : UIView
        private var currentViewConstraintsAffectingTargetView:[NSLayoutConstraint] = []
        private var nextConstraintIndex = 0

        mutating public func next() -> Element? {

            while(true){

                if nextConstraintIndex < currentViewConstraintsAffectingTargetView.count {
                    defer{nextConstraintIndex += 1}
                    return (currentViewConstraintsAffectingTargetView[nextConstraintIndex], currentView)
                }

                nextConstraintIndex = 0

                guard let superview = currentView.superview else { return nil }

                self.currentView = superview
                self.currentViewConstraintsAffectingTargetView = currentView.constraints.affectingView(targetView)
            }
        }
    }
}

Infine dichiariamo un'estensione su UIViewper esporre tutti i vincoli che lo influenzano in una semplice proprietà a cui puoi accedere con una semplice sintassi for-each.

extension UIView {

    var constraintsAffectingView:AllConstraintsSequence {
        return AllConstraintsSequence(view:self)
    }
}

Ora possiamo iterare tutti i vincoli che influenzano una vista e fare quello che vogliamo con loro ...

Elenca i loro identificatori ...

for (constraint, _) in someView.constraintsAffectingView{
    print(constraint.identifier ?? "No identifier")
}

Disattivali ...

for (constraint, _) in someView.constraintsAffectingView{
    constraint.isActive = false
}

O rimuoverli completamente ...

for (constraint, owningView) in someView.constraintsAffectingView{
    owningView.removeConstraints([constraint])
}

Godere!


0

Swift

La seguente estensione UIView rimuoverà tutti i vincoli di Edge di una vista:

extension UIView {
    func removeAllConstraints() {
        if let _superview = self.superview {
            self.removeFromSuperview()
            _superview.addSubview(self)
        }
    }
}

-1

Questo è il modo per disabilitare tutti i vincoli da una vista specifica

 NSLayoutConstraint.deactivate(myView.constraints)

Il problema con questa soluzione è che rimuove solo i vincoli che questa vista possiede (ad esempio la sua larghezza e altezza). Non rimuove elementi come i vincoli iniziali e superiori.
Senseful
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.