Eliminar el texto del título de un iOS UIBarButtonItem


198

Lo que quería hacer es eliminar el texto del botón 'Atrás' de a UIBarButtonItem, dejando solo el galón azul en la barra de navegación. Tenga en cuenta que estoy desarrollando para iOS 7. He probado varios métodos, que incluyen, entre otros:

Este es el método de imagen que no me gustó (la imagen parecía fuera de lugar):

UIBarButtonItem *barBtnItem = [[UIBarButtonItem alloc]initWithImage:[UIImage imageNamed:@"iOS7BackButton"] style:UIBarButtonItemStylePlain target:self action:@selector(goToPrevious:)];
self.navigationItem.leftBarButtonItem = barBtnItem;

Otro método que probé fue este, que simplemente no funcionó (no se mostró nada):

UIBarButtonItem *barBtn = [[UIBarButtonItem alloc]init];
barBtn.title=@"";
self.navigationItem.leftBarButtonItem=barBtn;

Lo que quería lograr es algo así como los botones de retroceso que se encuentran en la aplicación iOS 7 Music, que solo presentaba un solo galón.

Gracias.



¿Por qué no tomas una imagen de cuál es tu requisito? Y refiéralo en leftBarButtonItem.
Vishal Sharma

La razón por la que no utilicé el método de imagen es 1. Es muy difícil obtener una imagen perfecta del botón Atrás y 2. Habrá alguna forma de desalineación con la imagen y no se verá natural, y es por eso Acudí a StackOverflow para obtener ayuda sobre cómo lograr esto de forma nativa.
Pan Ziyue

Respuestas:


198
[[UIBarButtonItem appearance] setBackButtonTitlePositionAdjustment:UIOffsetMake(-60, -60)
                                                         forBarMetrics:UIBarMetricsDefault];

Luego puede eliminar el título del elemento del botón Atrás.

Si usa Storyboard, puede configurar el botón Atrás del inspector de atributos de navegación con espacio.


95
Hay un problema con grandes títulos: que no están centrados, pero empujados hacia la derecha, como si el texto del botón de vuelta todavía estaba allí
sonxurxo

11
Esto no funciona si el usuario ha activado "Accesibilidad - Formas de botón"
Mikael Bartlett

22
Creo que esta es una mejor respuesta:[[UIBarButtonItem appearance] setTitleTextAttributes:@{NSForegroundColorAttributeName: [UIColor clearColor]} forState:UIControlStateNormal];
benjamin

19
Cualquier solución con números mágicos es una mala idea. Con el impulso a la distribución automática, las clases de tamaño, las características de accesibilidad, etc., usar valores constantes como este seguramente lo morderá.
Michael Peterson

3
Para mejorar el comentario de Benjamin, se debe configurar esto para UIControlStateHighlightedasí
n_b

411

Para configurar el título del botón de retroceso para un controlador de vista sin cambiar el uso del título:

C objetivo:

self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:self.navigationItem.backBarButtonItem.style target:nil action:nil];

Rápido:

navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)

Para que quede claro, esto se hace en el controlador de vista que vería si presiona el botón Atrás. es decir, en lugar de ver '<Configuración', solo desea ver '<' y luego, en su SettingsViewController, debe poner esto en su init. Entonces no obtienes ninguno de los problemas del título que no se muestra cuando estás mirando el controlador de vista en sí.


1
No sé por qué la otra respuesta está marcada como correcta, es solo eliminar el título, por lo que el botón de retroceso tiene un "título" para mostrar, ¡pero ese no es el enfoque correcto en absoluto! Esta debería ser la respuesta correcta.
Pach

8
También para cualquiera que use Swift, es navigationItem.backBarButtonItem = UIBarButtonItem (título: "", estilo: .Plain, target: nil, action: nil). No es difícil convertir de Obj-C a Swift, pero te ahorraré el problema
Zoyt

