Load UITableViewCell, one at a time - ios

I am loading data into my UITableViewCell one at a time in the following method
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
_anIterationCounter++;
if (_anIterationCounter==_currentCount) {
if ((_currentCount+1)<=[_allTrains count]&&(_currentCount+1)<=5) {
_currentCount++;
_anIterationCounter=0;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(queue, ^{
[tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES]; //call apis here
});
}
}
the "_iterationCount" variable is initialised to be 0 in the beginning. Data is loaded from the array [_allTrains objectAtIndex:_currentCount] and I only want to show 5 cells atmost.
But my program has a further step where a didSelectRowAtIndexPath method exists.
But the control doesn't go into the delegate function until all the cells are done loading. How will I resolve this issue?

what about doing it like so?
#import "TableViewController.h"
#interface TableViewController ()
#property (strong, nonatomic) NSArray *regularData;
#property (strong, nonatomic) NSMutableArray *lazyLoadedData;
#property (strong, nonatomic) NSTimer *insertTimer;
#end
#implementation TableViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.tableFooterView = [[UIView alloc] init];
self.regularData = #[#"value1",
#"value2",
#"value3",
#"value4",
#"value5",
#"value6",
#"value7",
#"value8",
#"value9"];
self.lazyLoadedData = [#[] mutableCopy];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.insertTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(insertRow) userInfo:nil repeats:YES];
}
- (void)insertRow {
if (self.lazyLoadedData.count == 5) {
// maximum reached
[self.insertTimer invalidate];
self.insertTimer = nil;
return;
}
[self.lazyLoadedData addObject:self.regularData[self.lazyLoadedData.count]];
[self.tableView insertRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:self.lazyLoadedData.count - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.lazyLoadedData.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
cell.textLabel.text = self.lazyLoadedData[indexPath.row];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"%s", __PRETTY_FUNCTION__);
}
#end
or take a look at my demo project:
https://www.dropbox.com/sh/oe1t4u8zksalmdy/AAAJO7G32YUhXfw7ixBcIQXla?dl=0
hope i got you right. :)

Rather than reloading the table each time, use the tableView's insertAtIndexPaths: method to insert cells one by one.

