Reputation: 3
I'm trying to create a countdown app that will show the number of Years....Seconds until an upcoming date. I want to be able to view several countdowns in a table view. I have some basic logic that works idependently of a tableview cell to create the countdown:
.
.
.
var timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "countdown", userInfo: nil, repeats: true)
func countdown() {
let hourMinuteComponents: NSCalendarUnit =
NSCalendarUnit.CalendarUnitYear |
NSCalendarUnit.CalendarUnitMonth |
NSCalendarUnit.CalendarUnitDay |
NSCalendarUnit.CalendarUnitHour |
NSCalendarUnit.CalendarUnitMinute |
NSCalendarUnit.CalendarUnitSecond
let timeDifference = userCalendar.components(
hourMinuteComponents,
fromDate: NSDate(),
toDate: date1.enteredDate as NSDate,
options: nil)
// Sample Output
println("Year: \(timeDifference.year)")
println("Month: \(timeDifference.month)")
println("Day: \(timeDifference.day)")
println("Hour: \(timeDifference.hour)")
println("Minute: \(timeDifference.minute)")
println("Second: \(timeDifference.second)")
}
This works well and the console shows the countdown. However, I can't get past an error when I try to call it from a cell.
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFTimer row]: unrecognized selector sent to instance
Here's the code that isn't working.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
println("Index Path: \(indexPath)")
let cell = tableView.dequeueReusableCellWithIdentifier("countDownCell") as! UITableViewCell
let cellIndex = indexPath
var timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "update:", userInfo: cellIndex, repeats: true)
//let secondlabel = cell.viewWithTag(1005) as! UILabel
//secondlabel.text = "Test"
return cell
}
func update(cellIndex: NSIndexPath) {
let hourMinuteComponents: NSCalendarUnit =
NSCalendarUnit.CalendarUnitYear |
NSCalendarUnit.CalendarUnitMonth |
NSCalendarUnit.CalendarUnitDay |
NSCalendarUnit.CalendarUnitHour |
NSCalendarUnit.CalendarUnitMinute |
NSCalendarUnit.CalendarUnitSecond
let timeDifference = userCalendar.components(
hourMinuteComponents,
fromDate: NSDate(),
toDate: date1.enteredDate as NSDate,
options: nil)
println("Year: \(timeDifference.year)")
println("Month: \(timeDifference.month)")
println("Day: \(timeDifference.day)")
println("Hour: \(timeDifference.hour)")
println("Minute: \(timeDifference.minute)")
println("Second: \(timeDifference.second)")
if let cell = tableView.cellForRowAtIndexPath(cellIndex) {
let yearlabel = cell.viewWithTag(1000) as! UILabel
let secondlabel = cell.viewWithTag(1005) as! UILabel!
}
secondlabel.text = "\(timeDifference.second)"
}
I appreciate any help you can provide. I initially focused my fixing attempts on changing how I'm calling update() from the timer. I tried passing the cell explicitly and then thought it better to try calling the indexPath of the cell. I also checked my syntax as was suggested in a number of NSTimer posts. I think I have it right (and believe I've tried pretty much all other options).
The code breaks at this line:
if let cell = tableView.cellForRowAtIndexPath(cellIndex) { ...
Upvotes: 0
Views: 1678
Reputation: 6987
I would recommend a different approach to how you've constructed your app.
UITableView
will "recycle" its cells using the dequeueReusableCellWithIdentifier
method. So, if a user were to create 100 countdowns with your app, but there are only ever a maximum of 4 cells visible on screen, when the user scrolls those four cells will be enqueued into table view's cache of cells and then dequeued by your data source code. This is done to give performance enhancements so the table view scrolls smoothly. You can read more about this behavior in Apple's Table View Programming Guide.
If it were me...
I'd start by having an object that can track the state of a countdown: countdown start date, countdown end date, countdown name, etc.
I'd use an array of those objects to be the data that is represented by the table view.
Since all the countdowns update every second, you could update what's currently displayed by asking the table view what index paths is it currently showing, match those index paths to the countdown objects in your array, and then changing the text of all the different labels. This means you would only have one NSTimer
instance in your view controller responsible for driving the updates.
There are plenty of other possible ways to structure this, but I think trying to manage many timer instances is going to cause some gray hairs.
Upvotes: 7
Reputation: 2636
More simplistic approach will be to keep running your nstimer az it isvand call the countdown method and reload your UITableView at the end. Keep the values to be display in an NSArray everytime before reloading and pass it as object to UITableView.
Upvotes: 0
Reputation: 8218
The selector passed to NSTimer
's scheduledTimerWithTimeInterval(_:target:selector:userInfo:repeats:)
receives a NSTimer
object, not a NSIndexPath
.
That is, your update(_:)
method should be defined as
func update(timer: NSTimer) {
cellIndex = timer.userInfo as! NSIndexPath
...
}
See the method's documentation for more information
Upvotes: 1