[英]Countdown timer in table view cell shows different values after scrolling
該問題在標題中進行了描述,但在這里更具體地描述了整個情況。
我有一個自定義的表格視圖單元格子類,其中帶有顯示倒數計時器的標簽。 當計時器的一小部分工作正常時,但是要處理大量數據,我需要顯示遠遠超出可見單元格的計時器,並且當我快速向下滾動然后快速向上滾動時,單元格中的計時器值開始顯示不同的值,直到在某個時間點之后,它會顯示正確的值。
我為那些可重復使用的單元嘗試了不同的變體,但是我找不到問題。 需要幫助!!!
這是邏輯實現的代碼。
自定義單元格子類:
let calendar = Calendar.current
var timer: Timer?
var deadlineDate: Date? {
didSet {
updateTimeLabel()
}
}
override func awakeFromNib() {
purchaseCellCardView.layer.cornerRadius = 10
let selectedView = UIView(frame: CGRect.zero)
selectedView.backgroundColor = UIColor.clear
selectedBackgroundView = selectedView
}
override func prepareForReuse() {
super.prepareForReuse()
if timer != nil {
print("Invalidated!")
timer?.invalidate()
timer = nil
}
}
func configure(for purchase: Purchase) {
purchaseSubjectLabel.text = purchase.subject
startingPriceLabel.text = purchase.NMC
stageLabel.text = purchase.stage
fzImageView.image = purchase.fedLaw.contains("44") ? #imageLiteral(resourceName: "FZ44") : #imageLiteral(resourceName: "FZ223")
timeLabel.isHidden = purchase.stage == "Работа комиссии"
warningImageView.image = purchase.warningImage
}
func updateTimeLabel() {
setTimeLeft()
timer = Timer(timeInterval: 1, repeats: true) { [weak self] _ in
guard let strongSelf = self else {return}
strongSelf.setTimeLeft()
}
RunLoop.current.add(timer!, forMode: .commonModes)
}
@objc private func setTimeLeft() {
let currentDate = getCurrentLocalDate()
if deadlineDate?.compare(currentDate) == .orderedDescending {
var components = calendar.dateComponents([.day, .hour, .minute, .second], from: currentDate, to: deadlineDate!)
let dayText = (components.day! == 0 || components.day! < 0) ? "" : String(format: "%i", components.day!)
let hourText = (components.hour == 0 || components.hour! < 0) ? "" : String(format: "%i", components.hour!)
switch (dayText, hourText) {
case ("", ""):
timeLabel.text = String(format: "%02i", components.minute!) + ":" + String(format: "%02i", components.second!)
case ("", _):
timeLabel.text = hourText + " ч."
default:
timeLabel.text = dayText + " дн."
}
} else {
stageLabel.text = "Работа комиссии"
timeLabel.text = ""
timeLabel.isHidden = true
timer?.invalidate()
}
}
private func getCurrentLocalDate() -> Date {
var now = Date()
var nowComponents = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: now)
nowComponents.timeZone = TimeZone(abbreviation: "UTC")
now = calendar.date(from: nowComponents)!
return now
}
deinit {
print("DESTROYED")
timer?.invalidate()
timer = nil
}
tableView(_cellForRowAt :)最重要的部分
case .results:
if filteredArrayOfPurchases.isEmpty {
let cell = tableView.dequeueReusableCell(
withIdentifier: TableViewCellIdentifiers.nothingFoundCell,
for: indexPath)
let label = cell.viewWithTag(110) as! UILabel
switch segmentedControl.index {
case 1:
label.text = "Нет закупок способом\n«Запрос предложений»"
case 2:
label.text = "Нет закупок способом\n«Конкурс»"
case 3:
label.text = "Нет закупок способом\n«Аукцион»"
default:
label.text = "Нет закупок способом\n«Запрос котировок»"
}
return cell
} else {
let cell = tableView.dequeueReusableCell(
withIdentifier: TableViewCellIdentifiers.purchaseCell,
for: indexPath) as! PurchaseCell
cell.containerViewTopConstraint.constant = indexPath.row == 0 ? 8.0 : 4.0
cell.containerViewBottomConstraint.constant = indexPath.row == filteredArrayOfPurchases.count - 1 ? 8.0 : 4.0
let purchase = filteredArrayOfPurchases[indexPath.row]
cell.configure(for: purchase)
if cell.timer != nil {
cell.updateTimeLabel()
} else {
search.getDeadlineDateAndTimeToApply(purchase.purchaseURL, purchase.fedLaw, purchase.stage, completion: { (date) in
cell.deadlineDate = date
})
}
return cell
}
最后一個難題:
func getDeadlineDateAndTimeToApply(_ url: URL?, _ fedLaw: String, _ stage: String, completion: @escaping (Date) -> ()) {
var deadlineDateAndTimeToApply = Date()
guard stage != "Работа комиссии" else { return }
if let url = url {
dataTask = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if let error = error as NSError?, error.code == -403 {
// TODO: Add alert here
return
}
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200, let data = data, let html = String(data: data, encoding: .utf8), let purchasePageBody = try? SwiftSoup.parse(html), let purchaseCard = try? purchasePageBody.select("td").array() else {return}
let mappedArray = purchaseCard.map(){String(describing: $0)}
if fedLaw.contains("44") {
guard let deadlineDateToApplyString = try? purchaseCard[(mappedArray.index(of: "<td class=\"fontBoldTextTd\">Дата и время окончания подачи заявок</td>"))! + 1].text().components(separatedBy: " ") else {return}
dateFormatter.dateFormat = "dd.MM.yyyy HH:mm"
let deadlineDateToApply = deadlineDateToApplyString.first!
let deadlineTimeToApply = deadlineDateToApplyString[1]
guard let deadlineDateAndTimeToApplyCandidate = dateFormatter.date(from: "\(deadlineDateToApply) \(deadlineTimeToApply)") else {return}
deadlineDateAndTimeToApply = deadlineDateAndTimeToApplyCandidate
} else {
guard let deadlineDateToApplyString = try? purchaseCard[(mappedArray.index(of: "<td>Дата и время окончания подачи заявок<br> (по местному времени заказчика)</td>"))! + 1].text().components(separatedBy: " ") else {return}
dateFormatter.dateFormat = "dd.MM.yyyy HH:mm"
let deadlineDateToApply = deadlineDateToApplyString.first!
let deadlineTimeToApply = deadlineDateToApplyString[2]
guard let deadlineDateAndTimeToApplyCandidate = dateFormatter.date(from: "\(deadlineDateToApply) \(deadlineTimeToApply)") else {return}
deadlineDateAndTimeToApply = deadlineDateAndTimeToApplyCandidate
}
DispatchQueue.main.async {
completion(deadlineDateAndTimeToApply)
}
})
dataTask?.resume()
}
}
一些注意事項:
您的問題在這里:
search.getDeadlineDateAndTimeToApply(purchase.purchaseURL,
purchase.fedLaw,
purchase.stage,
completion: { (date) in
cell.deadlineDate = date
})
getDeadlineDateAndTimeToApply
異步運行,計算某些內容,然后更新主線程中的cell.deadlineData
(可以)。 但是與此同時,在計算內容時,用戶可能會上下滾動, cell
可能已被重新用於另一行,現在更新不正確地更新了該cell
。 您需要做的是:不要直接存儲UITableViewCell
。 相反,請跟蹤要更新的IndexPath
,一旦計算完成,就檢索屬於該IndexPath
的單元格並進行更新。
這是很多代碼,但是根據您所描述的問題,是重用單元格。
您最好將計時器從單元格中分離出來並將它們放在對象中。 它們就是它所屬的地方(或在諸如視圖控制器之類的管理器中)。 想象一下,像下面這樣:
class MyObject {
var timeLeft: TimeInterval = 0.0 {
didSet {
if timeLeft > 0.0 && timer == nil {
timer = Timer.scheduled...
} else if timeLeft <= 0.0, let timer = timer {
timer.invalidate()
self.timer = nil
}
delegate?.myObject(self, updatedTimeLeft: timeLeft)
}
}
weak var delegate: MyObjectDelegate?
private var timer: Timer?
}
現在,您只需要在索引路徑的行單元格中分配對象: cell.myObject = myObjects[indexPath.row]
。
您的單元將執行以下操作:
var myObject: MyObject? {
didSet {
if oldValue.delegate == self {
oldValue.delegate = nil // detach from previous item
}
myObject.delegate = self
refreshUI()
}
}
func myObject(_ sender: MyObject, updatedTimeLeft timeLeft: TimeInterval) {
refreshUI()
}
我相信其余的應該很簡單...
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.