iOS NSAttributedString en UIButton


Estoy usando iOS 6, por lo que las cadenas atribuidas deben ser fáciles de usar, ¿verdad? Bien... no tanto.

Lo que quiero hacer:

Usando una subclase personalizada de UIButton (no hace nada personalizado a titleLabel), me gustaría tener un título de varias líneas atribuido que sea:

  1. Todas las mayúsculas (me doy cuenta de que no es parte de los atributos) en la primera línea
  2. En negrita en la primera línea
  3. Subrayado en la primera línea
  4. "Normal" peso en la segunda línea
  5. No subrayado en la segunda línea
  6. Centrado en ambas líneas

He sido capaz de obtener # ' s 1 a 5 hasta ahora (al menos, pensé que lo hice, pero las pruebas actuales están produciendo errores con texto multilínea), pero cuando traté de hacer algo (cualquier cosa!) para que el texto se centre, mi aplicación sigue fallando. Cuando intento que todos los 6 elementos funcionen (a través de varios métodos), obtengo el siguiente fallo/error:

Terminating app due to uncaught exception 
'NSInternalInconsistencyException', reason: 
'NSAttributedString invalid for autoresizing, 
it must have a single spanning paragraph style
(or none) with a non-wrapping lineBreakMode.'

Basado en lo que he probado, parece que puedo tener una de las siguientes opciones, pero no ambas:

  1. Una etiqueta multi-línea, centrada
  2. Una etiqueta atribuida

Puedo vivir con uno u otro si debo, pero no puedo creer que no pueda tener lo que parece ser un concepto bastante sencillo.

¿Puede alguien por favor decirme lo que tengo mal?

Aquí está la última iteración del código que estoy intentando:

NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[style setAlignment:NSTextAlignmentCenter];
[style setLineBreakMode:NSLineBreakByWordWrapping];

UIFont *font1 = [UIFont fontWithName:@"HelveticaNeue-Medium" size:20.0f];
UIFont *font2 = [UIFont fontWithName:@"HelveticaNeue-Light"  size:20.0f];
NSDictionary *dict1 = @{NSUnderlineStyleAttributeName:@(NSUnderlineStyleSingle),  
                        NSFontAttributeName:font1};
NSDictionary *dict2 = @{NSUnderlineStyleAttributeName:@(NSUnderlineStyleNone),    
                        NSFontAttributeName:font2};

NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] init];
[attString appendAttributedString:[[NSAttributedString alloc] initWithString:@"LINE 1\n"    attributes:dict1]];
[attString appendAttributedString:[[NSAttributedString alloc] initWithString:@"line 2"      attributes:dict2]];
[[self buttonToStyle] setAttributedTitle:attString forState:UIControlStateNormal];
[[[self buttonToStyle] titleLabel] setNumberOfLines:0];
[[[self buttonToStyle] titleLabel] setLineBreakMode:NSLineBreakByWordWrapping];
Author: Bartłomiej Semańczyk, 2013-07-20

2 answers

Me parece que olvidaste en tu código usar el objeto "style" que configuraste.. acabas de instanciarlo. Deberías modificar tu código para que se vea así:

NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[style setAlignment:NSTextAlignmentCenter];
[style setLineBreakMode:NSLineBreakByWordWrapping];

UIFont *font1 = [UIFont fontWithName:@"HelveticaNeue-Medium" size:20.0f];
UIFont *font2 = [UIFont fontWithName:@"HelveticaNeue-Light"  size:20.0f];
NSDictionary *dict1 = @{NSUnderlineStyleAttributeName:@(NSUnderlineStyleSingle),
                        NSFontAttributeName:font1,
                        NSParagraphStyleAttributeName:style}; // Added line
NSDictionary *dict2 = @{NSUnderlineStyleAttributeName:@(NSUnderlineStyleNone),
                        NSFontAttributeName:font2,
                        NSParagraphStyleAttributeName:style}; // Added line

NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] init];
[attString appendAttributedString:[[NSAttributedString alloc] initWithString:@"LINE 1\n"    attributes:dict1]];
[attString appendAttributedString:[[NSAttributedString alloc] initWithString:@"line 2"      attributes:dict2]];
[self.resolveButton setAttributedTitle:attString forState:UIControlStateNormal];
[[self.resolveButton titleLabel] setNumberOfLines:0];
[[self.resolveButton titleLabel] setLineBreakMode:NSLineBreakByWordWrapping];

Tenga en cuenta que solo agregué las líneas que definen el NSParagraphStyleAttributeName.. todo lo demás es igual.. y esto es lo que obtengo por el botón:

introduzca la descripción de la imagen aquí

Y aquí está en Swift 3.0

