I don't necessarily need code, but I would appreciate pretty specific steps/logic on how to handle my problem.
My initial view controller is a table view controller. It will show the individual timer value and display its counting down in the current cell. The timer value is retrieved from a separate regular view controller. This view controller contains a UIPicker and the timer value is calculated using the user-selected values from the picker.
I am having trouble with the logic for handling multiple timers in the tableviewcontroller. Each newly created cell should be assigned a new timer object that is created with the creation of that cell. This way, a specific cell uses the time value created with a specific UIPicker value. So far, I can only get it to where each cell uses the same timer value as the first cell that is created.
How do I tell a newly created cell located in my initial table view controller that it should only use the value created with its specific timer value selected by the user with the UIPicker located in the second view controller? How do I separate the timer values between cells?
You should never have a table view cell with logical implementations, so at the most, the interface of the cell should have a method called setTime, and you give it a String which it just assigns it to the label.
Now that this is cleared up, who should be doing the logic? I believe you can abstract the timer logic into their own objects, because it would be too much of a hassle for the table view controller to keep track of all the play/pause states, how much time has passed etc.
So the work that's left for table view controller is just to get the data (remaining time) from the timer objects, transform them into Strings (you might have formatting logic here that VCs should handle) then pass those strings to the cell.
The code would look like this:
var timerObject = timerObjectArray[rowNumber]
var timeRemaining: Date = timerObject.getTimeRemaining()
var timeString: String = convertToString(timeRemaining)
cell.setTimeString(timeString)
So now we try to figure out when/how to get the cells.
As for when, that would be your choice, I think a NSTimer.scheduledTimerWithInterval could work (be careful of retain cycle)
Now in the method you pass to the schedule to be run every cycle, you should have code like this:
self.tableView.beginUpdates()
var visibleCells = self.tableView.visibleCells
for cell in visibleCells {
var indexPath = self.tableView.indexPath(for: cell)
var rowNumber = indexPath.row
var timeRemainString = getTimeRemainingStringFor(row: rowNumber)
cell.setTimeString(timeRemainString)
}
self.tableView.endUpdates()
Hope this helped! I probably missed something so just ask
I think your problem is maintaining state for each row data. Since table view reuses the cell, it becomes a problem.
Save the state of the timers in an array in parent view controller.
Set that value in every call of cellForRowAtIndex.
Please use below instead of NSTimer.
Solution : 1
int64_t delayInSeconds = gameInterval; // Your Game Interval as mentioned above by you
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// Update your label here.
});
Solution : 2
NSTimer.scheduledTimerWithTimeInterval(1,
target: NSBlockOperation(block: {...}),
selector: #selector(NSOperation.main),
userInfo: nil,
repeats: true)
Related
Over the last couple of days I have been trying to figure out how I can update a tableViewCell from TableView controller and it's giving me hard time. Here is where I have reached thus far:
Creating a timer
I set the time for the reminder in editVC(which you accessed by tapping on the tableViewCell). There you can set the time and the reminder is made. Every task has sSelectedDate which is the date set by the user.
This is what I think I should do next: Send this sSelectedDate to the firstViewController(where the tableView resides) and use this date to create a timer in the firstVC.
Question: What is the correct way of sending the particular date from the task to firstVC?
timesUpTimer = Timer(fireAt: selectedDate, interval: 0, target: self, selector: #selector(updateTimeLabel), userInfo: ["taskID": EditNotes], repeats: false)
UserInfo Dilemma
The "taskID" I am sending can be a uniqueTaskID I can generate with each task when a reminder is created. EditNotes is the class of NSManagedObject for CoreData.
Question: What's the correct way of sending the uniqueTaskID to the firstViewController?
Selector Method
#objc func updateTimeLabel(_ timer: Timer)
{
let userInfo = timer.userInfo as Dictionary<String, AnyObject>
var task = userinfo["taskID"] as EditNotes
task.belliconcolor = .white
// do stuff with task object
}
Since I am changing the coreData here I hope FetchedResultsController method is triggered and the tableView is updated.
This is the method invoked when a timer is up.
Question: Should I only rely on FRC or should I actually update the corresponding row in tableview? If yes then how?
Should I create Array for timers
This timer I create in the firstVC is for just one task? Do I have to create an array of timers and save it in coreData also?
Question: How to do it?
TLDR:
How to create timers/array of timers for every element/task of the tableview that when invoked updates the corresponding row in the tableview?
I am trying to implement a table view design where a user can click a button outside of a table view cell and the display mode of all the buttons should change. However this is not the 'selected' mode for a given cell (that will be yet a third state that becomes accessible via switching to this second state). What's the proper way to accomplish this?
I am using dequeueReusableCellWith so I don't want to simply cycle through every cell because some that are out of sight probably shouldn't be modified. I simply want any cell that is visible, or becomes visible, while the table view cell is in this second display mode to follow a second design rather than the first design.
The second design, for now, is being modified via a method I added to a subclass of UITableViewCell like so:
- (void) p_refreshDisplay {
if (self.editing) {
self.buttonToClearWidth.constant = 20;
self.buttonToClearLeadingWidth.constant = 20;
} else {
self.buttonToClearWidth.constant = 0;
self.buttonToClearLeadingWidth.constant = 0;
}
}
However, I'm not sure how to trigger this p_refreshDisplay for every visible (and to become visible) cell. It seems unwise to call this many times and refresh the table view. What would be the proper way to accomplish what I want to do?
You do what should be done for any table view change:
Update your data model or some flag as needed.
Either call reloadData on the table view or call reloadRowsAtIndexPaths:withRowAnimation: passing in indexPathsForVisibleRows as the list of rows to reload.
Implement cellForRowAtIndexPath to provide appropriate cells for the given data/flags.
It sounds like you should have a custom cell class that has one or more properties that can be set on the cell in cellForRowAtIndexPath so the cell can render itself properly based on the specified state.
You can achieve this by doing three things:
Establish some property that indicates the "mode" of the table, either a boolean or perhaps an enum if there are more than three states
Ensure that cellForRowAtIndexPath configures the cell appropriately based on the value of this property. This will ensure that newly displayed cells are configured correctly.
When the "mode" changes you can use the tableview's visibleCells property to update any currently visible cells:
for cell in tableview.visibleCells {
if let myCell = cell as? MyCustomCellClass {
myCell.setButtonStyle()
}
}
I'm writing this app that has a table view, showing data about stock market. The app uses SignalR (this lib) for updating the data in real-time. Each of the table view cells have 10 labels representing some information about the respective instrument.
As I said, the app works in real time and sometimes gets as much as 20 updates per second which need to appear on UI. Each of the SignalR notifications contain a string that after parsing it I know which row, and which labels on that row(not all of them are changed every time) need to be updated.
The question is: which of the following ways is better performance wise?
updating the model and then calling
self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
getting a reference to that specific row and updating the labels with changed values:
let indexPath = NSIndexPath(forRow: i, inSection: 0)
let cell = self.tableView.cellForRowAtIndexPath(indexPath)!
if self.watch[i]["bestBidQuantity"].string != list[3] {
let bestBidQuantityLabel = cell.viewWithTag(7) as! UILabel
bestBidQuantityLabel.text = StringManipulation.localizeDecimalNumber(Int64(list[3])!)
}
one important thing to note is that the cell in question may not be visible at the time of updating. As far as I know calling reloadRowsAtIndexPaths updates the row only if it's visible, but I'm not sure about my second solution regarding out of the view cells.
I'm not sure why you're worried about updating cells that aren't on screen? If you're dequeueing a cell (as you should) in cellForRowAtIndexPath:, your cell will be dequeued and setup with the correct information (from your model) when it's needed.
When you get your SignalR notification, update your model from the notification. When the user scrolls, a cell will be dequeued and setup with the latest information from your model.
For cells that are already in view, I like the second option, but still update the model for when the cell goes off screen and needs to be set up again.
Have you also not created a UITableViewCell subclass? I recommend using a subclass with IBOutlets instead of viewWithTag. You can then include a function in your cell subclass to update it's UI components. Something like this -
class StockCell: UITableViewCell
{
#IBOutlet weak var bestBidQuantityLabel: UILabel?
func update(notification: SignalIR) {
bestBidQuantityLabel?.text = notification.bestBidQuantity
}
}
When you get a new notification you could do something like this:
updateModel(notification)
if let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: ..., inSection: ...)) as? StockCell {
cell.update(notification)
}
You can also reuse the update(...) function to setup cells from cellForRowAtIndexPath:
I have multiple views each associated with its own timer. However, only one view is running a time each time. I am accessing each view in a for ... in loop and if the previous timer in the previous view has stopped, which I invalidate in the selector method, I want to fire the time for the next view.
the code looks like this:
var timer = NSTimer()
let timeInterval:NSTimeInterval = 1
var views = [TimerView]()
var timeCountDown: NSTimeInterval = 0
override func viewDidLoad() {
//viewDidLoad create the necessary UIView and fill the views array then pass them to a call to startTimer:
startTimer(views)
}
//startTimer function create the countdown timer
func startTimer(timerViews: [TimerView]){
for timerView in timerViews {
if !timer.valid { // a view with its associated timer start only when the previous timer has run its course and is invalidated
//creating a timer to associate with the current view
timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval,
target: self,
selector: "timerDidEnd:",
userInfo: timerView,
repeats: true)
}
}
}
//Callback function
func timerDidEnd(timer: NSTimer){
let timerView = timer.userInfo as! TimerView
timeCountDown = timeCountDown - timeInterval
timerView.timeLabel.text = timeToString(timeCountDown)
timerView.curValue = CGFloat(timeCountDown)
if timeCount <= 0.0 {
resetTimeCount()
}
}
func timeToString(time:NSTimeInterval) -> String {
let minutes = Int(time) / 60
let seconds = time - Double(minutes) * 60
return String(format:"%02i:%02i",minutes,Int(seconds))
}
func resetTimeCount(){
timer.invalidate()
}
With some print debugging for three views I am getting this output: "running the for loop", "calling view #1 by for loop", "calling the timer", "calling view #2 by for loop", "calling view #3 by for loop","countdown starting" ... That is the countdown only start after the for loop has terminated.
the problem I have is that the first timer run while the for loop calls all the views in my view array. How do I get the for loop to wait for the timer to be invalidated before it iterates to the next view?
What I am trying to achieve is this: the first view starts a countdown; when that countdown finished a second appears and run a countdown as well. Then the third view with a new countdown and so on until the last view. But it looks like the for loop in my code finish looping before the first timer associated with the first view in the collection actually starts
thanks
edit: I am wondering if the timer is not running on a different thread than the for loop?
Your code has a logic problem. It looks like you have a single instance variable, timer. Your for loop starts, and on the first pass, presumably timer is not valid. So you overwrite it with a new timer that IS valid. Now on the second pass through the array of timerViews, you check the same shared timer instance variable, but this time (And all subsequent times) timer is valid, so the body of the if statement doesn't fire.
If you really want a separate timer for each view then you will need to store all those timers. One way would be to have an array of timers that goes along with your array timerViews. Or you could create an array of tuples where each element contains a view and it's associated timer.
Stepping back from your problem, though, why do you want a separate timer for each field? Why not have a single timer, and each time it fires, loop through the array of fields and decide what you need to do with each one? You could have an array of structs that contains references to your views plus status information that lets your loop decide what to do with each one.
I would like to do something similar what we do in ASP.NET where we parse through all the rows in a GridView and assign a particular value to a particular cell in a row which has a matching TaskId as the current Id.
This has to happen in a Tick function of a Dispatcher Timer object. Since I have a Start Timer button Column for every row in a GridView. Upon a particular row's Start Timer Button click, I have to start its timer and display in a cell in that row. Similarly there can be multiple timers running in parallel.
For this I need to be able to check the task Id of the particular task and keep updating the cell values with the updated time in all of the tasks that have a Timer Started.
TimeSpan TimeRemaining = somevalue;
string CurrentTaskId = "100";
foreach(GridViewRow row in RadGridView1.Rows) // Here I tried RadGridView1.ChildrenOfType() as well but it has null
{
if( (row.DataContext as Task).TaskId == CurrentTaskId )
row.Cells[2].Content = a.TaskTimeRemaining.ToString();
}
Can someone please let me know how do I get this functionality using the Telerik RadGridView?
Cheers,
Syed.
this works for me - using Telrik WinControls Q2 2009. I have this in the tick event of a timer.
foreach (GridViewRowInfo row in this.radGridView1.MasterGridViewTemplate.Rows)
{
if (row.Cells["colId"].Value.ToString() == CurrentTaskId)
{
row.Cells["colTimerValue"].Value = a.TaskTimeRemaining.ToString();
}
}