15
@BenjaminPiette, ¿estás llamando esto en el UIViewController que verías si presionas el botón Atrás? No es el UIViewController que muestra el botón Atrás.
DonnaLea

2
¡Qué solución tan elegante! Gracias. Simplemente no se sentía bien hacer lo compensado.
hyd00

2
Esta solución funciona perfectamente si escribe esta línea de código en el controlador anterior en la pila de navegación.
Bohdan Savych

128

Si está utilizando Storyboards, puede Attributes Inspectoracceder al ViewController Navigation Item(hacer clic en Navigation Bar) y establecer la Back Buttonpropiedad en "" (un carácter de espacio). Esto establecerá el título del botón Atrás en un carácter de espacio, dejando el galón visible. No hay necesidad de meterse con el código.

imagen de ejemplo

Tenga en cuenta que esto establecerá el Back Buttontítulo para el botón Atrás que pasará a este controlador de vista del que se presionó encima, ¡no para el Back Buttonque se mostrará dentro de este controlador!


10
Desearía poder votar esto más: "Tenga en cuenta que esto establecerá el título del botón Atrás para el botón Atrás que pasará a este controlador de vista desde el que se presionó encima, no para el botón Atrás que se mostrará dentro de este ¡Controlador!"
Jayson Lane, el

121

Esto funciona para mí para mostrar solo el galón 'atrás' sin ningún texto:

self.navigationController.navigationBar.topItem.title = @"";

Establezca esta propiedad en viewDidLoadel controlador de vista que presenta la barra de navegación y hará el truco.

Nota: solo lo he probado en iOS 7, que está dentro del alcance de la pregunta.


33
Eso tiene un efecto secundario de eliminar la etiqueta del título en el medio de la barra de navegación.
Pwner

77
Establezca el título del VC de inserción en 'viewWillAppear' con la misma propiedad. De esta manera no se elimina.
Guto Araujo

55
Moví ese fragmento de código al -viewDidLoadVC para el que no quiero el texto Atrás. Sin embargo, lo que descubrí más tarde fue que cuando navigationControlleraparece el segundo VC del texto del Título aparece poco después, no inmediatamente. Supongo que un video ilustraría esto mejor, así que aquí está: enlace
Pan Ziyue

1
Esta no es la mejor respuesta, consulte la respuesta de andyleehao: [[Apariencia UIBarButtonItem] setBackButtonTitlePositionAdjustment: UIOffsetMake (0, -60) forBarMetrics: UIBarMetricsDefault];
lmn más allá del

1
Esta es la respuesta correcta según la documentación de Apple: "Cuando el receptor está en la pila de elementos de navegación y es el segundo desde la parte superior, en otras palabras, su controlador de vista administra las vistas a las que el usuario volvería a navegar, el valor en esta propiedad se usa para el botón Atrás en la barra de navegación superior. Si el valor de esta propiedad es nulo, el sistema usa la cadena "Atrás" como texto del botón Atrás ".
Raphael Oliveira

27

Cuando configure el título del botón, use @ "" en lugar de @ "".

--EDITAR--

¿Algo cambia cuando pruebas otras cuerdas? Estoy usando el siguiente código con éxito:

UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:backString style:UIBarButtonItemStyleDone target:nil action:nil];
[[self navigationItem] setBackBarButtonItem:backButton];

backString es una variable que se establece en @ "" o @ "Back", dependiendo de si estoy en iOS 7 o una versión inferior.

Una cosa a tener en cuenta es que este código no está en el controlador de la página para la que quiero personalizar el botón Atrás. En realidad está en el controlador antes que en la pila de navegación.


Hmm, iOS 7 no parece cambiar eso. Sigue usando "Atrás" como título. Si pongo en setLeftBarButtonItem:backButtoncambio nada estaría allí.
Pan Ziyue

1
Si ese es el caso, y no sucede nada cuando establece el título en otra cosa, creo que tendrá que intentar mover su código al controlador de vista principal. Estoy usando el código que publiqué anteriormente en mi método prepareForSegue.
Kamaros

