重用 UITableViewCell 时无法重置 UILabel attributedText
Can't reset UILabel attributedText when a UITableViewCell is reused
问题
我正在使用 UITableView
来显示信用卡的交易列表。如果交易是拒付,我会在标签中添加删除线样式:
重复使用该特定单元格时会出现问题。即使在重置标签的 text
和 attributedText
属性 之后,删除线装饰仍然存在。
下面我添加了我的代码的相关部分:
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)
}
}
我试过的
我尝试了一些方法,但没有成功:
- 覆盖
prepareForReuse
并重置标签的 text
和 attributedText
。
- 在
if transactionData.isChargeback
之后创建一个 else
以设置没有修饰的 transactionDescriptionLabel
和 transactionValueLabel
属性文本
那么,当单元格被重复使用时,如何重置它的标签以删除我之前添加的删除线样式?
您应该尝试在此处设置 .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;
问题
我正在使用 UITableView
来显示信用卡的交易列表。如果交易是拒付,我会在标签中添加删除线样式:
重复使用该特定单元格时会出现问题。即使在重置标签的 text
和 attributedText
属性 之后,删除线装饰仍然存在。
下面我添加了我的代码的相关部分:
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)
}
}
我试过的
我尝试了一些方法,但没有成功:
- 覆盖
prepareForReuse
并重置标签的text
和attributedText
。 - 在
if transactionData.isChargeback
之后创建一个else
以设置没有修饰的transactionDescriptionLabel
和transactionValueLabel
属性文本
那么,当单元格被重复使用时,如何重置它的标签以删除我之前添加的删除线样式?
您应该尝试在此处设置 .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;