Podkreślenie tekstu w UIButton

143

Czy ktoś może podpowiedzieć, jak podkreślić tytuł UIButton? Mam UIButton typu niestandardowego i chcę, aby tytuł był podkreślony, ale program Interface Builder nie zapewnia żadnej opcji, aby to zrobić.

W programie Interface Builder po wybraniu opcji czcionki dla przycisku udostępnia opcję wyboru Brak, Pojedynczy, Podwójny, Kolor, ale żadna z nich nie zapewnia żadnych zmian w tytule przycisku.

Każda pomoc doceniona.

RVN
źródło
1
Możesz użyć UITextView z przypisanym ciągiem, dodając do niego link, tak jak w tym pytaniu stackoverflow.com/questions/21629784/…
Khaled Annajar

Odpowiedzi:

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
źródło
1
może nie tak na czas, jak potrzebujesz!
Nick H247
4
Dzięki, skończyło się na tym, że nazwałem to w następujący sposób: UIButton * btn = [UIUnderlinedButton buttonWithType: UIButtonTypeCustom];
hb922
2
Kod działa dobrze, ale zauważyłem, że podkreślenie nie zmniejsza się / nie powiększa, gdy widok zmienia rozmiar przy obracaniu, co jest spowodowane drawRectbrakiem wywołania przy obracaniu. Można to rozwiązać, ustawiając przycisk tak, aby przerysował: myButton.contentMode = UIViewContentModeRedraw;co zmusza przycisk do przerysowywania, gdy zmieniają się granice.
AndroidNoob,
4
Możesz także zmienić tę setTitlemetodę w następujący sposób:objective-c - (void)setTitle:(NSString *)title forState:(UIControlState)state { [super setTitle:title forState:state]; [self setNeedsDisplay]; }
Kirualex,
379

Aby użyć konstruktora interfejsu do podkreślenia, należy:

  • Zmień to na przypisane
  • Podświetl tekst w Inspektorze atrybutów
  • Kliknij prawym przyciskiem myszy, wybierz Czcionka, a następnie Podkreśl

Podkreśl używając IB

Wideo, które zrobił ktoś inny https://www.youtube.com/watch?v=5-ZnV3jQd9I

finneycanhelp
źródło
2
Dobre pytanie @ new2ios Może ktoś inny wie
finneycanhelp
1
Zadam nowe pytanie @finneycanhelp. Mam nadzieję, że w Xcode 6.3 będzie łatwiejszy sposób. Chodzi mi o to, że mogę ustawić twoje rozwiązanie, a następnie użyć setTitlez przypisanym tekstem. Dla mnie tworzenie niestandardowego przycisku do rysowania podkreślenia jest nieco egzotyczne (być może wciąż jestem nowy w iOS, nawet mając ukończoną jedną aplikację).
new2ios
2
finneydonehelped! dzięki za to! nie mógł zrozumieć, dlaczego wyskakujące okno dialogowe Czcionki nie przyniosło efektu. Prawe kliknięcie jest idealne.
IMFletcher
2
Dobra odpowiedź dla użytkowników Interface Builder na tego rodzaju proste rzeczy, które wymagają trochę pracy w kodzie. Dzięki! (Y)
Randika Vishman,
1
Dlaczego programiści iOS wolą pisać bardzo długi kod tylko dla bardzo prostego problemu?
mr5
129

Od iOS6 można teraz używać NSAttributedString do wykonywania podkreślania (i obsługi wszystkich innych przypisanych ciągów) w znacznie bardziej elastyczny sposób:

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];

Uwaga: dodałem to jako kolejną odpowiedź - ponieważ jest to zupełnie inne rozwiązanie niż moje poprzednie.

Edycja: dziwnie (przynajmniej w iOS8) musisz podkreślić pierwszy znak, inaczej to nie zadziała!

aby obejść ten problem, ustaw pierwszy znak podkreślony wyraźnym kolorem!

    // 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
źródło
9
Pamiętaj tylko, że robiąc to w ten sposób, musisz również dodać atrybut dla koloru, ponieważ przypisany tekst tytułu nie będzie używał koloru ustawionego za pomocą setTitleColor: forState:
daveMac
2
Wspaniale i dziękuję @daveMac za uwagę na kolor. Dla tych, którzy nie znają atrybutu, to: NSForegroundColorAttributeName
Ryan Crews
w tej metodzie podkreślenie przycisku jest zbliżone do tekstu, jakakolwiek metoda zmiany pozycji y podkreślenia?
Ilesh P
49

Możesz to zrobić w samym konstruktorze interfejsu.

  1. Wybierz inspektora atrybutów
  2. Zmień typ tytułu ze zwykłego na przypisany

wprowadź opis obrazu tutaj

  1. Ustaw odpowiedni rozmiar czcionki i wyrównanie tekstu

wprowadź opis obrazu tutaj

  1. Następnie zaznacz tekst tytułu i ustaw czcionkę jako podkreśloną

wprowadź opis obrazu tutaj

Lineesh K Mohan
źródło
28

Jest to bardzo proste z przypisanym ciągiem

