running timers in tableViewCells being overwritten when tableView scrolls - ios

I have tried asking this question several times but I haven't been able to explain what is going on. Maybe some screen shots may help. I can only post one because I don't have enough reputation points yet.
screenshot after tableview scroll
You can see that one of the timers (2) has been reset. I have tried to fix this multiple ways without success. Here is the code that puts the timers into the tableview cells:
-(void) calculateTimer:(NSTimer *)theTimer
{
self.timerItem = [theTimer userInfo];
// for date only cell
if(self.timerItem.timerType == 0){
[theTimer invalidate];
}
for (NRCItemCell *cell in [self.tableView visibleCells])
{
NSIndexPath *ip = [self.tableView indexPathForCell:cell];
NSUInteger row = [[[NRCItemStore sharedStore]allItems] indexOfObjectIdenticalTo:self.timerItem];
if (row == ip.row){
[self configureTimers:cell forRowAtIndexPath:ip];
cell.timer.text = self.timerItem.timerOutput;
cell.timerName.text = self.timerItem.timerName;
}
}
}
-(void)configureTimers:(NRCItemCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
NRCtimerItem *item = [[NRCItemStore sharedStore]allItems][indexPath.row];
NSInteger timerType = item.timerType;
// timerType set by TimerTypeTableView Controller as follows:
// 0 - date
// 1 - seconds elapsed
// 2 - minutes elapsed
// 3 - hours elapsed
// 4 - days elapsed
// 5 - months elapsed
// 6 - years elapsed
switch (timerType) {
case 0:{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[dateFormatter setTimeStyle:NSDateFormatterNoStyle];
NSDate *date = [NSDate date];
NSString *formattedDateString = [dateFormatter stringFromDate:date];
item.timerOutput = formattedDateString;
}
break;
case 1:
{
NSTimeInterval interval = [self.timerItem.startTime timeIntervalSinceNow];
interval = (-1 * interval);
int time = round(interval);
div_t h = div(time, 3600); //seconds total, divided by 3600 equals
int hours = h.quot; // hours, divided by 60 equals
div_t m = div(h.rem, 60); // minutes
int minutes = m.quot;
int seconds = m.rem; // and remainder is seconds
// NSLog(#"%d:%d:%d", hours, minutes, seconds);
//NSString *intervalString = [NSString stringWithFormat:#"%ld", (long)time];
NSString *intervalString = [NSString stringWithFormat:#"%d hours, %d minutes, %d seconds", hours, minutes, seconds];
NSString *outputString = [intervalString stringByAppendingString:#" ago"];
item.timerOutput = outputString;
}
break;
case 2:
{
NSTimeInterval interval = [self.timerItem.startTime timeIntervalSinceNow];
interval = (-1 * interval);
int time = roundf(interval);
div_t h = div(time, 3600); // seconds total, divided by 3600 equals
int hours = h.quot; // hours, divided by 60 equals
div_t m = div(h.rem, 60); // minutes
int minutes = m.quot;
NSString *intervalString = [NSString stringWithFormat:#"%d hours, %d minutes", hours, minutes];
NSString *outputString = [intervalString stringByAppendingString:#" ago"];
item.timerOutput = outputString;
}
break;
case 3:
{
NSTimeInterval interval = [self.timerItem.startTime timeIntervalSinceNow];
interval = (-1 * interval);
int time = roundf(interval);
div_t h = div(time, 3600); // seconds total, divided by 3600 equals
int hours = h.quot; // hours
NSString *intervalString = [NSString stringWithFormat:#"%d hours", hours];
NSString *outputString = [intervalString stringByAppendingString:#" ago"];
item.timerOutput = outputString;
}
break;
case 4:
{
NSTimeInterval interval = [self.timerItem.startTime timeIntervalSinceNow];
interval = (-1 * interval);
int time = roundf(interval);
div_t h = div(time, 3600); // seconds total, divided by 3600 equals
int hours = h.quot; // hours, divided by 24 equals
div_t d =div(h.rem, 24); // days
int days = d.quot;
NSString *intervalString = [NSString stringWithFormat:#"%d days, %d hours", days, hours];
NSString *outputString = [intervalString stringByAppendingString:#" ago"];
item.timerOutput = outputString;
}
break;
case 5:
{
NSTimeInterval interval = [self.timerItem.startTime timeIntervalSinceNow];
interval = (-1 * interval);
int time = roundf(interval);
div_t h = div(time, 3600); // seconds total, divided by 3600 equals
__unused int hours = h.quot; // hours, divided by 24 equals
div_t d =div(h.rem, 24); // days
int days = d.quot;
div_t y = div(d.rem, 12);// divided by 12 equals months
int months = y.quot;
NSString *intervalString = [NSString stringWithFormat:#"%d months, %d days", months, days];
NSString *outputString = [intervalString stringByAppendingString:#" ago"];
item.timerOutput = outputString;
}
break;
case 6:
{
NSTimeInterval interval = [self.timerItem.startTime timeIntervalSinceNow];
interval = (-1 * interval);
int time = roundf(interval);
div_t h = div(time, 3600); // seconds total, divided by 3600 equals
__unused int hours = h.quot; // hours, divided by 24 equals
div_t d =div(h.rem, 24); // days
int days = d.quot;
div_t y = div(d.rem, 365);// divided by 365 equals years
int years = y.quot;
NSString *intervalString = [NSString stringWithFormat:#"%d years, %d days", years, days];
NSString *outputString = [intervalString stringByAppendingString:#" ago"];
item.timerOutput = outputString;
}
break;
}
}
The key is the for(NRCItemCell *cell in [self.tableView visibleCells]) fast enumeration. The idea is to loop through the visible cells and only update a cell if the cell's indexPath is equal to the position of the timer in my datastore. However, it looks like scrolling the tableView causes a mismatch between indexPath and and position of the timer in the datastore so that the wrong cell gets overwritten. I have searched all over for an answer and have tried several different approaches but the solution depends on the subtitle label in my custom cell not being overwritten unless the cell position matches the datastore position (which is the way MVC should work, as I understand it). But using reusable cells and scrolling apparently doesn't work the way I thought it did. If there is a solution, I sure would like the help. Thanks in advance!

Your main issue here is the reuse of the cells. Every time you scroll the table the cells are reused with the data of other cells. To keep it short, store your timers data in an array and not in the actual cell.
Importent pointers:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
cell.timerView.data = nil; //reset the data in the current timer
myCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
if (updateCell)// Makes sure the cell is still visible
updateCell.timerView.data = timersArray[indexpath.row];
});
}

My bad. I found the bug in my code. It wasn't with tableView at all. I was storing the timers in an array, correctly, not in the cell, even though that's what the comments in the code said. The bug was that I was inadvertently firing a timer every pass through the tableView, so these timers would fire at unpredictable times and then my code would overlay the cell for the corresponding timer. A lot of debugging work, but good experience. Code is working now.
Thanks for the comments!

Related

Issue in finding time difference

In my app, I want to know the difference between:
the total hours worked in the week(54.30 hours)
and
the total hours worked on a specific day(10.45 hours)
For example, I want to know the value of (54.30 hours - 10.45 hours) in
hours. Since the total hours of week is greater than 24 hrs, I
couldn't convert it into NSDate.
Use this 2 functions,this will help you
The below function will return total seconds from the hour string
- (NSNumber *)secondsForTimeString:(NSString *)string {
NSArray *components = [string componentsSeparatedByString:#"."];
NSInteger hours = [[components objectAtIndex:0] integerValue];
NSInteger minutes = [[components objectAtIndex:1] integerValue];
//NSInteger seconds = [[components objectAtIndex:2] integerValue];
return [NSNumber numberWithInteger:(hours * 60 * 60) + (minutes * 60)];
}
And below function will return formatted time from seconds
- (NSString *)timeFormatted:(int)totalSeconds {
//int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
int hours = totalSeconds / 3600;
return [NSString stringWithFormat:#"%02d.%02d",hours, minutes];
}
Now you can calculate the remaining hours like this :
int diffrence =[[self secondsForTimeString:#"54.30"] intValue] - [[self secondsForTimeString:#"10.45"] intValue];
NSLog(#"Remaining Hours - %#",[self timeFormatted:diffrence]);

iOS display time as score and highscore on a UILabel

I'm having a problem when I'm trying to display the score as a highscore.
This is my code for getting the time ont the label on the correct format.
- (void)populateLabelwithTime:(int)milliseconds {
int seconds = milliseconds / 1000;
int minutes = seconds / 60;
int hours = minutes / 60;
seconds -= minutes * 60;
minutes -= hours * 60;
resultScoreLabel.text = [NSString stringWithFormat:#"%ds:%dms",seconds, milliseconds%1000];
if (currentTime > highScore) {
[[NSUserDefaults standardUserDefaults] setInteger:currentTime forKey:#"highscore"];
resultHighScoreLabel.text = [NSString stringWithFormat:#"%i",highScore +10];
}
else {
}
}
-(void)scoring {
currentTime += 10;
[self populateLabelwithTime:currentTime];
highScore = [[NSUserDefaults standardUserDefaults] integerForKey:#"highscore"];
}
The score format appears correct.
score is: 1s:186ms
but the highscore appears wrong
Your highscore is: 1186
know that this line is wrong
resultHighScoreLabel.text = [NSString stringWithFormat:#"%i",highScore +10];
but I cannot figure how to make it work
any suggestions??
This is the quickfix:
int newHighscore = highScore+10;
int seconds = ((int)floor((newHighscore)/1000));
int milliseconds = newHighscore%1000;
resultHighScoreLabel.text = [NSString stringWithFormat:#"%is:%ims",seconds,milliseconds];
However I would highly recommend to change the way you handle the highscore

Get NSDate to say "XX time ago" that article was published [duplicate]

This question already has answers here:
Is there some functionality in Cocoa to display time intervals in natural language?
(4 answers)
Closed 9 years ago.
I have a date an article was published, but need to get how long ago it was published in relation to the current time.
So if the Article was published at 8:45AM, and it is 9:45AM on the same day, I need to be able to have a UILabel that says "1 hr ago".
Currently, I am getting the date formatted to get a date like "May 5, 2013 5:35PM":
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Feed *feedLocal = [headlinesArray objectAtIndex:indexPath.row];
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setDateFormat:#"MMMM d, yyyy h:mma"];
NSString *dateString = [df stringFromDate:feedLocal.published];
cell.publishedLabel.text = dateString;
}
How could I convert that to get something like "1 hr ago"? Thanks!
EDIT
Here is the current method I have to at least get the time ago:
-(NSString *)timeAgo {
NSDate *todayDate = [NSDate date];
double ti = [self timeIntervalSinceDate:todayDate];
ti = ti * -1;
if (ti < 1) {
return #"1s";
} else if (ti < 60) {
return #"1m";
} else if (ti < 3600) {
int diff = round(ti / 60);
return [NSString stringWithFormat:#"%dm", diff];
} else if (ti < 86400) {
int diff = round(ti / 60 / 60);
return[NSString stringWithFormat:#"%dh", diff];
} else if (ti < 2629743) {
int diff = round(ti / 60 / 60 / 24);
return[NSString stringWithFormat:#"%dd", diff];
} else if (ti < 31556926) {
int diff = round(ti / 60 / 60 / 24 / 30);
return [NSString stringWithFormat:#"%dmo", diff];
} else {
int diff = round(ti / 60 / 60 / 24 / 30 / 12);
return [NSString stringWithFormat:#"%dy", diff];
}
}
Im not sure what timeAgo is a method of, but here is a solution assuming its in the same viewController as tableView:cellForRowAtIndexPath. If you can clarify what its a method of I may be able to modify this and help you more.
First change timeAgo to take in a date and do a comparison on it.
-(NSString *)timeSincePublished:(NSDate *)publicationDate
{
double ti = [publicationDate timeIntervalSinceNow];
Everything else should be the same in the above method.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Feed *feedLocal = [headlinesArray objectAtIndex:indexPath.row];
NSString *dateString = [self timeSincePublished:feedLocal.published];
cell.publishedLabel.text = dateString;
}

iOS Format String into minutes and seconds

I am receiving a string from the YouTube JSONC api, but the duration is coming as a full number i.e 2321 instead of 23:21 or 2 instead of 0:02. How would I go about fixing this?
JSON C
EDIT:
int duration = [videos valueForKey:#"duration"];
int minutes = duration / 60;
int seconds = duration % 60;
NSString *time = [NSString stringWithFormat:#"%d:%02d", minutes, seconds];
Assuming the duration value is really the duration in seconds, then you can calculate the number of minutes and seconds and then format those into a string.
int duration = ... // some duration from the JSON
int minutes = duration / 60;
int seconds = duration % 60;
NSString *time = [NSString stringWithFormat:#"%d:%02d", minutes, seconds];
You should use DateComponentsFormatter if the duration is intended to be user-facing:
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [ .minute, .second ]
formatter.zeroFormattingBehavior = [ .pad ]
let formattedDuration = formatter.string(from: duration)!
Try this very optimized
+ (NSString *)timeFormatConvertToSeconds:(NSString *)timeSecs
{
int totalSeconds=[timeSecs intValue];
int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
int hours = totalSeconds / 3600;
return [NSString stringWithFormat:#"%02d:%02d:%02d",hours, minutes, seconds];
}
int sec = diff;//INFO: time in seconds
int a_sec = 1;
int a_min = a_sec * 60;
int an_hour = a_min * 60;
int a_day = an_hour * 24;
int a_month = a_day * 30;
int a_year = a_day * 365;
NSString *text = #"";
if (sec >= a_year)
{
int years = floor(sec / a_year);
text = [NSString stringWithFormat:#"%d year%# ", years, years > 0 ? #"s" : #""];
sec = sec - (years * a_year);
}
if (sec >= a_month)
{
int months = floor(sec / a_month);
text = [NSString stringWithFormat:#"%#%d month%# ", text, months, months > 0 ? #"s" : #""];
sec = sec - (months * a_month);
}
if (sec >= a_day)
{
int days = floor(sec / a_day);
text = [NSString stringWithFormat:#"%#%d day%# ", text, days, days > 0 ? #"s" : #""];
sec = sec - (days * a_day);
}
if (sec >= an_hour)
{
int hours = floor(sec / an_hour);
text = [NSString stringWithFormat:#"%#%d hour%# ", text, hours, hours > 0 ? #"s" : #""];
sec = sec - (hours * an_hour);
}
if (sec >= a_min)
{
int minutes = floor(sec / a_min);
text = [NSString stringWithFormat:#"%#%d minute%# ", text, minutes, minutes > 0 ? #"s" : #""];
sec = sec - (minutes * a_min);
}
if (sec >= a_sec)
{
int seconds = floor(sec / a_sec);
text = [NSString stringWithFormat:#"%#%d second%#", text, seconds, seconds > 0 ? #"s" : #""];
}
NSLog(#"<%#>", text);
Here is the great code I finds for this
int duration = 1221;
int minutes = floor(duration/60)
int seconds = round(duration - (minutes * 60))
NSString * timeStr = [NSString stringWithFormat:#"%i:%i",minutes,seconds];
NSLog(#"Dilip timeStr : %#",timeStr);
And the output will belike this
Dilip timeStr : 20:21
You can subString the 2321 and get the first string as 23 and the second as 21 and convert them to int. Also check for the length of the text:
if (text.length < 4)
//add zeros on the left of String until be of length 4
Objective C:
NSDateComponentsFormatter * formatter = [[NSDateComponentsFormatter alloc]init];
[formatter setUnitsStyle:NSDateComponentsFormatterUnitsStyleShort];
[formatter setAllowedUnits:NSCalendarUnitSecond | NSCalendarUnitMinute];
[formatter setZeroFormattingBehavior:NSDateComponentsFormatterZeroFormattingBehaviorPad];
return [formatter stringFromTimeInterval:duration];

How to limit float Value in iOS

Now i am trying to get the length of Songs in iOS.
- (NSString *)returnofTotalLength
{
float duration = [[self.player.nowPlayingItem valueForProperty:MPMediaItemPropertyPlaybackDuration] floatValue]/60.0f;
NSString *length = [NSString stringWithFormat:#"%.2f",duration];;
NSString *totalLength = [length stringByReplacingOccurrencesOfString:#"." withString:#":"];
return totalLength;
}
above codes is the total length of song that show like 5:90.
You know that 5:90 can't be true because 60 seconds is 1 minute.
It's should be 6:30.
So i want to limit that value for 1 minute (60 seconds).
How can i do it Please help me?
Thanks.
This is simple math. Pseudocode:
minutes = (int)(seconds / 60);
rest = seconds % 60;
result = minutes:rest
Objc:
int seconds = 150;
int minutes = (int)(seconds / 60);
int rest = seconds % 60;
return [NSString stringWithFormat:#"%i:%i", minutes, rest];
do following :
min=(int)duration/60;
sec=duration%60;
than append minutes and second
If your time crosses to hours then you can go for this :
NSInteger seconds = duration % 60;
NSInteger minutes = (duration / 60) % 60;
NSInteger hours = duration / (60 * 60);
NSString *result = nil;
result = [NSString stringWithFormat:#"%02ld:%02ld:%02ld", hours, minutes, seconds];
I just got answer for my own question.
Thanks for other answers. :)
NSTimeInterval currentProgress = [[self.player.nowPlayingItem valueForProperty:MPMediaItemPropertyPlaybackDuration] floatValue];
float min = floor(currentProgress/60);
float sec = round(currentProgress - min * 60);
NSString *time = [NSString stringWithFormat:#"%02d:%02d", (int)min, (int)sec];
return time;
That will return NSString with Complete Format.

Resources