NOTE: Asking for answers in Swift please.
What I'm trying to do:
How I'm doing it currently:
I have a tableview with cells that contain a label.
When the cells are generated, they call a function that calculates the time between today and a stored target date, and it displays the countdown time remaining in the label.
To get the cells to update the label every second, I
use an NSTimer
that reloads the tableview every second.
PROBLEM: The countdown works, but when I try to do a Swipe-to-Delete action, because the NSTimer
reloads the tableview, it resets the
swiped cell so that it is no longer swiped and of course this is not acceptable for end users.
What I'm trying / Potential Solutions
I heard that a better way to do this is to use NSNotificationCenter
that is triggered by NSTimer
and add listeners to each cell. Every 1 second a "broadcast" is sent from the NSNotificationCenter
, the cell will receive the "broadcast" and will update the label.
I tried adding a listener to each cell in the part of the code where it generates a new cell:
// Generate the cells
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Tablecell", forIndexPath: indexPath) as UITableViewCell
let countdownEntry = fetchedResultController.objectAtIndexPath(indexPath) as CountdownEntry
// MARK: addObserver to "listen" for broadcasts
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateCountdownLabel", name: mySpecialNotificationKey, object: nil)
func updateCountdownLabel() {
println("broadcast received in cell")
if let actualLabelCountdown = labelCountdown {
// calculate time between stored date to today's date
var countdownValue = CountdownEngine.timeBetweenDatesRealTime(countdownEntry.date)
actualLabelCountdown.text = countdownValue
}
}
but the observer's selector only seems to be able to target functions at the view controller level, not in the cell. (I can't get "updateCountdownLabel" to fire from within the cell. When I create a function of the same name at view controller level, the function can be called)
Questions
Thanks in advance for your help!
It might be a solution not reloading cells at all. Just make the cells listen to an update notification and change their label accordingly.
I assume you subclass UITableViewCell
and give the cell a storedDate
property. You will set that property when preparing the cell.
The timer will just fire the notification.
Remember to unregister the cell from notification center in dealloc
Here is a quick an dirty example.
The View Controller containing your TableView:
class ViewController: UIViewController, UITableViewDataSource {
@IBOutlet weak var tableView: UITableView!
var timer: NSTimer!
//MARK: UI Updates
func fireCellsUpdate() {
let notification = NSNotification(name: "CustomCellUpdate", object: nil)
NSNotificationCenter.defaultCenter().postNotification(notification)
}
//MARK: UITableView Data Source
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "CustomCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! CustomTableViewCell
cell.timeInterval = 20
return cell
}
//MARK: View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.timer = NSTimer(timeInterval: 1.0, target: self, selector: Selector("fireCellsUpdate"), userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(self.timer, forMode: NSRunLoopCommonModes)
}
deinit {
self.timer?.invalidate()
self.timer = nil
}
}
The custom cell subclass:
class CustomTableViewCell: UITableViewCell {
@IBOutlet weak var label: UILabel!
var timeInterval: NSTimeInterval = 0 {
didSet {
self.label.text = "\(timeInterval)"
}
}
//MARK: UI Updates
func updateUI() {
if self.timeInterval > 0 {
--self.timeInterval
}
}
//MARK: Lifecycle
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: Selector("updateUI"), name: "CustomCellUpdate", object: nil)
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
}
I'm pretty sure this example doesn't adhere to your app's logic. Just showing how things are glowed together.
As Ian MacDonald has suggested you should avoid reloading the cell when the timer ticks, if you are swiping. Also drop the NSNotification, as It and timer are essentially doing the samething
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With