I have a custom TableViewCell.
I want the timer Label in each cell to decrease every second. I have referred to this link. The only problem is I am unable to change the Label's text. If I print it in the console, the value is coming fine. How can I reload the TableView after changing cell's value from custom cell class?
I am new to custom TableView cells so please help.
I have referred to this link too.
class CustomCell: UITableViewCell {
#IBOutlet weak var timerLabel: UILabel!
var timer = Timer()
func updateRow(){
print("started timer")
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(CustomCell.decreaseTimer), userInfo: nil, repeats: true)
}
func decreaseTimer(){
let cell = self
let timerVal = cell.timerLabel.text
print("decrease "+timerVal!)
//how to reflect this timerVal value in the cell?
}}
You need to assign update time value to label .
func decreaseTimer(){
let cell = self
let timerVal = cell.timerLabel.text
print("decrease "+timerVal!)
//how to reflect this timerVal value in the cell?
cell.timerLabel.text = timerVal
}}
Related
I have a table view containing a dynamic number of UITableViewCells. The data that populates the cell originates from Firebase. Each record (and therefore each cell) has a field for recordTimestamp which is the Unix timestamp for when that record was added to Firebase.
I'm trying to find a way/best practice for making a "counter" in each cell that updates every second to show how many minutes/seconds it has been since recordTimestamp. The counter should continue to increment every second until a button in the cell is tapped.
I've tried using a timer object to call a function once per second which compares recordTimestamp against the current time. Then, on button tap it'd fire timer.invalidate. This method kind of worked, but after about 20 seconds of running the counters were getting all out of sync and some were a few seconds behind. It was also incredibly laggy - there are only about 10 rows in the table and you could barely scroll smoothly.
Any suggestions for achieving this?
EDIT: Code as requested. For now, I'm just trying to get it to count from 0 properly. Once I get that sorted I'll add the logic to first calculate the time difference and then increment every second.
class newRequestCell: UITableViewCell {
#IBOutlet weak var acceptButton: UIButton!
#IBOutlet weak var timeSinceRequest: UILabel!
var timer = Timer()
var counter = 0
func setRequestCell(customerRequest: customerRequest) {
customerName.text = String(customerRequest.customerName)
orderNumber.text = String(customerRequest.orderNumber)
timer = Timer.scheduledTimer(timeInterval: 1, target:self, selector: #selector(self.updateCounter), userInfo: nil, repeats: true)
}
#objc func updateCounter() {
counter += 1
timeSinceRequest.text = String(counter)
}
#IBAction func acceptButtonTapped(_ sender: Any) {
//TODO: stop timer, make call to Firebase updating record status
}
}
Here's an example of the inconsistent counting. These two cells were loaded in at the same time, yet after about 6 minutes the second timer is behind by about 2 minutes.
Many thanks to #DanielStorm and #Paulw11 for the insights which led to this answer.
All logic for the timer has been moved out of the cell and into the primary ViewController.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let customerRequest = activeRequestsArray[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "newRequestCell") as! newRequestCell
cell.setRequestCell(customerRequest: customerRequest)
let requestTime = NSDate(timeIntervalSince1970: TimeInterval(activeRequestsArray[indexPath.row].requestTimestamp/1000))
let calendar = Calendar.current
let timeComponents = calendar.dateComponents([.hour, .minute, .second], from: requestTime as Date)
let nowComponents = calendar.dateComponents([.hour, .minute, .second], from: Date())
let difference = calendar.dateComponents([.second], from: timeComponents, to: nowComponents).second!
cell.timeSinceRequest.text = String(difference)
cell.buttonPressed = {
print(indexPath.row)
//TODO: delete cell on button tap
}
return cell
}
So I have a UITableView and on top cell(1st cell) I have a timer on the cell.detailText. The timer displays but the tableview can’t scroll past the first cell because the cell text label is constantly being updated by this timer. What can I do to make the table view scroll properly without stopping the timer when the user scrolls?
Please help
Thanks in advance.
You don't need to keep calling reloadData(). You can do all of your timer work in the cell itself - you just need to remember to invalidate timers when you are done with them. I maintain an app that does something similar and use the following:
#IBOutlet weak var timeLeftLbl: UILabel!
var secondTimer: Timer!
var secondsLeft: Int = 0
func configureCell(item: PreviousAuctionItem) {
//Timers
secondsLeft = getSecondsLeft(endTime: item.endDate)
timeLeftLbl.text = secondsLeft
secondTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.decreaseTimeLeft), userInfo: nil, repeats: true)
}
#objc func decreaseTimeLeft() {
if secondsLeft > 0 {
secondsLeft -= 1
timeLeftLbl.text = secondsLeft.formatTimeAsString()
} else {
timeLeftLbl.text = ""
secondTimer?.invalidate()
}
}
override func prepareForReuse() {
secondTimer?.invalidate()
}
The getSeconds method is an API method I use to get how long an item has left.
Hope this helps!
I am trying to have a button change title and color every second but there is lag. What happens is that when it changes from lets say YELLOW to RED the button will show R.. or simply ... for a split second. It does not happen every time the button changes, only a few times. This the the code I have.
#IBOutlet var tapButton: UIButton!
var color:[UIColor] = [UIColor.redColor(), UIColor.blueColor(), UIColor.yellowColor(), UIColor.greenColor()]
var colorName:[String] = ["RED", "BLUE", "YELLOW", "GREEN"]
func game(){
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(SecondViewController.subtractTime), userInfo: nil, repeats: true)
}
func subtractTime(){
seconds -= 1
let num1 = Int(arc4random_uniform(4))
let num2 = Int(arc4random_uniform(4))
tapButton.titleLabel?.textColor = color[num1]
tapButton.setTitle(colorName[num2], forState: UIControlState.Normal)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
game()
}
I have changed the segues to Show Details, this seems to have fixed my problem. I also have the timers stop when I change view controllers.
I no longer get the lag that I was experiencing before.
I have a UITableView in which I have numerous timers set on each UITableViewCell. Each timer starts from when a user creates a "post" on my application and should expire within 24 hours. However, I want it so that when all 24 hours is over, the UITableViewCell deletes itself in real time but I can't seem to figure out where or when I should be deleting the timer. I have a method that will constantly refresh the timer every second using NSTimer.scheduledTimerWithTimeInterval and it updates the timers on each UITableViewCell every second. However, I can't find a method or find how I can find if each timer inside each UITableViewCell is finished. Obviously I can find if the timer is finished in viewDidLoad but that is only called right when the view becomes active. Is there any method I am missing or anything I can use to find if a timer via the scheduledTimerWithTimeInterval method is finished, and if it is, to delete it? Here is my code below:
//I have a self.offers array declared in the beginning of my class whcih will act as the UITableView data source.
var offers = [Offer]()
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//Dequeue a "reusable" cell
let cell = tableView.dequeueReusableCellWithIdentifier(offerCellIdentifier) as! OfferCell
setCellContents(cell, indexPath: indexPath)
return cell
}
func setCellContents(cell:OfferCell, indexPath: NSIndexPath!){
let item = self.offers[indexPath.row]
cell.offerName.text = item.offerName()
cell.offerPoster.text = item.offerPoster()
var expirDate: NSTimeInterval = item.dateExpired()!.doubleValue
//Get current time and subtract it by the predicted expiration date of the cell. Subtract them to get the countdown timer.
var timeUntilEnd = expirDate - NSDate().timeIntervalSince1970
if timeUntilEnd <= 0 {
//Here is where I want to delete the countdown timer but it gets difficult to do so when you are also inserting UITableViewCells and deleting them at the same time.
self.offers.removeAtIndex(indexPath!.row)
self.offersReference = Firebase(url:"<Database Link>")
self.offersReference.removeValue()
self.tableView.reloadData()
cell.timeLeft.text = "Finished."
}
else{
//Display the time left
var seconds = timeUntilEnd % 60
var minutes = (timeUntilEnd / 60) % 60
var hours = timeUntilEnd / 3600
cell.timeLeft.text = NSString(format: "%dh %dm %ds", Int(hours), Int(minutes), Int(seconds)) as String
}
}
override func viewDidLoad() {
super.viewDidLoad()
var timeExpired = false
//I set up my offers array above with completion handler
setupOffers { (result, offer) -> Void in
if(result == true){
//Insert each row one by one.
var currentCount = self.offers.count
var indexPaths: [NSIndexPath] = [NSIndexPath]()
indexPaths.append(NSIndexPath(forRow:currentCount, inSection: 0))
self.offers.append(offer)
currentCount++
self.tableView.reloadData()
}
}
// Do any additional setup after loading the view, typically from a nib.
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.rowHeight = 145.0
}
//Called when you click on the tab
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.refreshTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "refreshView:", userInfo: nil, repeats: true)
//Should fire while scrolling, so we need to add the timer manually:
//var currentRunLoop = NSRunLoop()
//currentRunLoop.addTimer(refreshTimer, forMode: NSRunLoopCommonModes)
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
refreshTimer.invalidate()
refreshTimer = nil
}
//Constantly refreshes the data in the offers array so that the time will continuously be updating every second on the screen.
func refreshView(timer: NSTimer){
self.tableView.reloadData()
}
You have a number of misconceptions, a few problems with your code, and your description isn't clear.
If you create a timer using scheduledTimerWithTimeInterval, you don't need to, and shouldn't, add it to the runloop. The scheduledTimerWithTimeInterval method does that for you.
If you create a repeating timer, it never "finishes." It keeps repeating forever. If instead you create a non-repeating timer, it fires once and then goes away. You don't need to delete it. Just don't keep a strong reference to it and it will be deallocated once it fires.
You say you create a timer for each table view cell, but the code you posted only creates a single timer for the view controller. You say "...it updates the timers on each UITableViewCell every second. However, I can't find a method or find how I can find if each timer inside each UITableViewCell is finished." That doesn't match the code you posted. Your code is running a timer once a second that simply tells the table view to reload it's data.
I guess you mean that you re-display a remaining time counter in each cell when your timer fires?
So, are you asking how to figure out if the time remaining for all of your cell's item.timeLeft() has reached zero? You haven't posted the code or the requirements for your timeLeft() function, so your readers can't tell what is supposed to be happening.
I'm trying to activate a function inside my custom cell by setting the value of a boolean inside the custom cell class. This is my best attempt at doing this:
func blurViewActive(gestureRecognizer:UIGestureRecognizer) {
if (gestureRecognizer.state == UIGestureRecognizerState.Began) {
println("STATE BEGAN")
var point = gestureRecognizer.locationInView(self.tv)
if let indexPath = self.tv.indexPathForRowAtPoint(point) {
let data = messageList[indexPath.row] as Messages
let mcell: TableViewCell = self.tv.dequeueReusableCellWithIdentifier("cell") as TableViewCell
mcell.read = true
}
}
}
but this doesn't work, and I really have no idea how to do this any other way.
Here is the code for my custom cell class:
class TableViewCell: UITableViewCell, UIGestureRecognizerDelegate {
#IBOutlet weak var labelOutl: UILabel!
var timer = NSTimer()
var counter = 10
var read = Bool()
#IBOutlet weak var dateLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
if read == true{
println("hello")
}
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func timerStarted(){
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "update", userInfo: nil, repeats: true)
}
func update(){
println(--counter)
}
}
My expected outcome is that once read has been set to "true" in my view controller, the function inside awakeFromNib-function should be executed instantaneously.
There seems to be a number of points of confusion here.
dequeueReusableCellWithIdentifier returns a cell for use in the table view it is called on. This will be either a newly instantiated cell or an existing cell not currently displayed in any of the table's visible rows. Setting the read property on this cell will therefore have no immediate visible effect.
If you want access to a visible cell you could use cellForRowAtIndexPath but even then changes made to that cell will not necessarily update the UI. Instead you probably want to update whatever model backs that cell and call reloadRowsAtIndexPaths to update a specific cell.
Additionally awakeFromNib will be called only when a new cell is created. That will be before it is returned from dequeueReusableCellWithIdentifier and therefore well before you can take any action on it, like setting its read property. It will also not be called once per row in your table or per displayed row since you are using a reuse identifier. Instead the table view will create at least one cell for each visible row and reuse them as row scroll into and out of sight. This is convenient because minimizing the number of objects created helps reduce memory use and reduces load which could slow down scrolling performance. However it means that your data source needs to be prepared to update these cells as they are reused from one row to the next.