Sublinhando o texto em UIButton

143

Alguém pode sugerir como sublinhar o título de um UIButton? Eu tenho um UIButton do tipo Personalizado e quero que o Título seja sublinhado, mas o Interface Builder não fornece nenhuma opção para fazer isso.

No Interface Builder, quando você seleciona a opção de fonte para um botão, ele fornece a opção para selecionar Nenhum, Único, Duplo, Cor, mas nenhum deles fornece nenhuma alteração no Título no botão.

Qualquer ajuda apreciada.

RVN
fonte
1
Você pode usar UITextView com corda atribuído acrescentando um link para ele como nesta questão stackoverflow.com/questions/21629784/...
Khaled Annajar

Respostas:

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
fonte
1
talvez não seja tão oportuno quanto necessário!
Nick H247
4
Obrigado, acabei chamando da seguinte forma: UIButton * btn = [UIUnderlinedButton buttonWithType: UIButtonTypeCustom];
hb922
2
O código funciona bem, mas notei que o sublinhado não diminuiu / cresceu quando a exibição muda de tamanho na rotação, causada por drawRectnão ser chamada na rotação. Isso pode ser resolvido configurando o botão para redesenhar da seguinte maneira: o myButton.contentMode = UIViewContentModeRedraw;que força o botão a redesenhar quando os limites mudam.
AndroidNoob
4
Você também pode substituir o setTitlemétodo assim:objective-c - (void)setTitle:(NSString *)title forState:(UIControlState)state { [super setTitle:title forState:state]; [self setNeedsDisplay]; }
Kirualex
379

Para usar o construtor de interface para sublinhar, é necessário:

  • Altere para atribuído
  • Destaque o texto no inspetor Atributos
  • Clique com o botão direito, escolha Fonte e, em seguida, Sublinhar

Sublinhar usando IB

Vídeo que alguém fez https://www.youtube.com/watch?v=5-ZnV3jQd9I

finneycanhelp
fonte
2
Boa pergunta @ new2ios talvez alguém sabe
finneycanhelp
1
Farei uma nova pergunta, @finneycanhelp. Espero que no Xcode 6.3 haja uma maneira mais fácil. Quero dizer que pode ser que eu possa definir sua solução e depois usá-la setTitlecom o texto atribuído. Para mim, criar um botão personalizado para desenhar sublinhado é um pouco exótico (talvez eu ainda seja novo no iOS, mesmo tendo um aplicativo concluído).
New2ios
2
finneydonehelped! obrigado por isso! não conseguia descobrir por que o diálogo pop-up de fontes não teve efeito. O clique com o botão direito é perfeito.
IMFletcher
2
Boa resposta para os usuários do Interface Builder para esse tipo de coisa simples, que é um pouco trabalhosa no código. Obrigado! (Y)
Randika Vishman 22/03
1
Por que os desenvolvedores do iOS preferem escrever um código muito longo apenas para um problema muito simples?
MR5
129

Agora, a partir do iOS6, agora é possível usar um NSAttributedString para executar sublinhados (e qualquer outro suporte atribuído a cadeias) de uma maneira muito mais flexível:

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: adicionou isso como outra resposta - como uma solução totalmente diferente da minha anterior.

Editar: estranhamente (no iOS8, pelo menos), você precisa sublinhar o primeiro caractere, caso contrário, não funciona!

Como alternativa, defina o primeiro caracter sublinhado com cores claras!

    // 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
fonte
9
Esteja ciente de que quando você fazê-lo desta forma, você também deve adicionar atributos para a cor, como o texto do título atribuído não vai usar a cor que você definir usando setTitleColor: forState:
daveMac
2
Incrível, e obrigado @daveMac pela atenção na cor. Para aqueles que não sabem o atributo é: NSForegroundColorAttributeName
Ryan Crews
nesses métodos, o botão sublinhado é próximo ao texto de qualquer método para alterar a posição y do sublinhado?
Ilesh P
49

Você pode fazer isso no próprio construtor de interface.

  1. Selecione o inspetor de atributos
  2. Alterar o tipo de título de simples para atribuído

insira a descrição da imagem aqui

  1. Definir o tamanho da fonte e o alinhamento de texto apropriados

insira a descrição da imagem aqui

  1. Em seguida, selecione o texto do título e defina a fonte como sublinhada

insira a descrição da imagem aqui

Lineesh K Mohan
fonte
28

