Can't reset UILabel attributedText when a UITa

2020-06-18 09:12发布

问题:

The problem

I'm using a UITableView to show the list of transactions of a credit card. If the transaction is a chargeback, I'm adding a strikethrough style to the label:

The problem happens when that specific cell is reused. The strikethrought decoration is still there, even after resetting the text and attributedText property of the label.

Below I've added the relevant parts of my code:

Table view

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 view cell

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

}

Strikethrough style

extension String {

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

}

What I've tried

I've tried some approaches, without success:

  • Override prepareForReuse and reset the text and attributedText of the label there.
  • Create an else after the if transactionData.isChargeback to set the transactionDescriptionLabel and transactionValueLabel attributed text without the decoration

So, when the cell is reused, how can I reset it's labels to remove the strikethrough style I've added before?

回答1:

You should try to set with .attributedText here instead of using `.text'. If it won't work I'll delete my answer.

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

So, try this

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

One more thing. Actually I don't like didSet. I suggest you to create a method to configure your cell. Here is an example of what I want to tell you.

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

Next.

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
}


回答2:

Alex answer did fix my problem, but I've also found out why the reset of the labels didn't work. I'll leave it here in case it's useful for someone.

For some reason, if I reset only the attributedText, it works:

transactionDescriptionLabel.attributedText = nil
transactionValueLabel.attributedText = nil

Or, if I reset the attributedText first, and then reset the text, it also works:

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

According to the UILabel documentation, assigning a new value to either property replaces the value of the other, so the order should not matter. But it does, apparently.

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

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