无法删除 UITableViewCell 中 UILabel 中的删除线属性

Cannot remove strikethrough attribute in UILabel inside a UITableViewCell

这里有一个简单的测试代码来演示我遇到的问题。当 tableView 第一次出现时,它的所有行在单元格默认 textLabel 的文本中都有一个删除线,使用 attributedString。当我点击一行时,它应该删除删除线属性,但属性文本保持不变。我做错了什么?

import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    var tableData = ["Green tomatoes", "Yellow bananas", "Red peppers"]
    let cellId = "cellID"

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let tableview = UITableView(frame: CGRect(x: 0, y: 200, width: view.frame.width, height: view.frame.height))
        tableview.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
        tableview.rowHeight = 40
        tableview.delegate = self
        tableview.dataSource = self
        view.addSubview(tableview)
    }
 
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tableData.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
        cell.backgroundColor = #colorLiteral(red: 0.7176730633, green: 0.9274803996, blue: 0.9678700566, alpha: 1)
        cell.textLabel?.attributedText = tableData[indexPath.row].addStrikeThrough()
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
        cell.textLabel?.attributedText = tableData[indexPath.row].removeStrikeThrough() // THIS DOESN'T WORK
        tableView.reloadData() // This doesn't make any difference
    }
}

extension String {
    func addStrikeThrough() -> NSAttributedString {
        let attributeString = NSMutableAttributedString(string: self)
        attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle,
                                     value: 2,
                                     range: NSRange(location: 0, length: attributeString.length))
        attributeString.addAttribute(NSAttributedString.Key.strikethroughColor,
                                     value: UIColor.red,
                                     range: NSMakeRange(0, attributeString.length))
        return attributeString
    }
    
    func removeStrikeThrough() -> NSAttributedString {
        let attributeString = NSMutableAttributedString(string: self)
        attributeString.removeAttribute(NSAttributedString.Key.strikethroughStyle, range: NSMakeRange(0, attributeString.length))
        return attributeString
    }
}

但是,它 工作 对于常规 UILabel 来说还不错:


class ViewController: UIViewController {
    
    var label: UILabel!
    let labelText = "Hello World"
    
    override func viewDidLoad() {
        label = UILabel()
        label.attributedText = labelText.addStrikeThrough()
        label.backgroundColor = .yellow
        label.isUserInteractionEnabled = true
        label.translatesAutoresizingMaskIntoConstraints = false
       
        let tap = UITapGestureRecognizer(target: self, action: #selector(labelTapped))
        label.addGestureRecognizer(tap)
        
        view.addSubview(label)
        
        NSLayoutConstraint.activate([
            label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            label.topAnchor.constraint(equalTo: view.topAnchor, constant: 200),
            label.heightAnchor.constraint(equalToConstant: 30)
        ])
    }
    
    @objc func labelTapped() {
        label.attributedText = labelText.removeStrikeThrough() // THIS WORKS!
    }
}

extension String {
    func addStrikeThrough() -> NSAttributedString {
        let attributeString = NSMutableAttributedString(string: self)
        attributeString.addAttribute(NSAttributedString.Key.strikethroughStyle,
                                     value: 2,
                                     range: NSRange(location: 0, length: attributeString.length))
        attributeString.addAttribute(NSAttributedString.Key.strikethroughColor,
                                     value: UIColor.red,
                                     range: NSMakeRange(0, attributeString.length))
        return attributeString
    }
    
    func removeStrikeThrough() -> NSAttributedString {
        let attributeString = NSMutableAttributedString(string: self)
        attributeString.removeAttribute(NSAttributedString.Key.strikethroughStyle, range: NSMakeRange(0, attributeString.length))
        return attributeString
    }
}

好的,将我的评论转为答案,希望这对您有所帮助。

基本上,UITableView 使用 数据源 广告我没有看到更新它的代码。这个问题涉及两件事。首先,这个:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
    cell.textLabel?.attributedText = tableData[indexPath.row].removeStrikeThrough() // THIS DOESN'T WORK
    tableView.reloadData() // This doesn't make any difference
}

我看到的是您正在更新 单元,而不是数据源。让我假设您的数据源 - tableData 有两件事:一些文本和一个标志,用于指示是否使用删除线(例如,它设置为 true)。只需通过替换来更新数据源这与:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableData[indexPath.row].useStrikethrough = false
    tableView.reloadData() // This doesn't make any difference
}

请注意,您正在更新 数据,而不是 单元格

其次,像这样显示单元格时更新内容:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
    cell.backgroundColor = #colorLiteral(red: 0.7176730633, green: 0.9274803996, blue: 0.9678700566, alpha: 1)
    if tableData[indexPath.row].useStrikethrough {
        cell.textLabel?.attributedText = tableData[indexPath.row].addStrikeThrough()
    } else {
        cell.textLabel?.attributedText = tableData[indexPath.row].addStrikeThrough().removeStrikethrough()
    }
    return cell
}

我正在使用您的代码,所以请原谅对 .addStrikeThrough().removeStrikethrough() 的奇怪调用 - 这只是为了显示这里的主要问题。您可以轻松更改 that 逻辑。我希望展示的关键是:

  • 更新 tableData,而不是单元格本身,
  • 呼叫 reloadData - 你做得对 - 接下来,
  • 并更改cellForRowAt中的逻辑,告诉单元格是否使用删除线。

在您的 didSelectRowAt 中,您正在使一个单元格出队。永远不要在 cellForRowAt 数据源函数之外调用 dequeueReusableCell()

您可以使用 tableview.cellForRow(at:IndexPath) 获取指定索引路径的当前屏幕单元格(如果有的话)。

所以,你可以说:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let cell = tableView.cellForRow(at:indexPath) 
    cell.textLabel?.attributedText = tableData[indexPath.row].removeStrikeThrough() 
       
}

但不要这样做

table 视图单元格应仅反映您的数据。当行滚动进出视图时,它们会被重用。如果一行重新出现,将再次调用 cellForRowAt,在您的情况下,文本将再次具有删除线。

如果您直接更改单元格而不更改数据,那么下次调用该单元格时您将看到旧数据。

您需要更复杂的数据模型:

struct Item {
   var name
   var isStruck = true

   mutating func unstrike() -> Void {
       self.isStruck = false
   }

   var attributedText: NSAttributedText { 
        let baseText = NSMutableAttributedText(string: self.name)
        if self.isStruck {
            let range = NSRange(location:0, length: baseText.length)           
            baseText.addAttribute(NSAttributedString.Key.strikethroughStyle,
                                     value: 2,
                                     range: range)
            baseText.addAttribute(NSAttributedString.Key.strikethroughColor,
                                     value: UIColor.red,
                                     range: range)
       }
       return baseText
    }
}

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    var tableData = [Item(name:"Green tomatoes"), Item(name:"Yellow bananas"), Item(name:"Red peppers")]

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
        cell.backgroundColor = #colorLiteral(red: 0.7176730633, green: 0.9274803996, blue: 0.9678700566, alpha: 1)
 
        cell.textLabel?.attributedText = tableData[indexPath.row].attributedText
        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableData[indexPath.row].unstrike()
        tableView.reloadRow(at: indexPath) // Don't reload the entire table unless all of the data has changed
    }