Tworzy słownik z ustawionymi atrybutami i stosuje się do przypisanego ciągu. Następnie możesz ustawić przypisany ciąg jako przypisany tytuł w uibutton lub atrybut przypisany w 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
źródło
Co commentString; skopiowałeś z odpowiedzi @ NickH247?
znaczenie-ma znaczenie
21

Oto moja funkcja, działa w 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)
}

Rozszerzenie 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
źródło
13

Odpowiedź Nicka to świetny, szybki sposób na zrobienie tego.

Dodałem obsługę drawRectcieni.

Odpowiedź Nicka nie bierze pod uwagę, jeśli tytuł przycisku ma cień pod tekstem:

wprowadź opis obrazu tutaj

Ale możesz przesunąć podkreślenie w dół o wysokość cienia w następujący sposób:

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

Wtedy otrzymasz coś takiego:

wprowadź opis obrazu tutaj

annie
źródło
self.titleLabel.font.descender; zostało to umorzone w iOS 3.0
KING
5

W przypadku Swift 3 można użyć następującego rozszerzenia:

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
źródło
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
źródło
3

Rozwijając odpowiedź autorstwa @Nick H247, napotkałem problem polegający na tym, że po pierwsze podkreślenie nie było przerysowywane, gdy rozmiar przycisku zmieniał się podczas obracania; można to rozwiązać, ustawiając przycisk tak, aby przerysował:

myButton.contentMode = UIViewContentModeRedraw; 

Zmusza to przycisk do przerysowywania, gdy zmieniają się granice.

Po drugie, oryginalny kod zakładał, że masz tylko 1 wiersz tekstu na przycisku (mój przycisk zawija się do 2 wierszy po obróceniu), a podkreślenie pojawia się tylko w ostatnim wierszu tekstu. Kod drawRect można zmodyfikować, aby najpierw obliczyć liczbę wierszy w przycisku, a następnie umieścić podkreślenie na każdym wierszu, a nie tylko na dole, na przykład:

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

}


}

Mam nadzieję, że ten kod pomoże komuś innemu!

AndroidNoob
źródło
3

Szybko

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
źródło
3

Możesz użyć tego kodu, aby dodać podkreślenie z odstępami w przycisku.

  • Kiedy próbowałem narysować podkreślenie z konstruktora interfejsu. Wygląda jak na poniższym obrazku.

1 - odniesienie do konstruktora interfejsu

wprowadź opis obrazu tutaj

  • A po zastosowaniu poniższego kodu osiągnąłem wynik taki, jaki chciałem.

2 - za pomocą opisanego kodu

wprowadź opis obrazu tutaj

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
źródło
Dziękujemy za ten fragment kodu, który może zapewnić ograniczoną, natychmiastową pomoc. Właściwe wyjaśnienie znacznie poprawiłoby jego długoterminową wartość, pokazując, dlaczego jest to dobre rozwiązanie problemu, i uczyniłoby go bardziej użytecznym dla przyszłych czytelników z innymi, podobnymi pytaniami. Proszę edytować swoją odpowiedź dodać kilka wyjaśnień, w tym założeń już wykonanych.
Toby Speight
3

Wersja Swift 5.0, która działa od września 2019 w 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)
  }
}

Aby go użyć, najpierw ustaw tytuł przycisku za pomocą, button.setTitle("Button Title", for: .normal)a następnie zadzwoń, button.underlineText()aby ten tytuł był podkreślony.

Max Desiatov
źródło
1
Mogę potwierdzić, że działa to na wersjach tak starych jak iOS 10.3.1, Xcode 10.3 nie obsługuje symulatorów starszych niż na Mojave, o ile wiem.
Max Desiatov
2

Jak sobie poradzimy ze sprawą, kiedy przytrzymamy wciśnięty przycisk podkreślony? W takim przypadku kolor tekstu przycisku zmienia się zgodnie z podświetlonym kolorem, ale linia pozostaje w oryginalnym kolorze. Powiedzmy, że jeśli kolor tekstu przycisku w stanie normalnym jest czarny, to jego podkreślenie również będzie miało kolor czarny. Podświetlony kolor przycisku jest biały. Przytrzymanie wciśniętego przycisku zmienia kolor tekstu przycisku z czarnego na biały, ale kolor podkreślenia pozostaje czarny.

Parvez Qureshi
źródło
2
Możesz sprawdzić, czy przycisk jest podświetlony i czy wybrany, i odpowiednio ustawić kolor. Nie jestem pewien, czy przerysowanie będzie żądane automatycznie, jeśli nie, musisz nadpisać setSelected / setHighlighted i zadzwonić do super i [self setNeedsDisplay]
Nick H247
2

Uważam, że to jakiś błąd w edytorze czcionek w XCode. Jeśli używasz konstruktora interfejsu, musisz zmienić tytuł ze zwykłego na przypisany, otwórz TextEdit, utwórz podkreślony tekst i skopiuj i wklej do pola tekstowego w XCode

dangh
źródło
2

Odpowiedź Nicka H247, ale szybkie podejście:

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
źródło
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
źródło
1

Wersja Swift 3 dla odpowiedzi @ NickH247 z niestandardowym kolorem podkreślenia, szerokością linii i odstępami:

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
źródło