Subrayado de texto en UIButton

143

¿Alguien puede sugerir cómo subrayar el título de un UIButton? Tengo un UIButton de tipo Personalizado y quiero que el Título esté subrayado, pero el Creador de interfaces no proporciona ninguna opción para hacerlo.

En Interface Builder, cuando selecciona la opción de fuente para un botón, proporciona la opción de seleccionar Ninguno, Individual, Doble, Color, pero ninguno de estos proporciona ningún cambio en el Título del botón.

Cualquier ayuda apreciada.

RVN
fuente
1
Puede usar UITextView con una cadena atribuida agregando un enlace como en esta pregunta stackoverflow.com/questions/21629784/…
Khaled Annajar

Respuestas:

79

UIUnderlinedButton.h

@interface UIUnderlinedButton : UIButton {

}


+ (UIUnderlinedButton*) underlinedButton;
@end

UIUnderlinedButton.m

@implementation UIUnderlinedButton

+ (UIUnderlinedButton*) underlinedButton {
    UIUnderlinedButton* button = [[UIUnderlinedButton alloc] init];
    return [button autorelease];
}

- (void) drawRect:(CGRect)rect {
    CGRect textRect = self.titleLabel.frame;

    // need to put the line at top of descenders (negative value)
    CGFloat descender = self.titleLabel.font.descender;

    CGContextRef contextRef = UIGraphicsGetCurrentContext();

    // set to same colour as text
    CGContextSetStrokeColorWithColor(contextRef, self.titleLabel.textColor.CGColor);

    CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + textRect.size.height + descender);

    CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + textRect.size.height + descender);

    CGContextClosePath(contextRef);

    CGContextDrawPath(contextRef, kCGPathStroke);
}


@end
Nick H247
fuente
1
¡Tal vez no sea tan oportuno como lo necesitabas!
Nick H247
44
Gracias, terminé llamándolo de la siguiente manera: UIButton * btn = [UIUnderlinedButton buttonWithType: UIButtonTypeCustom];
hb922
2
El código funciona bien, pero noté que el subrayado no se redujo / aumentó cuando la vista cambia de tamaño en la rotación, causada por drawRectno ser llamado en la rotación. Esto se puede resolver configurando el botón para volver a dibujar de esta manera: lo myButton.contentMode = UIViewContentModeRedraw;que obliga al botón a volver a dibujar cuando cambian los límites.
AndroidNoob
44
También puede anular el setTitlemétodo de esta manera:objective-c - (void)setTitle:(NSString *)title forState:(UIControlState)state { [super setTitle:title forState:state]; [self setNeedsDisplay]; }
Kirualex
379

Para usar el generador de interfaces para subrayar, uno tiene que:

  • Cámbielo a atribuido
  • Resalta el texto en el inspector de atributos
  • Haga clic derecho, elija Fuente y luego Subrayar

Subrayar usando IB

Video que alguien más hizo https://www.youtube.com/watch?v=5-ZnV3jQd9I

finneycanhelp
fuente
2
Buena pregunta @ new2ios Quizás alguien más lo sepa
finneycanhelp
1
Haré una nueva pregunta, @finneycanhelp. Espero que en Xcode 6.3 haya una forma más fácil. Quiero decir que puede configurar su solución y luego usarla setTitlecon texto atribuido. Para mí, crear un botón personalizado para dibujar subrayado es un poco exótico (es posible que todavía sea nuevo en iOS, incluso cuando haya completado una aplicación).
new2ios
2
Finneydonehelped! ¡gracias por esto! no podía entender por qué el cuadro de diálogo emergente Fuentes no tenía efecto El clic derecho es perfecto.
IMFletcher
2
Buena respuesta para los usuarios de Interface Builder para este tipo de cosas simples que son un poco de trabajo al hacer en Code. ¡Gracias! (Y)
Randika Vishman
1
¿Por qué los desarrolladores de iOS prefieren escribir un código muy largo solo por un problema muy simple?
Mr5
129

Desde iOS6 ahora es posible usar una cadena NSAttributedString para realizar subrayado (y cualquier otra cosa que le atribuya soporte de cadenas) de una manera mucho más flexible:

NSMutableAttributedString *commentString = [[NSMutableAttributedString alloc] initWithString:@"The Quick Brown Fox"];

[commentString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:NSMakeRange(0, [commentString length])];

[button setAttributedTitle:commentString forState:UIControlStateNormal];

Nota: agregué esto como otra respuesta, ya que es una solución totalmente diferente a la anterior.

Editar: curiosamente (al menos en iOS8) tienes que subrayar el primer carácter, de lo contrario no funciona.

