无法删除 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
}
这里有一个简单的测试代码来演示我遇到的问题。当 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
}