重用 UITableViewCell 时无法重置 UILabel attributedText

Can't reset UILabel attributedText when a UITableViewCell is reused

问题

我正在使用 UITableView 来显示信用卡的交易列表。如果交易是拒付,我会在标签中添加删除线样式:

重复使用该特定单元格时会出现问题。即使在重置标签的 textattributedText 属性 之后,删除线装饰仍然存在。

下面我添加了我的代码的相关部分:

Table 查看

class TimelineViewController: UIViewController {

    private lazy var tableView: UITableView = {
        let tableView = UITableView.init(frame: view.frame, style: .plain)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.register(TimelineTableViewCell.self, forCellReuseIdentifier: TimelineTableViewCell.reuseIdentifier)
        tableView.dataSource = self
        tableView.delegate = self
        return tableView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        addTableViewOnView()
        getTimeline()
    }

}

extension TimelineViewController: UITableViewDataSource {

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: TimelineTableViewCell.reuseIdentifier,
                                                       for: indexPath) as? TimelineTableViewCell else { fatalError() }
        cell.transactionData = viewModel.timeline[indexPath.row]
        return cell
    }

}

Table 查看单元格

class TimelineTableViewCell: UITableViewCell {

    static let reuseIdentifier = "TimelineTableViewCell"

    var transactionData: TimelineResponseModel! {
        didSet {
            // Reset the labels
            transactionDescriptionLabel.text = nil
            transactionDescriptionLabel.attributedText = nil
            transactionValueLabel.text = nil
            transactionValueLabel.attributedText = nil

            // Fill in the values
            transactionDescriptionLabel.text = transactionData.description
            transactionValueLabel.text = Formatter.currency.string(for: transactionData.balance)

            if transactionData.isChargeback {
                let value = Formatter.currency.string(for: transactionData.balance) ?? ""
                transactionDescriptionLabel.attributedText = transactionData.description.strikedThrough()
                transactionValueLabel.attributedText = value.strikedThrough()
            }
        }
    }

    private lazy var transactionDescriptionLabel: UILabel = {
        let transactionDescriptionLabel = UILabel()
        transactionDescriptionLabel.translatesAutoresizingMaskIntoConstraints = false
        transactionDescriptionLabel.font = UIFont.preferredFont(forTextStyle: .footnote)
        transactionDescriptionLabel.adjustsFontForContentSizeCategory = true
        transactionDescriptionLabel.textColor = UIColor.activeText()
        transactionDescriptionLabel.numberOfLines = 0
        return transactionDescriptionLabel
    }()

    private lazy var transactionValueLabel: UILabel = {
        let transactionValueLabel = UILabel()
        transactionValueLabel.translatesAutoresizingMaskIntoConstraints = false
        transactionValueLabel.font = UIFont.preferredFont(forTextStyle: .caption1).bold()
        transactionValueLabel.adjustsFontForContentSizeCategory = true
        transactionValueLabel.textColor = UIColor.activeText()
        return transactionValueLabel
    }()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        addTransactionDescriptionLabel()
        addTransactionValueLabel()
    }

}

删除线样式

extension String {

    func strikedThrough() -> NSAttributedString {
        let strikethroughStyle = [NSAttributedString.Key.strikethroughStyle: NSUnderlineStyle.single.rawValue]
        return NSAttributedString(string: self, attributes: strikethroughStyle)
    }

}

我试过的

我尝试了一些方法,但没有成功:

那么,当单元格被重复使用时,如何重置它的标签以删除我之前添加的删除线样式?

您应该尝试在此处设置 .attributedText 而不是使用 `.text'。如果它不起作用,我会删除我的答案。

// Fill in the values
transactionDescriptionLabel.text = transactionData.description
transactionValueLabel.text = Formatter.currency.string(for: transactionData.balance)

所以,试试这个

transactionDescriptionLabel.attributedText = //
transactionValueLabel.attributedText = //

还有一件事。其实我不喜欢didSet。 我建议你创建一个方法来配置你的单元格。 这是我想告诉你的一个例子。

func configure(with transactionData: TimelineResponseModel) {
   // Reset the labels
   transactionDescriptionLabel.text = nil
   transactionDescriptionLabel.attributedText = nil
   transactionValueLabel.text = nil
   transactionValueLabel.attributedText = nil

   // Fill in the values
   transactionDescriptionLabel.text = transactionData.description
   transactionValueLabel.text = Formatter.currency.string(for: transactionData.balance)

   if transactionData.isChargeback {
      let value = Formatter.currency.string(for: transactionData.balance) ?? ""
      transactionDescriptionLabel.attributedText = transactionData.description.strikedThrough()
      transactionValueLabel.attributedText = value.strikedThrough()
   }
}

下一个。

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: TimelineTableViewCell.reuseIdentifier,
                                                   for: indexPath) as? TimelineTableViewCell else { fatalError() }
    // Thats what I really like
    cell.configure(with: viewModel.timeline[indexPath.row])
    return cell
}

Alex 的回答确实解决了我的问题,但我也发现了为什么重置标签不起作用。我会把它留在这里以防它对某人有用。

出于某种原因,如果我只重置 attributedText,它会起作用:

transactionDescriptionLabel.attributedText = nil
transactionValueLabel.attributedText = nil

或者,如果我先重置 attributedText,然后再重置 text,它也有效:

transactionDescriptionLabel.attributedText = nil
transactionValueLabel.attributedText = nil
transactionDescriptionLabel.text = nil
transactionValueLabel.text = nil

根据 UILabel 文档,将新值分配给其中一个 属性 会替换另一个的值,因此顺序无关紧要。但显然确实如此。

https://developer.apple.com/documentation/uikit/uilabel/1620538-text

https://developer.apple.com/documentation/uikit/uilabel/1620542-attributedtext

Swift5 UILabel 扩展

extension UILabel {
    func strikeThrough(_ isStrikeThrough: Bool = true) {
        guard let text = self.text else {
            return
        }
        
        if isStrikeThrough {
            let attributeString =  NSMutableAttributedString(string: text)
            attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: NSMakeRange(0,attributeString.length))
            self.attributedText = attributeString
        } else {
            let attributeString =  NSMutableAttributedString(string: text)
            attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: [], range: NSMakeRange(0,attributeString.length))
            self.attributedText = attributeString
        }
    }
}

我刚刚在 iOS 15.x 的应用程序中发现了同样的问题。 正如其他答案所建议的那样,我尝试了不同的方法来在设置新值之前删除属性字符串。

不幸的是,我无法让重置工作。 然后我意识到只有当属性(在我的例子中是背景颜色)覆盖了整个文本长度时才会出现问题。

为了防止这种情况发生,我在我的属性字符串中附加了一个不可见的字符,问题神奇地得到了解决。

let myString: NSMutableAttributedString = ...
myString.append(NSAttributedString(string: "\u{200B}")) // zero-width space

// set any attributes but do not set them on the last character

Li Jin Swift 上面的答案在分配给 Label 的 AttributedText 字段时正确地重置了 StrikethroughStyle 位。

Xamarin.iOS (C#) 示例:

// does not reset StrikethroughStyle bit
NSMutableAttributedString attrString = new NSMutableAttributedString("Text", foregroundColor: UIColor.Black, strikethroughStyle: NSUnderlineStyle.None);  
                        
// does reset bit StrikethroughStyle when assigned to a label
attrString.AddAttribute(UIStringAttributeKey.StrikethroughStyle, NSNumber.FromLong(0), new NSRange(0, attrString.Length)); 
                            
Label.AttributedText = attrString;