así que, como solución alternativa, configure el primer carácter subrayado con un color claro.

    // underline Terms and condidtions
    NSMutableAttributedString* tncString = [[NSMutableAttributedString alloc] initWithString:@"View Terms and Conditions"];

    // workaround for bug in UIButton - first char needs to be underlined for some reason!
    [tncString addAttribute:NSUnderlineStyleAttributeName
                      value:@(NSUnderlineStyleSingle)
                      range:(NSRange){0,1}];
    [tncString addAttribute:NSUnderlineColorAttributeName value:[UIColor clearColor] range:NSMakeRange(0, 1)];


    [tncString addAttribute:NSUnderlineStyleAttributeName
                      value:@(NSUnderlineStyleSingle)
                      range:(NSRange){5,[tncString length] - 5}];

    [tncBtn setAttributedTitle:tncString forState:UIControlStateNormal];
Nick H247
fuente
9
Solo tenga en cuenta que cuando lo haga de esta manera, también debe agregar un atributo para el color, ya que el texto del título atribuido no usará el color que configuró usando setTitleColor: forState:
daveMac
2
Impresionante, y gracias @daveMac por el aviso sobre el color. Para aquellos que no conocen el atributo es: NSForegroundColorAttributeName
Ryan Crews
En este método, el subrayado del botón está cerca del texto. ¿Algún método para cambiar la posición y del subrayado?
Ilesh P
49

Puede hacerlo en el propio generador de interfaces.

  1. Seleccione el inspector de atributos
  2. Cambie el tipo de título de simple a atribuido

ingrese la descripción de la imagen aquí

  1. Establecer el tamaño de fuente apropiado y la alineación del texto

ingrese la descripción de la imagen aquí

  1. Luego seleccione el texto del título y configure la fuente como subrayada

ingrese la descripción de la imagen aquí

Lineesh K Mohan
fuente
28

Es muy simple con cadena atribuida

Crea un diccionario con atributos establecidos y se aplica a la cadena atribuida. Luego puede configurar la cadena atribuida como título atribuido en uibutton o atributo atribuido en uilabel.

NSDictionary *attrDict = @{NSFontAttributeName : [UIFont
 systemFontOfSize:14.0],NSForegroundColorAttributeName : [UIColor
 whiteColor]};
 NSMutableAttributedString *title =[[NSMutableAttributedString alloc] initWithString:@"mybutton" attributes: attrDict]; 
[title addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:NSMakeRange(0,[commentString length])]; [btnRegLater setAttributedTitle:title forState:UIControlStateNormal];
Rinku
fuente
Lo que es commentString; ¿Copiaste la respuesta de @ NickH247?
significado-asuntos
21

Aquí está mi función, funciona en Swift 1.2.

func underlineButton(button : UIButton, text: String) {

    var titleString : NSMutableAttributedString = NSMutableAttributedString(string: text)
    titleString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleSingle.rawValue, range: NSMakeRange(0, count(text.utf8)))
    button.setAttributedTitle(titleString, forState: .Normal)
}

ACTUALIZACIÓN Swift 3.0 extensión:

extension UIButton {
    func underlineButton(text: String) {
        let titleString = NSMutableAttributedString(string: text)
        titleString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.styleSingle.rawValue, range: NSMakeRange(0, text.characters.count))
        self.setAttributedTitle(titleString, for: .normal)
    }
}
Adam Studenic
fuente
13

La respuesta de Nick es una excelente forma rápida de hacer esto.

Agregué soporte drawRectpara sombras.

La respuesta de Nick no tiene en cuenta si el título de su botón tiene una sombra debajo del texto:

ingrese la descripción de la imagen aquí

Pero puede mover el subrayado hacia abajo por la altura de la sombra de la siguiente manera:

CGFloat descender = self.titleLabel.font.descender;
CGContextRef contextRef = UIGraphicsGetCurrentContext();
CGFloat shadowHeight = self.titleLabel.shadowOffset.height;
descender += shadowHeight;

Entonces obtendrás algo como esto:

ingrese la descripción de la imagen aquí

annie
fuente
self.titleLabel.font.descender; esto se ha depreciado en iOS 3.0
KING
5

Para Swift 3 se puede usar la siguiente extensión:

extension UIButton {
    func underlineButton(text: String) {
        let titleString = NSMutableAttributedString(string: text)
        titleString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.styleSingle.rawValue, range: NSMakeRange(0, text.characters.count))
        self.setAttributedTitle(titleString, for: .normal)
    }
}
Durga Vundavalli
fuente
4
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
CGRect textRect = self.titleLabel.frame;

// need to put the line at top of descenders (negative value)
CGFloat descender = self.titleLabel.font.descender;