27

ingrese la descripción de la imagen aquí

A veces es útil ver las cosas en contexto. Aquí hay un proyecto mínimo que oculta el texto "atrás" pero aún muestra la flecha.

Storyboard

ingrese la descripción de la imagen aquí

Hay un show segue del botón "Mostrar segundo controlador de vista" al segundo controlador de vista.

También agregué un elemento de navegación al segundo controlador de vista para que tuviera un título. Esto es opcional No afecta el botón de retroceso.

Código

FirstViewController.swift

import UIKit
class FirstViewController: UIViewController {

    @IBAction func showSecondViewControllerButtonTapped(sender: UIButton) {

        // hide the back button text
        navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
    }
}

SecondViewController.swift

import UIKit
class SecondViewController: UIViewController {
    // Nothing at all needed here
}

Método alternativo (solo IB, sin código)

En el guión gráfico, seleccione el elemento de navegación para el primer controlador de vista (no el segundo). Simplemente ingrese un espacio para el texto del botón Atrás.

ingrese la descripción de la imagen aquí


Concepto clave que "para el primer controlador de vista (no el segundo)" como es el nombre del VC al que desea regresar, por lo que es responsabilidad dar este título.
buguibu

15
self.navigationController.navigationBar.topItem.title = @"";

1
Puede usar backItem en lugar de topItem. TopItem ocultará el título, mientras que backItem ocultará el título del botón de retroceso.
Ravi Ojha

2
Esta es la única solución si childViewController está en profundidad 2+ en la pila de niños de NavigationController
Nathaniel Blumer

2
Esto también eliminará el título del controlador de vista anterior. En la mayoría de los casos, esto va a
apestar

11

En iOS7, Apple introdujo dos nuevas propiedades a UINavigationBar, 'backIndicatorTransitionMaskImage' y 'backIndicatorImage'.

Simplemente llamando una vez:

[[UINavigationBar appearance] setBackIndicatorImage:[UIImage imageNamed:@"your_image"]];
[[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"your_image_mask"]];

Representará una imagen personalizada en lugar del glifo de chevron predeterminado, heredando el color de tinte de keyWindow.

Y para eliminar el título, sugeriré la respuesta de Kamaros. Recuerde llamar a este código en el controlador de vista que está impulsando su nuevo controlador de vista. Eliminar el texto del título de un iOS UIBarButtonItem


Para ajustar el color del tinte [[UINavigationBar apariencia] setTintColor: [UIColor whiteColor]];
Mazen Kasser

10

No tuve mucho éxito con las respuestas proporcionadas, pero encontré una solución muy simple. En su guión gráfico, puede hacer clic en el elemento de navegación de su controlador UIViewController y establecer el texto del botón Atrás. Lo configuré en un solo espacio '' y me dio el comportamiento que estaba buscando.ingrese la descripción de la imagen aquí


10

Esto funcionó para mí en iOS10. Llame a esto desde viewDidLoad del controlador de vista.

self.navigationController?.navigationBar.topItem?.title = ""

6

La solución simple a este problema, trabajando tanto en iOS7 como en 6, es establecer una vista de título personalizada en viewDidLoad:

- (void)viewDidLoad {

    [super viewDidLoad];

    UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
    titleLabel.text = self.title;
    titleLabel.backgroundColor = [UIColor clearColor];

    [titleLabel sizeToFit];

    self.navigationItem.titleView = titleLabel;
}

Luego, en viewWillAppear: puede llamar con seguridad

self.navigationController.navigationBar.topItem.title = @" ";

Debido a que su vista de título es una vista personalizada, no se sobrescribirá cuando vuelva a la pila de navegación.


Solo necesitaba la última parte del código y no el UILabel en blanco. Estaba llamando [self.navigationItem.backBarButtonItem setTitle:@" "];a viewWillAppear pero no funcionaba en las vistas presentadas después de descartar una vista modal.
Diziet