let style = NSMutableParagraphStyle()
style.alignment = .center
style.lineBreakMode = .byWordWrapping

guard
    let font1 = UIFont(name: "HelveticaNeue-Medium", size: 20),
    let font2 = UIFont(name: "HelveticaNeue-Light", size: 20)  else { return }

let dict1:[String:Any] = [
    NSUnderlineStyleAttributeName:NSUnderlineStyle.styleSingle.rawValue,
    NSFontAttributeName:font1,
    NSParagraphStyleAttributeName:style
]

let dict2:[String:Any] = [
    NSUnderlineStyleAttributeName:NSUnderlineStyle.styleNone.rawValue,
    NSFontAttributeName:font2,
    NSParagraphStyleAttributeName:style
]

let attString = NSMutableAttributedString()
attString.append(NSAttributedString(string: "LINE 1", attributes: dict1))
attString.append(NSAttributedString(string: "line 2", attributes: dict2))

button.setAttributedTitle(attString, for: .normal)
button.titleLabel?.numberOfLines = 0
button.titleLabel?.lineBreakMode = .byWordWrapping
 89
Author: ccwasden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-27 22:39:21

Con Swift 4, puede usar la implementación de la subclase UIButton a continuación para resolver su problema:

import UIKit

class CustomButton: UIButton {

    required init(title: String, subtitle: String) {
        super.init(frame: CGRect.zero)

        let style = NSMutableParagraphStyle()
        style.alignment = NSTextAlignment.center
        style.lineBreakMode = NSLineBreakMode.byWordWrapping

        let titleAttributes: [NSAttributedStringKey : Any] = [
            NSAttributedStringKey.underlineStyle : NSUnderlineStyle.styleSingle.rawValue,
            NSAttributedStringKey.font : UIFont.preferredFont(forTextStyle: UIFontTextStyle.largeTitle),
            NSAttributedStringKey.paragraphStyle : style
        ]
        let subtitleAttributes = [
            NSAttributedStringKey.font : UIFont.preferredFont(forTextStyle: UIFontTextStyle.body),
            NSAttributedStringKey.paragraphStyle : style
        ]

        let attributedString = NSMutableAttributedString(string: title, attributes: titleAttributes)
        attributedString.append(NSAttributedString(string: "\n"))
        attributedString.append(NSAttributedString(string: subtitle, attributes: subtitleAttributes))

        setAttributedTitle(attributedString, for: UIControlState.normal)
        titleLabel?.numberOfLines = 0
        titleLabel?.lineBreakMode = NSLineBreakMode.byWordWrapping
    }

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

}

Uso:

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let button = CustomButton(title: "Title", subtitle: "Subtitle")
        button.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(button)

        let horizontalConstraint = button.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        let verticalConstraint = button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint])
    }

}

Como alternativa si realmente necesita un botón de tipo system, puede usar el siguiente código:

import UIKit

extension UIButton {

    static func customSystemButton(title: String, subtitle: String) -> UIButton {            
        let style = NSMutableParagraphStyle()
        style.alignment = NSTextAlignment.center
        style.lineBreakMode = NSLineBreakMode.byWordWrapping

        let titleAttributes: [NSAttributedStringKey : Any] = [
            NSAttributedStringKey.underlineStyle : NSUnderlineStyle.styleSingle.rawValue,
            NSAttributedStringKey.font : UIFont.preferredFont(forTextStyle: UIFontTextStyle.largeTitle),
            NSAttributedStringKey.paragraphStyle : style
        ]
        let subtitleAttributes = [
            NSAttributedStringKey.font : UIFont.preferredFont(forTextStyle: UIFontTextStyle.body),
            NSAttributedStringKey.paragraphStyle : style
        ]

        let attributedString = NSMutableAttributedString(string: title, attributes: titleAttributes)
        attributedString.append(NSAttributedString(string: "\n"))
        attributedString.append(NSAttributedString(string: subtitle, attributes: subtitleAttributes))

        let button = UIButton(type: UIButtonType.system)
        button.setAttributedTitle(attributedString, for: UIControlState.normal)
        button.titleLabel?.numberOfLines = 0
        button.titleLabel?.lineBreakMode = NSLineBreakMode.byWordWrapping

        return button
    }

}

Uso:

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let button = UIButton.customSystemButton(title: "Title", subtitle: "Subtitle")
        button.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(button)

        let horizontalConstraint = button.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        let verticalConstraint = button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint])
    }

}

Las dos capturas de pantalla a continuación muestran la visualización del resultado para la subclase UIButton (a la izquierda) y para el botón tipo system (a la derecha):

introduzca la descripción de la imagen aquí

 3
Author: Imanou Petit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-02 13:25:25