É muito simples com string atribuída

Cria um dicionário com atributos definidos e aplica-se à sequência atribuída. Em seguida, você pode definir a sequência atribuída como attibutedtitle em uibutton ou attributetext em 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
fonte
O que é commentString; você copiou da resposta de @ NickH247?
significado-importa
21

Aqui está a minha função, funciona no 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)
}

Extensão UPDATE Swift 3.0:

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
fonte
13

A resposta de Nick é uma maneira excelente e rápida de fazer isso.

Eu adicionei suporte drawRectpara sombras.

A resposta de Nick não leva em conta se o título do botão tiver uma sombra abaixo do texto:

insira a descrição da imagem aqui

Mas você pode mover o sublinhado para baixo pela altura da sombra da seguinte forma:

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

Então você terá algo parecido com isto:

insira a descrição da imagem aqui

Annie
fonte
self.titleLabel.font.descender; isso foi depreciado no iOS 3.0
KING
5

Para Swift 3, a seguinte extensão pode ser usada:

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
fonte
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
fonte
3

Expandindo a resposta de @Nick H247, tive um problema em que, em primeiro lugar, o sublinhado não era redesenhado quando o botão era redimensionado na rotação; isso pode ser resolvido configurando o botão para redesenhar da seguinte maneira:

myButton.contentMode = UIViewContentModeRedraw; 

Isso força o botão a redesenhar quando os limites mudam.

Em segundo lugar, o código original supunha que você tinha apenas 1 linha de texto no botão (meu botão quebra duas linhas na rotação) e o sublinhado aparece apenas na última linha de texto. O código drawRect pode ser modificado para primeiro calcular o número de linhas no botão e, em seguida, colocar um sublinhado em todas as linhas, e não apenas na parte inferior, da seguinte forma:

 - (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 ajude outra pessoa!

AndroidNoob
fonte
3

Em 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
fonte
3

Você pode usar esse código para adicionar sublinhado com espaçamento no botão.

  • Quando tentei desenhar um sublinhado do construtor de interface. Parece com a imagem abaixo.

1 - Referência do Construtor de Interface

insira a descrição da imagem aqui

  • E depois de usar o código abaixo, consegui o resultado como queria.

2 - usando o código descrito

insira a descrição da imagem aqui

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
fonte
Obrigado por este trecho de código, que pode fornecer ajuda imediata e limitada. Uma explicação adequada melhoraria bastante seu valor a longo prazo, mostrando por que essa é uma boa solução para o problema e a tornaria mais útil para futuros leitores com outras perguntas semelhantes. Por favor edite sua resposta para adicionar alguma explicação, incluindo as suposições que você fez.
Toby Speight
3

A versão Swift 5.0 que funciona em setembro de 2019 no 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 usá-lo, defina primeiro o título do botão button.setTitle("Button Title", for: .normal)e depois ligue button.underlineText()para torná-lo sublinhado.

Max Desiatov
fonte
1
Posso confirmar que isso funciona em versões tão antigas quanto o iOS 10.3.1, o Xcode 10.3 não suporta simuladores mais antigos que o do Mojave, pelo que sei.
Max Desiatov
2

Como lidar com o caso quando mantemos um botão sublinhado pressionado? Nesse caso, a cor do texto do botão muda de acordo com a cor destacada, mas a linha permanece da cor original. Digamos que se a cor do texto do botão no estado normal for preta, o sublinhado também terá a cor preta. A cor destacada do botão é branca. Manter o botão pressionado altera a cor do texto do botão de preto para branco, mas a cor do sublinhado permanece preta.

Parvez Qureshi
fonte
2
Você pode testar se o botão está destacado e / ou selecionado e definir a cor de acordo. não tenho certeza se redesenho será solicitado automaticamente, caso contrário você precisará substituir setSelected / setHighlighted e super chamada e [auto setNeedsDisplay]
Nick H247
2

Eu acredito que é algum bug no editor de fontes no XCode. Se você estiver usando o construtor de interface, precisará alterar o título de Simples para Atribuído, abra o TextEdit, crie texto sublinhado e copie e cole para caixa de texto no XCode

dangh
fonte
2

A resposta de Nick H247, mas a abordagem Swift:

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
fonte
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
fonte
1

Versão Swift 3 para a resposta do @ NickH247 com sublinhado personalizado de cor, largura de linha e espaço:

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
fonte