Souligner le texte dans UIButton

143

Quelqu'un peut-il suggérer comment souligner le titre d'un UIButton? J'ai un UIButton de type personnalisé et je veux que le titre soit souligné, mais le constructeur d'interface ne propose aucune option pour le faire.

Dans Interface Builder, lorsque vous sélectionnez l'option Police pour un bouton, il fournit l'option de sélectionner Aucun, Simple, Double, Couleur, mais aucun de ces éléments ne modifie le titre du bouton.

Toute aide appréciée.

RVN
la source
1
Vous pouvez utiliser UITextView avec une chaîne attribuée en ajoutant un lien vers celui-ci comme dans cette question stackoverflow.com/questions/21629784/...
Khaled Annajar

Réponses:

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
la source
1
peut-être pas aussi rapide que nécessaire!
Nick H247
4
Merci, j'ai fini par l'appeler comme suit: UIButton * btn = [UIUnderlinedButton buttonWithType: UIButtonTypeCustom];
hb922
2
Le code fonctionne bien, mais j'ai remarqué que le soulignement ne rétrécissait pas / ne grossissait pas lorsque la vue change de taille lors de la rotation, car il drawRectn'est pas appelé lors de la rotation. Cela peut être résolu en paramétrant votre bouton pour qu'il soit redessiné comme ceci: myButton.contentMode = UIViewContentModeRedraw;ce qui force le bouton à se redessiner lorsque les limites changent.
AndroidNoob
4
Vous pouvez également remplacer la setTitleméthode comme objective-c - (void)setTitle:(NSString *)title forState:(UIControlState)state { [super setTitle:title forState:state]; [self setNeedsDisplay]; }
suit
379

Pour utiliser le générateur d'interface pour souligner, il faut:

  • Changez-le en attribué
  • Mettez le texte en surbrillance dans l'inspecteur des attributs
  • Faites un clic droit, choisissez Police puis Soulignez

Souligner avec IB

Vidéo réalisée par quelqu'un d'autre https://www.youtube.com/watch?v=5-ZnV3jQd9I

finneypeut aider
la source
2
Bonne question @ new2ios Peut-être que quelqu'un d'autre sait
finneycanhelp
1
Je vais poser une nouvelle question, @finneycanhelp. J'espère que dans Xcode 6.3, il y aura un moyen plus simple. Je veux dire que je peux peut-être définir votre solution, puis l'utiliser setTitleavec du texte attribué. Pour moi, créer un bouton personnalisé pour dessiner un soulignement est un peu exotique (peut-être que je suis encore nouveau sur iOS, même si une application est terminée).
new2ios
2
finneydonehelped! Merci pour cela! impossible de comprendre pourquoi la boîte de dialogue contextuelle Polices n'avait aucun effet. Le clic droit est parfait.
IMFletcher
2
Bonne réponse pour les utilisateurs d'Interface Builder pour ce genre de choses simples qui sont un peu de travail lorsque vous faites dans Code. Merci! (Y)
Randika Vishman
1
Pourquoi les développeurs iOS préfèrent-ils écrire un code très long juste pour un problème très simple?
mr5
129

À partir d'iOS6, il est désormais possible d'utiliser un NSAttributedString pour effectuer un soulignement (et toute autre prise en charge des chaînes attribuées) d'une manière beaucoup plus 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];

Remarque: ajouté ceci comme une autre réponse - car c'est une solution totalement différente de la précédente.

Edit: bizarrement (sous iOS8 au moins) il faut souligner le premier caractère sinon ça ne marche pas!

pour contourner le problème, définissez le premier caractère souligné avec une couleur claire!

    // 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
la source
9
Sachez simplement que lorsque vous le faites de cette façon, vous devez également ajouter un attribut pour la couleur, car le texte du titre attribué n'utilisera pas la couleur que vous avez définie à l'aide de setTitleColor: forState:
daveMac
2
Génial, et merci @daveMac pour les informations sur la couleur. Pour ceux qui ne connaissent pas l'attribut, c'est: NSForegroundColorAttributeName
Ryan Crews
dans ces méthodes, le bouton souligné est proche du texte toute méthode pour changer la position y du soulignement?
Ilesh P
49

Vous pouvez le faire dans le générateur d'interface lui-même.

  1. Sélectionnez l'inspecteur d'attributs
  2. Changer le type de titre de simple à attribué

entrez la description de l'image ici

  1. Définissez la taille de police et l'alignement du texte appropriés

entrez la description de l'image ici

  1. Sélectionnez ensuite le texte du titre et définissez la police comme soulignée

entrez la description de l'image ici

Lineesh K Mohan
la source
28

C'est très simple avec une chaîne attribuée