CGContextRef contextRef = UIGraphicsGetCurrentContext();
UIColor *colr;
// set to same colour as text
if (self.isHighlighted || self.isSelected) {
    colr=self.titleLabel.highlightedTextColor;
}
else{
    colr= self.titleLabel.textColor;
}
CGContextSetStrokeColorWithColor(contextRef, colr.CGColor);

CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y +        textRect.size.height + descender);

CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + textRect.size.height + descender);

CGContextClosePath(contextRef);

CGContextDrawPath(contextRef, kCGPathStroke);
}
//Override this to change the underline color to highlighted color
-(void)setHighlighted:(BOOL)highlighted
{
[super setHighlighted:highlighted];
// [self setNeedsDisplay];
}
Rohit
fuente
3

Ampliando la respuesta de @Nick H247, experimenté un problema en el que, en primer lugar, el subrayado no se volvía a dibujar cuando el botón cambiaba de tamaño en la rotación; Esto se puede resolver configurando el botón para volver a dibujar de la siguiente manera:

myButton.contentMode = UIViewContentModeRedraw; 

Esto obliga a que el botón se vuelva a dibujar cuando cambian los límites.

En segundo lugar, el código original suponía que solo tenía 1 línea de texto en el botón (mi botón se ajusta a 2 líneas en la rotación) y el subrayado solo aparece en la última línea de texto. El código drawRect se puede modificar para calcular primero el número de líneas en el botón, luego poner un subrayado en cada línea en lugar de solo en la parte inferior, de esta manera:

 - (void) drawRect:(CGRect)rect {
CGRect textRect = self.titleLabel.frame;

// need to put the line at top of descenders (negative value)
CGFloat descender = self.titleLabel.font.descender;

CGContextRef contextRef = UIGraphicsGetCurrentContext();

// set to same colour as text
CGContextSetStrokeColorWithColor(contextRef, self.titleLabel.textColor.CGColor);

CGSize labelSize = [self.titleLabel.text sizeWithFont:self.titleLabel.font
                            constrainedToSize:self.titleLabel.frame.size
                                lineBreakMode:UILineBreakModeWordWrap];

CGSize labelSizeNoWrap = [self.titleLabel.text sizeWithFont:self.titleLabel.font forWidth:self.titleLabel.frame.size.width lineBreakMode:UILineBreakModeMiddleTruncation ];

int numberOfLines = abs(labelSize.height/labelSizeNoWrap.height);

for(int i = 1; i<=numberOfLines;i++) {
 //        Original code
 //        CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + textRect.size.height + descender + PADDING);
 //        
 //        CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + textRect.size.height + descender);

    CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + (labelSizeNoWrap.height*i) + descender + PADDING);

    CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + (labelSizeNoWrap.height*i) + descender);

    CGContextClosePath(contextRef);

    CGContextDrawPath(contextRef, kCGPathStroke);

}


}

¡Espero que este código ayude a alguien más!

AndroidNoob
fuente
3

En rápido

func underlineButton(button : UIButton) {

var titleString : NSMutableAttributedString = NSMutableAttributedString(string: button.titleLabel!.text!)
titleString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleSingle.rawValue, range: NSMakeRange(0, button.titleLabel!.text!.utf16Count))
button.setAttributedTitle(titleString, forState: .Normal)}
Arshad
fuente
3

Puede usar este código para agregar subrayado con espacio en el botón.

  • Cuando intenté dibujar un subrayado del generador de interfaces. Se parece a la imagen de abajo.

1 - Referencia del creador de interfaces

ingrese la descripción de la imagen aquí

  • Y después de usar el siguiente código, logré el resultado que quería.

2 - usando el código descrito

ingrese la descripción de la imagen aquí

public func setTextUnderline()
    {
        let dummyButton: UIButton = UIButton.init()
        dummyButton.setTitle(self.titleLabel?.text, for: .normal)
        dummyButton.titleLabel?.font = self.titleLabel?.font
        dummyButton.sizeToFit()

        let dummyHeight = dummyButton.frame.size.height + 3

        let bottomLine = CALayer()
        bottomLine.frame = CGRect.init(x: (self.frame.size.width - dummyButton.frame.size.width)/2, y: -(self.frame.size.height - dummyHeight), width: dummyButton.frame.size.width, height: 1.0)
        bottomLine.backgroundColor = self.titleLabel?.textColor.cgColor
        self.layer.addSublayer(bottomLine)
    }