6

En realidad, puedes hacer esto con solo un truco:

Anule la UINavigationBarclase y agregue esta línea de código:

- (void)layoutSubviews{
    self.backItem.title = @"";
    [super layoutSubviews];
}

Luego inicialice su UINavigationController con esta clase personalizada UINavigationBar ... etc.UINavigationController * navController = [[UINavigationController alloc] initWithNavigationBarClass:[CBCNavigationBar class] toolbarClass:nil];

Espero que esto ayude


Esto funcionó para mí. Aunque parece que hay un ligero tartamudeo en la animación push en iOS 7: el título parece dejar espacio para el botón de retroceso y luego se centra, ya que no hay ninguno.
zekel

Sí, intente usar titleView personalizado en lugar de configurar el navigationItem.title directamente. Creo que eso debería solucionar tu problema.
rordulu

Esta solución es increíble porque es global para cada barra de navegación que tiene la subclase. Gracias
Yetispapa

5

Pude improvisar algo juntos usando la respuesta de DonnaLea. Así es como aparece la solución en mi subclase UIViewController:

var backItemTitle:String?

override func viewDidLoad() {
    super.viewDidLoad()

    //store the original title
    backItemTitle = self.navigationController?.navigationBar.topItem?.title

    //remove the title for the back button
    navigationController?.navigationBar.topItem?.title = ""
}

override func willMoveToParentViewController(parent: UIViewController?) {
    super.willMoveToParentViewController(parent)
    if parent == nil {

        //restore the orignal title
        navigationController?.navigationBar.backItem?.title = backItemTitle
    }
}

El problema con la respuesta original es que elimina el título del controlador cuando vuelve a aparecer. Intentar restablecer el título en viewWillDisappear es demasiado tarde en el proceso de transición; Hace que el título vuelva a aparecer en lugar de animarse bien. Sin embargo, willMoveToParentViewController ocurre antes y permite el comportamiento correcto.

Advertencia: solo he probado esto con un UINavigationController push / pop normal. Puede haber un comportamiento inesperado adicional en otras situaciones.


4

En el método prepareForSegue: de su primer ViewController, establece el título de las vistas en @ "", de modo que cuando se presiona la siguiente vista, mostrará el título anterior de ViewController, que será @ "".

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    self.navigationItem.title = @" ";
}

El único problema con esto es que cuando presiona el botón Atrás, su vista anterior no tendrá un título, por lo que puede agregarlo nuevamente en viewWillAppear:

- (void)viewWillAppear:(BOOL)animated{
    self.navigationItem.title = @"First View Title";
}

No me gusta mucho esta solución, pero funciona y no encontré otra forma de hacerlo.



3

Ninguna de las respuestas me ayudó. Pero lo hizo un truco: acabo de borrar el título del controlador de vista que presionó (hacia donde va el botón de retroceso) justo antes de presionarlo.

Entonces, cuando la vista anterior no tiene un título, en iOS 7 el botón Atrás solo tendrá una flecha, sin texto.

En viewWillAppearla vista de empuje, volví a colocar el título original.


1
Hmm, este truco funciona. Sin embargo, parece que las palabras allí arriba parpadearán a veces. No es una muy buena solución pero, sin embargo, una buena. +1
Pan Ziyue

3
    NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
                                [UIColor clearColor],UITextAttributeTextColor,
                                nil];

    [[UIBarButtonItem appearance] setTitleTextAttributes:attributes
                                                forState:UIControlStateNormal];

Estaba teniendo el mismo problema y lo hice de esta manera.

--EDITAR--

Esta es una solución cuando realmente desea eliminar el texto del título de todo UIBarbuttonItem. Si solo desea eliminar el título del elemento del botón de la barra posterior, no existe una solución simple y conveniente. En mi caso, dado que solo tengo algunos UIBarButtonItems que necesitan mostrar el texto del título, acabo de cambiar el título titleTextAttributes de esos botones específicos. Si desea ser más específico, use el código a continuación, que solo cambiará los botones de la barra de navegación:

NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
                                [UIColor clearColor],UITextAttributeTextColor,
                                nil];

[[UIBarButtonItem appearanceWhenContainedIn:[UINavigationBar class], nil] setTitleTextAttributes:attributes
                                                forState:UIControlStateNormal];

1
Esto funciona para mí, sin embargo, puede ver brevemente el texto al devolver el golpe.
Diziet

3
@Diziet trate de añadir la misma para el Estado UIControlStateHighlighted, así
CoderPug

2
Esto funciona bien, pero esto también "oculta" todos los demás botones, no solo el botón Atrás ... ¿Alguna idea para resolver esto?
Urkman

3

Esto está usando la subclase navigationController elimina la "Atrás".

Estoy usando esto para eliminarlo, permanentemente a través de la aplicación.

//.h
@interface OPCustomNavigationController : UINavigationController 

@end

//.m
@implementation OPCustomNavigationController

- (void)awakeFromNib
{
    [self backButtonUIOverride:YES];
}

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    [self backButtonUIOverride:NO];

    [super pushViewController:viewController animated:animated];
}

- (void)backButtonUIOverride:(BOOL)isRoot
{
    if (!self.viewControllers.count)
        return;

    UIViewController *viewController;

    if (isRoot)
    {
        viewController = self.viewControllers.firstObject;
    }
    else
    {
        int previousIndex = self.viewControllers.count - 1;

        viewController = [self.viewControllers objectAtIndex:previousIndex];
    }

    viewController.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@""
                                                                                       style:UIBarButtonItemStylePlain
                                                                                      target:nil
                                                                                      action:nil];
}

@end

3
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:backButtonImage forState:UIControlStateNormal barMetrics:UIBarMetricsDefaultPrompt];
[[UIBarButtonItem appearance] setBackButtonTitlePositionAdjustment:UIOffsetMake(10.0, NSIntegerMin) forBarMetrics:UIBarMetricsDefault];
[[UIBarButtonItem appearance] setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor whiteColor],
                                                               NSFontAttributeName:[UIFont systemFontOfSize:1]}
                                                    forState:UIControlStateNormal];

1
Por favor, agregue una explicación a su respuesta.
Alex

1 hago el mismo color que la barra de navegación 2 hace que el tamaño del texto sea lo suficientemente pequeño como para no apretar el título
Bill Xie

Revisé tu respuesta, siempre debes apoyar tu respuesta.
Alex

2

Ocultar el título del botón Atrás de la barra de navegación

UIBarButtonItem *barButton = [[UIBarButtonItem alloc] init];
barButton.title = @""; // blank or any other title
self.navigationController.navigationBar.topItem.backBarButtonItem = barButton;

2

Esto es lo que estoy haciendo, que es más simple para eliminar el título del botón Atrás

override func viewDidLoad() {
    super.viewDidLoad()
    navigationController?.navigationBar?.backItem?.title = ""
}

2

Swift 3.1 Puede hacer esto implementando el método delegado de UINavigationController. Ocultará el título solo con el botón Atrás, aún obtendremos la imagen de la flecha hacia atrás y la funcionalidad predeterminada.

func navigationController(_ navigationController: UINavigationController, 
  willShow viewController: UIViewController, animated: Bool) {
        let item = UIBarButtonItem(title: " ", style: .plain, target: nil, 
                    action: nil)
        viewController.navigationItem.backBarButtonItem = item
    }

1

También puedes usar esto:

UIBarButtonItem *temporaryBarButtonItem = [[UIBarButtonItem alloc] init];
temporaryBarButtonItem.title = @"";
self.navigationItem.backBarButtonItem = temporaryBarButtonItem;

[temporaryBarButtonItem release];

Esto funciona para mi


1
case : <Back as <

override func viewWillAppear(animated: Bool) {
navigationController!.navigationBar.topItem!.title = ""
    }

1