Crée un dictionnaire avec des attributs définis et s'applique à la chaîne attribuée. Ensuite, vous pouvez définir la chaîne attribuée en tant que titre attribué dans uibutton ou texte attribué dans 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
la source
Quoi commentString; avez-vous copié la réponse de @ NickH247?
sens-questions
21

Voici ma fonction, fonctionne dans 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)
}

MISE À JOUR de l'extension 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
la source
13

La réponse de Nick est un excellent moyen rapide de le faire.

J'ai ajouté le support drawRectpour les ombres.

La réponse de Nick ne prend pas en compte si le titre de votre bouton a une ombre sous le texte:

entrez la description de l'image ici

Mais vous pouvez déplacer le soulignement vers le bas de la hauteur de l'ombre comme ceci:

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

Ensuite, vous obtiendrez quelque chose comme ceci:

entrez la description de l'image ici

Annie
la source
self.titleLabel.font.descender; cela a été amorti dans iOS 3.0
KING
5

Pour Swift 3, l'extension suivante peut être utilisée:

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
la source
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
la source
3

En développant la réponse de @Nick H247, j'ai rencontré un problème où, tout d'abord, le soulignement n'était pas redessiné lorsque le bouton était redimensionné lors de la rotation; ceci peut être résolu en paramétrant votre bouton pour redessiner comme ceci:

myButton.contentMode = UIViewContentModeRedraw; 

Cela force le bouton à se redessiner lorsque les limites changent.

Deuxièmement, le code d'origine supposait que vous n'aviez qu'une ligne de texte dans le bouton (mon bouton s'habille à 2 lignes lors de la rotation) et le soulignement n'apparaît que sur la dernière ligne de texte. Le code drawRect peut être modifié pour d'abord calculer le nombre de lignes dans le bouton, puis mettre un soulignement sur chaque ligne plutôt que juste le bas, comme ceci:

 - (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);

}


}

J'espère que ce code aide quelqu'un d'autre!

AndroidNoob
la source
3

En rapide

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
la source
3

Vous pouvez utiliser ce code pour ajouter un soulignement avec un espacement dans le bouton.

  • Quand j'ai essayé de dessiner un soulignement à partir du constructeur d'interface. Cela ressemble à l'image ci-dessous.

1 - Référence constructeur d'interface

entrez la description de l'image ici

  • Et après avoir utilisé le code ci-dessous, j'ai obtenu le résultat que je voulais.

2 - en utilisant le code décrit

entrez la description de l'image ici

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
la source
Merci pour cet extrait de code, qui pourrait fournir une aide limitée et immédiate. Une explication appropriée améliorerait considérablement sa valeur à long terme en montrant pourquoi c'est une bonne solution au problème, et la rendrait plus utile aux futurs lecteurs avec d'autres questions similaires. Veuillez modifier votre réponse pour ajouter des explications, y compris les hypothèses que vous avez formulées.
Toby Speight
3

La version Swift 5.0 qui fonctionne à partir de septembre 2019 dans 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)
  }
}

Pour l'utiliser, définissez d'abord le titre de votre bouton avec button.setTitle("Button Title", for: .normal), puis appelez button.underlineText()pour que ce titre soit souligné.

Max Desiatov
la source
1
Je peux confirmer que cela fonctionne sur des versions aussi anciennes que iOS 10.3.1, Xcode 10.3 ne prend pas en charge les simulateurs plus anciens que celui de Mojave, pour autant que je sache.
Max Desiatov
2

Comment va-t-on gérer le cas lorsque nous maintenons un bouton souligné enfoncé? Dans ce cas, la couleur du texte du bouton change en fonction de la couleur en surbrillance mais la ligne reste de la couleur d'origine. Disons que si la couleur du texte du bouton à l'état normal est noire, son soulignement aura également une couleur noire. La couleur en surbrillance du bouton est le blanc. Si vous maintenez le bouton enfoncé, la couleur du texte du bouton passe du noir au blanc, mais la couleur de soulignement reste noire.

Parvez Qureshi
la source
2
Vous pouvez tester si le bouton est mis en surbrillance et / ou sélectionné, et définir la couleur en conséquence. Je ne sais pas si le rafraîchissement sera demandé automatiquement, sinon vous devrez remplacer setSelected / setHighlighted et appeler super et [self setNeedsDisplay]
Nick H247
2

Je crois que c'est un bug dans l'éditeur de polices dans XCode. Si vous utilisez le générateur d'interface, vous devez changer le titre de Plain à Attribué, ouvrez TextEdit, créez du texte souligné et copiez-collez dans la zone de texte dans XCode

dangh
la source
2

Réponse de Nick H247 mais approche 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
la source
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
la source
1

Version Swift 3 pour la réponse de @ NickH247 avec couleur de soulignement personnalisée, largeur de ligne et espace:

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
la source