iOS UIButton 上的 NSAttributedString

64

我使用的是iOS 6,因此属性字符串应该很容易使用,对吧?嗯……不尽然。

我想要做的事情是:

使用自定义的UIButton子类(它不会对titleLabel做任何自定义操作),我想要一个多行属性标题,需要:

  1. 第一行全大写(我知道这不是属性的一部分)
  2. 第一行加粗
  3. 第一行带下划线
  4. 第二行"正常"字重
  5. 第二行无下划线
  6. 两行都居中

到目前为止,我已经能够完成1至5号要求(至少我以为我做到了,但当前测试结果显示多行文本存在错误),但当我尝试做一些使文本居中的操作时,我的应用程序却总是崩溃。尝试通过各种方法让6个要求都能够实现时,我得到了以下的错误提示:

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.'

根据我所尝试的,似乎我只能选择以下两个选项中的一个,不能同时实现:

  1. 多行居中标签
  2. 带属性的标签

如果必须做出选择,我可以接受其中之一,但我不相信这个看起来相当简单的概念无法实现。

请问有人能告诉我哪里错了吗?

以下是我正在尝试的代码的最后一次迭代:

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

1
如何为UILabel添加样式并将其添加到按钮上? - JeffRegan
titleLabel 不应该是 UILabel 吗?我猜我可以这样做,但我仍然觉得这比必要的更大。 - mbm29414
这是iOS,什么都不合理...开玩笑的。我之前折腾过NSMutableAttributedString,但只带来了无尽的头痛。我的建议是尽可能保持简单和易用。 - JeffRegan
我并不反对,但我仍然坚持认为,如上所列的我的愿望并不是特别“前沿”!;-) 我不明白的是为什么我可以在一个普通的 UILabel 上实现它,但在 UIButton 上却似乎无法正常工作。奇怪。去看看你的想法是否可行。 - mbm29414
1
@JeffCompton 所以...你的答案完美地解决了我的问题!但是,我仍然不喜欢这个解决方案。它感觉很“临时修补”和不正确。我并不是在质疑它的有效性,只是认为它不应该是必要的。感谢您的帮助!如果您把您的评论作为答案,我会接受它(除非有人给我一个“本地”实现我想要的功能的方法)。 - mbm29414
3个回答

105

在我的看法中,你的代码好像忘记使用你设置的 "style" 对象了,你只是实例化它。你应该修改你的代码如下:

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];
请注意,我只添加了定义NSParagraphStyleAttributeName的行..其他都是相同的..这就是我为按钮获得的结果: enter image description here 这是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

当我使用属性文本时,有人有解决方案吗? - Arpit B Parekh
我在这里找到了答案:https://dev59.com/QYHba4cB1Zd3GeqPNSaF。 - Arpit B Parekh
1
如何添加颜色? - Abhishek Mitra
1
我知道了 NSForegroundColorAttributeName: UIColor.white - Abhishek Mitra
如果您不使用下划线样式,那么在第一行和第二行之间添加一个 '\n' 字符以强制换行是必要的。 - makeroo

17

使用Swift 5.1和iOS 13.1,您可以使用下面的UIButton子类实现来解决问题:

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: [NSAttributedString.Key : Any] = [
            NSAttributedString.Key.foregroundColor: UIColor.label,
            NSAttributedString.Key.underlineStyle : NSUnderlineStyle.single.rawValue,
            NSAttributedString.Key.font : UIFont.preferredFont(forTextStyle: UIFont.TextStyle.largeTitle),
            NSAttributedString.Key.paragraphStyle : style
        ]
        let subtitleAttributes = [
            NSAttributedString.Key.foregroundColor: UIColor.label,
            NSAttributedString.Key.font : UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body),
            NSAttributedString.Key.paragraphStyle : style
        ]

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

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

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

}

使用方法:

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])
    }

}

如果你真的需要一个类型为system的按钮,可以使用以下代码作为替代:

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: [NSAttributedString.Key : Any] = [
            NSAttributedString.Key.underlineStyle : NSUnderlineStyle.single.rawValue,
            NSAttributedString.Key.font : UIFont.preferredFont(forTextStyle: UIFont.TextStyle.largeTitle),
            NSAttributedString.Key.paragraphStyle : style
        ]
        let subtitleAttributes = [
            NSAttributedString.Key.font : UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body),
            NSAttributedString.Key.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: UIButton.ButtonType.system)
        button.setAttributedTitle(attributedString, for: UIControl.State.normal)
        button.titleLabel?.numberOfLines = 0
        button.titleLabel?.lineBreakMode = NSLineBreakMode.byWordWrapping

        return button
    }

}

使用方法:

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])
    }

}
下面展示了UIButton子类(左侧)和system类型按钮(右侧)的结果显示屏幕截图:
enter image description here

9

在 Swift 5.1 中,使用 NSAttributedString 标题的 UIButton 可以实现双行显示:

func customizeSubscribeButton() {
    subscribeButton.titleLabel?.numberOfLines = 2
    let title = NSMutableAttributedString()
    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.alignment = .center
    let part1 = NSAttributedString(string: "SUBSCRIBE FOR 12 MONTH\n",
                                   attributes: [NSAttributedString.Key.foregroundColor: UIColor.white,
                                                NSAttributedString.Key.font: UIFont.systemFont(ofSize: 24, weight: .semibold),
                                                NSAttributedString.Key.paragraphStyle: paragraphStyle])
    let part2 = NSAttributedString(string: "999.00 RUB ECONOMY 85%",
                                   attributes: [NSAttributedString.Key.foregroundColor: UIColor.white,
                                                NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12, weight: .light),
                                                NSAttributedString.Key.paragraphStyle: paragraphStyle])
    title.append(part1)
    title.append(part2)
    subscribeButton.setAttributedTitle(title, for: .normal)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接