【问题标题】:Cannot remove strikethrough attribute in UILabel inside a UITableViewCell无法删除 UITableViewCell 内的 UILabel 中的删除线属性
【发布时间】:2021-11-14 13:31:46
【问题描述】:

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

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

【问题讨论】:

  • 我没有看到非常重要的事情 - 你如何刷新表格。你在哪里更新tableData 数组?我看到您正在更新选定的单元格内容,但 UITableView 基于数据源(在您的情况下为 tableData)并且它没有被更新。您可能需要 (1) 向此数组添加一个字段,声明是否使用删除线文本,(2) 初始化它以执行此操作,然后 (3) 更新该数组以指示指向 dataSource 的单元格被选中,并且最后(4)致电reloadData

标签: ios swift uitableview nsattributedstring


【解决方案1】:

在您的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() 
       
}

但不要这样做

表格视图单元格应该只反映您的数据。当行滚动进出视图时,它们会被重用。如果再次出现一行,则再次调用 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
    }

【讨论】:

  • 谢谢。对我来说,最大的收获是始终更新数据,而不是单元格。这有帮助。我修复了我的 ode,它现在可以工作了。
【解决方案2】:

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

基本上,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() 的奇怪调用——这只是为了说明这里的主要问题。您可以轻松更改 那个 逻辑。我希望我展示的关键是:

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

【讨论】:

  • 谢谢。对我来说,最大的收获是始终更新数据,而不是单元格。这有帮助。我修复了我的 ode,它现在可以工作了。
  • 没错。我不确定我的评论是否表明了这一点,所以我发布了一个答案。如果你曾经尝试使用 SwiftUI,这种事情——更新数据并让 UI 跟随——是一件大事。很高兴为您提供帮助!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-03
  • 1970-01-01
  • 1970-01-01
  • 2015-08-14
  • 2016-08-01
  • 1970-01-01
相关资源
最近更新 更多