Ayaz Rafai
fuente
Gracias por este fragmento de código, que podría proporcionar una ayuda limitada e inmediata. Una explicación adecuada mejoraría en gran medida su valor a largo plazo al mostrar por qué esta es una buena solución al problema y lo haría más útil para futuros lectores con otras preguntas similares. Por favor, editar su respuesta a añadir un poco de explicación, incluyendo los supuestos realizados.
Toby Speight
3

La versión 5.0 de Swift que funciona a partir de septiembre de 2019 en Xcode 10.3:

extension UIButton {
  func underlineText() {
    guard let title = title(for: .normal) else { return }

    let titleString = NSMutableAttributedString(string: title)
    titleString.addAttribute(
      .underlineStyle,
      value: NSUnderlineStyle.single.rawValue,
      range: NSRange(location: 0, length: title.count)
    )
    setAttributedTitle(titleString, for: .normal)
  }
}

Para usarlo, configure primero el título de su botón button.setTitle("Button Title", for: .normal)y luego llame button.underlineText()para resaltar ese título.

Max Desiatov
fuente
1
Puedo confirmar que esto funciona en versiones tan antiguas como iOS 10.3.1, Xcode 10.3 no admite simuladores anteriores a eso en Mojave, que yo sepa.
Max Desiatov
2

¿Cómo se manejará el caso cuando mantengamos presionado un botón subrayado? En ese caso, el color del texto del botón cambia según el color resaltado pero la línea permanece del color original. Digamos que si el color del texto del botón en estado normal es negro, entonces su subrayado también tendrá color negro. El color resaltado del botón es blanco. Mantener el botón presionado cambia el color del texto del botón de negro a blanco, pero el color de subrayado sigue siendo negro.

Parvez Qureshi
fuente
2
Puede probar si el botón está resaltado o seleccionado, y establecer el color en consecuencia. no estoy seguro de si se volverá a solicitar automáticamente, de lo contrario, deberá anular setSelected / setHighlighted y llamar a super y [self setNeedsDisplay]
Nick H247
2

Creo que es un error en el editor de fuentes en XCode. Si utiliza el generador de interfaces, debe cambiar el título de Normal a Atribuido, abrir TextEdit, crear texto subrayado y copiar y pegar en el cuadro de texto en XCode

dangh
fuente
2

Respuesta de Nick H247 pero enfoque rápido:

import UIKit

class UnderlineUIButton: UIButton {

    override func drawRect(rect: CGRect) {
        super.drawRect(rect)

        let textRect = self.titleLabel!.frame

        var descender = self.titleLabel?.font.descender

        var contextRef: CGContextRef = UIGraphicsGetCurrentContext();

        CGContextSetStrokeColorWithColor(contextRef, self.titleLabel?.textColor.CGColor);

        CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + textRect.size.height + descender!);

        CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + textRect.size.height + descender!);

        CGContextClosePath(contextRef);

        CGContextDrawPath(contextRef, kCGPathStroke);
    }
}
el.severo
fuente
2
func underline(text: String, state: UIControlState = .normal, color:UIColor? = nil) {
        var titleString = NSMutableAttributedString(string: text)

        if let color = color {
            titleString = NSMutableAttributedString(string: text,
                               attributes: [NSForegroundColorAttributeName: color])
        }

        let stringRange = NSMakeRange(0, text.characters.count)
        titleString.addAttribute(NSUnderlineStyleAttributeName,
                                 value: NSUnderlineStyle.styleSingle.rawValue,
                                 range: stringRange)

        self.setAttributedTitle(titleString, for: state)
    }
LuAndre
fuente
1

Versión Swift 3 para la respuesta de @ NickH247 con color de subrayado personalizado, ancho de línea y espacio:

import Foundation

class UnderlinedButton: UIButton {

    private let underlineColor: UIColor
    private let thickness: CGFloat
    private let gap: CGFloat

    init(underlineColor: UIColor, thickness: CGFloat, gap: CGFloat, frame: CGRect? = nil) {
        self.underlineColor = underlineColor
        self.thickness = thickness
        self.gap = gap
        super.init(frame: frame ?? .zero)
    }

    override func draw(_ rect: CGRect) {
        super.draw(rect)

        guard let textRect = titleLabel?.frame,
            let decender = titleLabel?.font.descender,
            let context = UIGraphicsGetCurrentContext() else { return }

        context.setStrokeColor(underlineColor.cgColor)
        context.move(to: CGPoint(x: textRect.origin.x, y: textRect.origin.y + textRect.height + decender + gap))
        context.setLineWidth(thickness)
        context.addLine(to: CGPoint(x: textRect.origin.x + textRect.width, y: textRect.origin.y + textRect.height + decender + gap))
        context.closePath()
        context.drawPath(using: .stroke)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
Bughana
fuente