Solución perfecta a nivel mundial

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

    UIBarButtonItem.appearance().setTitleTextAttributes([NSForegroundColorAttributeName:UIColor.clearColor()], forState: UIControlState.Normal)
    UIBarButtonItem.appearance().setTitleTextAttributes([NSForegroundColorAttributeName:UIColor.clearColor()], forState: UIControlState.Highlighted)

    return true
}

la solución más tonta de todas ... también usa el espacio como antes ... se volvió transparente ...
Laszlo

Elimina el texto de todos los UIBarButtonItems de la aplicación
Mikel Sanchez

1

Creo una clase personalizada UINavigationControllery la aplico a todos los controladores de navegación de mi aplicación. Dentro de esta UINavigationControllerclase personalizada configuro el UINavigationBardelegado selfuna vez que se carga la vista.

- (void)viewDidLoad {
    self.navigationBar.delegate = self;
}

Luego implemento el método delegado:

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPushItem:(UINavigationItem *)item {

    // This will clear the title of the previous view controller
    // so the back button is always just a back chevron without a title
    if (self.viewControllers.count > 1) {
        UIViewController *previousViewController = [self.viewControllers objectAtIndex:(self.viewControllers.count - 2)];
        previousViewController.title = @"";
    }
    return YES;
}

De esta manera, simplemente asigno mi clase personalizada a todos mis controladores de navegación y borra el título de todos los botones de retroceso. Y solo por claridad, siempre configuro el título para todos mis otros controladores de vista en el interior viewWillAppearpara que el título siempre se actualice justo antes de que aparezca la vista (en caso de que se elimine con trucos como este).


1

¡Simplemente ingresando un solo espacio para el elemento de navegación del botón Atrás funciona!


1
Esto es más adecuado como comentario que como respuesta.
Al Lelopath

1

Si, como yo, estás usando una vista personalizada en lugar de la UINavigationBary estás atascado con el botón de retroceso, entonces tienes que hacer un poco de trabajo que se siente un poco complicado.

[self.navigationController.navigationBar setHidden:NO];
self.navigationController.navigationBar.topItem.title = @"";
[self.navigationController.navigationBar setHidden:YES];

Parece que si no se presenta, no importa lo que intente, muestre un título, esto significa que se muestra oculto antes de dibujarlo y resuelve el problema.


1
extension UIViewController{
    func hideBackButton(){
        navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
    }
}

1

Hice una categoría de configuración cero muy simple para ocultar todos los títulos de los botones de retroceso a través de nuestra aplicación, puede consultarla aquí . Esta pregunta ya ha aceptado la respuesta, pero para otros puede ser útil.

EDITAR:

archivo .h

#import <UIKit/UIKit.h>

@interface UINavigationController (HideBackTitle)
extern void PJSwizzleMethod(Class cls, SEL originalSelector, SEL swizzledSelector);

@end

archivo .m

#import "UINavigationController+HideBackTitle.h"
#import <objc/runtime.h>


@implementation UINavigationController (HideBackTitle)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        PJSwizzleMethod([self class],
                    @selector(pushViewController:animated:),
                    @selector(pj_pushViewController:animated:));
    });
}

- (void)pj_pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
    UIViewController *disappearingViewController =  self.viewControllers.lastObject;
    if (disappearingViewController) {
        disappearingViewController.navigationItem.backBarButtonItem=[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
    }
    if (!disappearingViewController) {
        return [self pj_pushViewController:viewController animated:animated];
    }
    return [self pj_pushViewController:viewController animated:animated];
}



@end

void PJSwizzleMethod(Class cls, SEL originalSelector, SEL swizzledSelector)
{
    Method originalMethod = class_getInstanceMethod(cls, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);

    BOOL didAddMethod =
    class_addMethod(cls,
                originalSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));

    if (didAddMethod) {
        class_replaceMethod(cls,
                        swizzledSelector,
                        method_getImplementation(originalMethod),
                        method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.