Change the
[tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
to
[tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
Basically I set waitUntilDone to NO.

Use UISCrollView and create cell object add into the scrollview then load all the cells one at a time.

Related

Refresh main view after choosing calendar and hitting back button

I followed this tutorial from AppCoda and I noticed that when I create another calendar, choose it (indicated by the checkmark beside it), then hit the Back button, the events in the main UIViewController view are not refreshed. I already added this code in my ViewController.m but nothing new happened:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (self.tblEvents == nil) {
NSLog(#"Your TableView becomes nil");
return;
}
[self.tblEvents reloadData];
}
Any ideas? Let me know if you need more information.
Edit:
.m
#import "MainViewController.h"
#import "AppDelegate.h"
#interface MainViewController ()
#property (nonatomic, strong) AppDelegate *appDelegate;
#property (nonatomic, strong) NSArray *arrEvents;
- (void)requestAccessToEvents;
- (void)loadEvents;
#end
#implementation MainViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
self.tblEvents.delegate = self;
self.tblEvents.dataSource = self;
[self performSelector:#selector(requestAccessToEvents) withObject:nil afterDelay:0.4];
[self performSelector:#selector(loadEvents) withObject:nil afterDelay:0.5];
[self.tblEvents reloadData];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.tblEvents reloadData];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"idSegueEvent"]) {
EventViewController *eventViewController = [segue destinationViewController];
eventViewController.delegate = self;
}
}
#pragma mark - UITableView Delegate and Datasource method implementation
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"%lu", (unsigned long)self.arrEvents.count);
return self.arrEvents.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"idCellEvent"];
// Get each single event.
EKEvent *event = [self.arrEvents objectAtIndex:indexPath.row];
// Set its title to the cell's text label.
cell.textLabel.text = event.title;
// Get the event start date as a string value.
NSString *startDateString = [self.appDelegate.eventManager getStringFromDate:event.startDate];
// Get the event end date as a string value.
NSString *endDateString = [self.appDelegate.eventManager getStringFromDate:event.endDate];
// Add the start and end date strings to the detail text label.
cell.detailTextLabel.text = [NSString stringWithFormat:#"%# - %#", startDateString, endDateString];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 60.0;
}
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
// Keep the identifier of the event that's about to be edited.
self.appDelegate.eventManager.selectedEventIdentifier = [[self.arrEvents objectAtIndex:indexPath.row] eventIdentifier];
// Perform the segue.
[self performSegueWithIdentifier:#"idSegueEvent" sender:self];
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the selected event.
[self.appDelegate.eventManager deleteEventWithIdentifier:[[self.arrEvents objectAtIndex:indexPath.row] eventIdentifier]];
// Reload all events and the table view.
[self loadEvents];
}
}
#pragma mark - EEventViewControllerDelegate method implementation
- (void)eventWasSuccessfullySaved
{
// Reload all events.
[self loadEvents];
}
#pragma mark - IBAction method implementation
- (IBAction)showCalendars:(id)sender
{
if (self.appDelegate.eventManager.eventsAccessGranted) {
[self performSegueWithIdentifier:#"idSegueCalendars" sender:self];
}
}
- (IBAction)createEvent:(id)sender
{
if (self.appDelegate.eventManager.eventsAccessGranted) {
[self performSegueWithIdentifier:#"idSegueEvent" sender:self];
}
}
#pragma mark - Private method implementation
- (void)requestAccessToEvents
{
[self.appDelegate.eventManager.eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
if (error == nil) {
// Store the returned granted value.
self.appDelegate.eventManager.eventsAccessGranted = granted;
} else {
// In case of error, just log its description to the debugger.
NSLog(#"%#", [error localizedDescription]);
}
}];
}
- (void)loadEvents
{
if (self.appDelegate.eventManager.eventsAccessGranted) {
self.arrEvents = [self.appDelegate.eventManager getEventsOfSelectedCalendar];
[self.tblEvents reloadData];
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
To make UITableView work you need to set the delegate and datasource object like this..
in you ViewController.m file try to add Delegate and DataSource like this.
#interface ViewController ()<UITableViewDelegate, UITableViewDataSource>
#end
now add these two lines in your view did load.
self.tblEvents.delegate = self;
self.tblEvents.dataSource = self;
And check the implimentation of you Data Source methods like this
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
//This method should return the number of rows you want to create in your tableView
return yourArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"yourCellIdentifier"];
//Now show what you want to show in your each cell? For Example you just want to show a simple text which is stored in you array.
cell.textLabel.text = [yourArray objectAtIndex:indexPath.row];
//indexPath.row is the numeric index number of each cell. This method will automatically execute exact the same number of time you return in above method.
return cell;
}
Now When your class/View Controller is open you might have zero data in your array and after some manipulation you got some data in your array Either by Call Web-Services/Loading from local Database/ by Passing Reference of array to next ViewController and on coming back to screen you want to refresh your TableView so now calling [tblEvents reloadData] will restart the process from numberOfRowsInSection method to cellForRowAtIndexPath method

update UITableViewCell from other method

i have search thousand in GG to find solution update data to UITableViewCell but all show me the solution is
UITableViewCell *cell=(UITableViewCell*)[self.tableView cellForRowAtIndexPath:indexPath];
but the cell is nil for all cells that are visible. I have use NSNotification to send data from one method to ViewController.m , and the Reiever method i want update data to cell by indexPath. but all cell is nil and cannt not update that.
here my code
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
#property(nonatomic, strong) IBOutlet UITableView *tableView;
#end
ViewController.m
#implementation ViewController
{
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(theReciever:) name:#"theSender" object:nil];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [recipes count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableCell";
UITableViewCell* cell = [self.tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
NSLog(#"cell nil");
}
NSString *idgame=#"Gamexyz";
cell.textLabel.text = idgame;
cell.tag=indexPath.row;
return cell;
}
-(void)theReciever:(NSNotification*)notif{
if([notif.object isKindOfClass:[packeData class]]){
packeData *data=[notif object];
NSString *key=data.key;
NSInteger *index=[key integerValue];
NSIndexPath *indexPath=[NSIndexPath indexPathWithIndex:index];
UITableViewCell *cell=(UITableViewCell*)[self.tableView cellForRowAtIndexPath:indexPath];
//UITableViewCell *cell=(UITableViewCell*)[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:7 inSection:0]];
if(cell==nil)
{
NSLog(#"cell NULL");
}else{
cell.textLabel.text=data.process;
}
}else{
NSLog(#"ERR: object not recognised");
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
any one can help me or solution example for update data in UITableViewCell by indexPath
NOTE: below is just an example u can do it in new project
One thing u need to change the data model packeData, lets say it contains key as NSIntager which holdes the index of the cell and process is NSString which holds the progress as string value for example
in packeData.h
#import <Foundation/Foundation.h>
#interface packeData : NSObject
#property (nonatomic, assign) NSInteger key; //holds index
#property (nonatomic, strong) NSString *process; //holds the progress info
#end
and in packeData.m
#import "packeData.h"
#implementation packeData
- (id)init //simply initialise it
{
self = [super init];
if(self)
{
}
return self;
}
#end
and in view controller where u are tableview,
in ViewController.h
#import <UIKit/UIKit.h>
#import "packeData.h"
#interface ViewController : UIViewController <UI TableViewDataSource,UITableViewDelegate>
#property (weak, nonatomic) IBOutlet UITableView *aTableView;
#property (strong,nonatomic) NSMutableArray *recipes; //array acts as datasource
#end
in in ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(theReciever:) name:#"theSender" object:nil];
_recipes = [[NSMutableArray alloc]init]; //initilise your datasource
for(int j = 0 ;j< 20;j++)
{
// for my example i took some values
//initially put some initial values
packeData *data = [[packeData alloc] init];
data.key = j;
data.process = [NSString stringWithFormat:#"game_name_%d",j];
[_recipes addObject:data];
}
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[NSTimer scheduledTimerWithTimeInterval:0.8 target:self selector:#selector(PostNotification) userInfo:nil repeats:YES]; //just for testing
}
- (void)PostNotification
{
//i am simply posting the notification with some random values
packeData *data = [[packeData alloc]init];
data.key = arc4random()%15;
data.process = [NSString stringWithFormat:#"%ld",( data.key + 20)];
[[NSNotificationCenter defaultCenter] postNotificationName:#"theSender" object:data];
}
- (void)theReciever:(NSNotification *)notif
{
if([notif.object isKindOfClass:[packeData class]]){
packeData *data=[notif object];
NSInteger key=data.key;
NSInteger index= key;
//modify the datasource
packeData *recipes_data = [_recipes objectAtIndex:index]; //get the pocket present in array
recipes_data.process = data.process; //modify the recipes data
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
UITableViewCell *cell=(UITableViewCell*)[self.aTableView cellForRowAtIndexPath:indexPath];
if(cell==nil)
{
NSLog(#"cell NULL");
}else
{
[self.aTableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
// cell.textLabel.text=data.process; no need u already mofied the content in the datasource this will call the "cellForRowAtIndexPath" method and displays the process in place of game name
}
}else{
NSLog(#"ERR: object not recognised");
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [_recipes count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableCell";
UITableViewCell* cell = [self.aTableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
NSLog(#"cell nil");
}
packeData *idgame= [_recipes objectAtIndex:indexPath.row];
cell.textLabel.text = idgame.process; //initially contains game name
cell.tag=indexPath.row;
return cell;
}
#end
EDIT
replace the below methods
- (void)PostNotification
{
//i am simply posting the notification with some random values
packeData *data = [[packeData alloc]init];
data.key = arc4random()%15; //15 change the number of rows
data.process = [NSString stringWithFormat:#"%ld",( data.key + arc4random() % 100)];
[[NSNotificationCenter defaultCenter] postNotificationName:#"theSender" object:data];
}
- (void)theReciever:(NSNotification *)notif
{
if([notif.object isKindOfClass:[packeData class]]){
packeData *data=[notif object];
NSInteger key=data.key;
NSInteger index= key;
//modify the datasource
packeData *recipes_data = [_recipes objectAtIndex:index]; //get the pocket present in array
recipes_data.process = data.process; //modify the recipes data
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
UITableViewCell *cell=(UITableViewCell*)[self.aTableView cellForRowAtIndexPath:indexPath];
if(cell==nil)
{
NSLog(#"cell NULL");
[self.aTableView reloadData]; //if cell is not visible then reload the whole table
}else
{
[self.aTableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
// cell.textLabel.text=data.process; no need u already mofied the content in the datasource this will call the "cellForRowAtIndexPath" method and displays the process in place of game name
}
}else{
NSLog(#"ERR: object not recognised");
}
}
Edit 2
as for testing just change the below method and as soon as simulator launches the app scroll to down so that only top 5 rows only updates, wait for 5 to 10 seconds and scroll to top and u will see all the calls are updates with same process 5
//scroll down as soon as launches the app and wait for 5 to 10 seconds then scroll to top u will see top 5 cells are updates with progress 5
- (void)PostNotification
{
packeData *data = [[packeData alloc]init];
data.key = arc4random()%5; //only top 5 cells are modify other wont modify
data.process = [NSString stringWithFormat:#"%ld",5];//updates with some same progress lates give it as 5 //( data.key + arc4random() % 100)];
[[NSNotificationCenter defaultCenter] postNotificationName:#"theSender" object:data];
}
form the above test u will see the top 5 cells are updates even when they are not visible
You can't set the value of any of your cell's controller apart from cellForRowAtIndexPath you have to populate the UITableViewCell data with an array, then when you want to update the data in your cell, update your array according to data, then update the single cell of your UITableView like this.
[tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:#[indexPathOfYourCell] withRowAnimation:UITableViewRowAnimationNone];
[tableView endUpdates];
Just specify your index path of your row and reload...
NSIndexPath* path = [NSIndexPath indexPathForRow:3 inSection:0];
NSArray* rowsToReload = [NSArray arrayWithObjects:path, nil];
[tableView reloadRowsAtIndexPaths:rowsToReload withRowAnimation:UITableViewRowAnimationNone];

Set UITableViewCell contents only once? [duplicate]

I'm using https://github.com/mineschan/MZTimerLabel/
and in my Tableview cellForRowAtIndex using the timer like below:
UILabel *lblTimer=(UILabel *)[cell viewWithTag:10];
MZTimerLabel *UpgradeTimer = [[MZTimerLabel alloc] initWithLabel:lblTimer andTimerType:MZTimerLabelTypeTimer];
[UpgradeTimer setCountDownTime:timestamp];
[UpgradeTimer startWithEndingBlock:^(NSTimeInterval timestamp) {
lblTimer.text = #"✔";
}];
But after any table reloading or scrolling, the timer behaves strange and seems it re-generates multiple timers for counting in the same place.
How should I fix this while using this timer?
Appreciate any help,
Elias
I had a look at MZTimerLabel, and it violates MVC badly. It puts something that belongs into the model (the timer that count's down the time) into the view. That is where your problem comes from. Views should be able to be recreated without having side effects on the model.
I would recommend to ditch that class, and create your own. It's actually quite easy to achieve something like this.
Create a new class that saves a title and a endDate
Store instances of that class in the model that backs your table
Create one NSTimer that refreshes the tableView
Set up your cells.
That's basically all the code you need for a basic countdown in a table. Because it does not store any data in the view you can scroll as much as you like:
#interface Timer : NSObject
#property (strong, nonatomic) NSDate *endDate;
#property (strong, nonatomic) NSString *title;
#end
#implementation Timer
#end
#interface MasterViewController () {
NSArray *_objects;
NSTimer *_refreshTimer;
}
#end
#implementation MasterViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSMutableArray *modelStore = [NSMutableArray arrayWithCapacity:30];
for (NSInteger i = 0; i < 30; i++) {
Timer *timer = [[Timer alloc] init];
timer.endDate = [NSDate dateWithTimeIntervalSinceNow:i*30];
timer.title = [NSString stringWithFormat:#"Timer %ld seconds", (long)i*30];
[modelStore addObject:timer];
}
_objects = modelStore;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[_refreshTimer invalidate]; // timer should not exist, but just in case.
_refreshTimer = [NSTimer timerWithTimeInterval:0.5f target:self selector:#selector(refreshView:) userInfo:nil repeats:YES];
// should fire while scrolling, so we need to add the timer manually:
[[NSRunLoop currentRunLoop] addTimer:_refreshTimer forMode:NSRunLoopCommonModes];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[_refreshTimer invalidate];
_refreshTimer = nil;
}
- (void)refreshView:(NSTimer *)timer {
// only refresh visible cells
for (UITableViewCell *cell in [self.tableView visibleCells]) {
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
[self configureCell:cell forRowAtIndexPath:indexPath];
}
}
#pragma mark - Table View
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return _objects.count;
}
- (void)configureCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
Timer *timer = _objects[indexPath.row];
cell.textLabel.text = timer.title;
NSInteger timeUntilEnd = (NSInteger)[timer.endDate timeIntervalSinceDate:[NSDate date]];
if (timeUntilEnd <= 0) {
cell.detailTextLabel.text = #"Finished";
}
else {
NSInteger seconds = timeUntilEnd % 60;
NSInteger minutes = (timeUntilEnd / 60) % 60;
NSInteger hours = (timeUntilEnd / 3600);
cell.detailTextLabel.text = [NSString stringWithFormat:#"%02ld:%02ld:%02ld", (long)hours, (long)minutes, (long)seconds];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
[self configureCell:cell forRowAtIndexPath:indexPath];
return cell;
}
#end

Using timer in a tableview re creates the timer after any scroll or table reload

I'm using https://github.com/mineschan/MZTimerLabel/
and in my Tableview cellForRowAtIndex using the timer like below:
UILabel *lblTimer=(UILabel *)[cell viewWithTag:10];
MZTimerLabel *UpgradeTimer = [[MZTimerLabel alloc] initWithLabel:lblTimer andTimerType:MZTimerLabelTypeTimer];
[UpgradeTimer setCountDownTime:timestamp];
[UpgradeTimer startWithEndingBlock:^(NSTimeInterval timestamp) {
lblTimer.text = #"✔";
}];
But after any table reloading or scrolling, the timer behaves strange and seems it re-generates multiple timers for counting in the same place.
How should I fix this while using this timer?
Appreciate any help,
Elias
I had a look at MZTimerLabel, and it violates MVC badly. It puts something that belongs into the model (the timer that count's down the time) into the view. That is where your problem comes from. Views should be able to be recreated without having side effects on the model.
I would recommend to ditch that class, and create your own. It's actually quite easy to achieve something like this.
Create a new class that saves a title and a endDate
Store instances of that class in the model that backs your table
Create one NSTimer that refreshes the tableView
Set up your cells.
That's basically all the code you need for a basic countdown in a table. Because it does not store any data in the view you can scroll as much as you like:
#interface Timer : NSObject
#property (strong, nonatomic) NSDate *endDate;
#property (strong, nonatomic) NSString *title;
#end
#implementation Timer
#end
#interface MasterViewController () {
NSArray *_objects;
NSTimer *_refreshTimer;
}
#end
#implementation MasterViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSMutableArray *modelStore = [NSMutableArray arrayWithCapacity:30];
for (NSInteger i = 0; i < 30; i++) {
Timer *timer = [[Timer alloc] init];
timer.endDate = [NSDate dateWithTimeIntervalSinceNow:i*30];
timer.title = [NSString stringWithFormat:#"Timer %ld seconds", (long)i*30];
[modelStore addObject:timer];
}
_objects = modelStore;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[_refreshTimer invalidate]; // timer should not exist, but just in case.
_refreshTimer = [NSTimer timerWithTimeInterval:0.5f target:self selector:#selector(refreshView:) userInfo:nil repeats:YES];
// should fire while scrolling, so we need to add the timer manually:
[[NSRunLoop currentRunLoop] addTimer:_refreshTimer forMode:NSRunLoopCommonModes];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[_refreshTimer invalidate];
_refreshTimer = nil;
}
- (void)refreshView:(NSTimer *)timer {
// only refresh visible cells
for (UITableViewCell *cell in [self.tableView visibleCells]) {
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
[self configureCell:cell forRowAtIndexPath:indexPath];
}
}
#pragma mark - Table View
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return _objects.count;
}
- (void)configureCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
Timer *timer = _objects[indexPath.row];
cell.textLabel.text = timer.title;
NSInteger timeUntilEnd = (NSInteger)[timer.endDate timeIntervalSinceDate:[NSDate date]];
if (timeUntilEnd <= 0) {
cell.detailTextLabel.text = #"Finished";
}
else {
NSInteger seconds = timeUntilEnd % 60;
NSInteger minutes = (timeUntilEnd / 60) % 60;
NSInteger hours = (timeUntilEnd / 3600);
cell.detailTextLabel.text = [NSString stringWithFormat:#"%02ld:%02ld:%02ld", (long)hours, (long)minutes, (long)seconds];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
[self configureCell:cell forRowAtIndexPath:indexPath];
return cell;
}
#end

After adding new cell, how to get the UITableViewControler to save the new entry, so when app is closed and re opened the new cell is there

Ok, so I'm new to iOS Development. I have just created a master view application for note taking. Every thing works fine except for one thing, after a new cell is created and i stop the app and reopen the app the new cell is not there!
Thanks in advance!
Master View Controller.m
#import "MasterViewController.h"
#import "DetailViewController.h"
#interface MasterViewController () {
NSMutableArray *_objects;
}
#end
#implementation MasterViewController
#synthesize myTableView, numbers;
- (void)awakeFromNib
{
[super awakeFromNib];
}
- (void)viewDidLoad
{
[super viewDidLoad];
//NSMT arry
numbers = [[NSMutableArray alloc]initWithObjects:#"One",#"Two",#"Three",#"Four", nil];
// Do any additional setup after loading the view, typically from a nib.
self.navigationItem.leftBarButtonItem = self.editButtonItem;
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(insertNewObject:)];
self.navigationItem.rightBarButtonItem = addButton;
}
- (void)viewWillAppear:(BOOL)animated {
}
-(void)viewWillDisappear:(BOOL)animated{
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)insertNewObject:(id)sender
{
UIAlertView * alert = [[UIAlertView alloc] initWithTitle:#"Enter" message:#"" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Ok", nil];
alert.alertViewStyle = UIAlertViewStylePlainTextInput;
[alert show];
/*
if (!numbers) {
numbers = [[NSMutableArray alloc] init];
}
[numbers insertObject:[NSDate date] atIndex:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
*/
}
-(void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
[myTableView setEditing:editing animated:animated];
}
#pragma mark - Table View
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return numbers.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
cell.textLabel.text = [numbers objectAtIndex:indexPath.row];
return cell;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
return YES;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
[numbers removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
//if user hits OK
if (buttonIndex == 1) {
NSString * tempTextField = [alertView textFieldAtIndex:0].text;
if (!numbers) {
numbers = [[NSMutableArray alloc] init];
}
[numbers insertObject:tempTextField atIndex:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.myTableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
/*
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
}
*/
/*
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the item to be re-orderable.
return YES;
}
*/
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"showDetail"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
NSDate *object = _objects[indexPath.row];
[[segue destinationViewController] setDetailItem:object];
}
}
#end
MasterViewController.h
#import <UIKit/UIKit.h>
#interface MasterViewController : UITableViewController
<UITableViewDataSource, UITableViewDelegate, UIAlertViewDelegate>
#property (strong, nonatomic) IBOutlet UITableView *myTableView;
#property (strong, nonatomic) NSMutableArray * numbers;
#end
Perhaps tell us how you're storing the data used to populate the tableview. As this is what you need to save.
You could save it using CoreData or just use NSUserDefaults, there are plenty of options open to you. However, without knowing how you data is currently structured it's hard to give you a specific example.
Show some code and you'll get a better answer.

Resources