Where to addObserver to NSNotificationcenter in a custom UITableViewCell? - ios

In my UITableViewCell I have a method initNotification which is called by the TableViewController in cellForRowAtIndexPath where the TableCells are created.
My Problem is that, every time this view is reloaded, the initNotification method is called again, so when the Notification appears, the NotificationHandle is called x-times!
I have tried to remove the Observer before adding it again with:
-(void) initNotification{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(handleNotificationOnOff:)
name:[[NSString alloc] initWithFormat:#"%#",[self.light beckhoffOnOff]]
object:nil];
}
but this do not work either.
The Problem is, I cannot use a bool-flag or anything like that, because the Cells are always reinitialized by the ViewController.
Is there a proper way to remove the NotificationHandle form the NotificationCenter?
edit: This is how I create my custom TableViewCells
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
Light* l = [[staticModel.model getRoomAtIndex:[indexPath section]]getLightAtIndex:[indexPath item]];
if([l typ]==ONOFF){
TableCellLight *conof = [tableView dequeueReusableCellWithIdentifier:#"ReuseIDOnOff" forIndexPath:indexPath];
LightOnOff *lonof = (LightOnOff*) l;
[[conof label] setText: [lonof bezeichnung]];
conof.light=lonof;
[conof initNotification];
cell = conof;
}
if([l typ]==DIMMER){
TableCellLightDim *cdim = [tableView dequeueReusableCellWithIdentifier:#"ReuseIDDim" forIndexPath:indexPath];
LightDim *ldim= (LightDim*) l;
[[cdim label] setText: [ldim bezeichnung]];
[[cdim slider]setValue:[ldim dimVal]];
cdim.light=ldim;
[cdim initNotification];
cell = cdim;
}
if([l typ]==RGB){
TableCellLightRGB *crgb = [tableView dequeueReusableCellWithIdentifier:#"ReuseIDRGB" forIndexPath:indexPath];
LightRGB *lrgb= (LightRGB*) l;
[[crgb label] setText: [lrgb bezeichnung]];
crgb.light=lrgb;
crgb.owner=self;
[crgb initNotification];
cell = crgb;
}
return cell;
}
Thanks

Generally speaking the cell shouldn't be observing anything. The controller should be observing changes and pushing the updated information onto the cells.
Calling removeObserver: before adding the observer should work. If you were going to do anything in prepareForReuse or tableView:didEndDisplayingCell:forRowAtIndexPath: to reset the cell, that would be the code you use. You need to look at how you tested that it wasn't working and how you're reusing cells.

Related

Wrong object added to UITableView

In my app I get an object by NSNotificationCenter (form another controller) and add the object to UITableView:
-(void)viewWillAppear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(RosterSave:) name:#"RosterSave" object:nil];
}
-(void)RosterSave:(NSNotification *)notification
{
NewRoster* newRoster = [[NewRoster alloc]init];
newRoster = notification.object;
[myUser.rosterArray addObject:newRoster];
[self.myRoster reloadData];
}
This is the tableView method:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return myUser.rosterArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *iden = #"MyTable";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:iden];
if (cell == nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:iden];
}
NewRoster* myNewRoster = [myUser.rosterArray objectAtIndex:indexPath.row];
cell.textLabel.text = myNewRoster.nameRoster;
return cell;
}
When the user adds the first object, the tableView get own row. When the user adds the second object, it adds two rows of the second object and on this way.
How can I fix this issue?
You have add observer(notification) in viewWillAppear which get called everytime when view will appear.
add notification in viewDidLoad instead of viewwillAppear.
I always like to put NSNotification subscriptions in init / and unsubscriptions in dealloc. This pattern is easy to read and debug. Also, it guarantees you will never double subscribe or double unsubscribe.
In your case, you are prone to creating multiple subscribtions in viewWillAppear
- (instancetype)init
{
...
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(RosterSave:) name:#"RosterSave" object:nil];
...
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#Feroz is right about you allocating a new object and replacing it with notification.object. #Lion is right about viewDidLoad vs. viewDidAppear You are generating multiple notifications. You need to only generate one per object. Put a breakpoint in your RosterSave code and count how many times it's called per new object. Also look at the stack trace to see who is generating these calls. It's down to a simple matter of stepping through, understanding your code, and seeing what's happening.

selectRowAtIndexPath not called

I have a class that allows a user to add entries to a server-side table. Everything works correctly until I attempt to refresh the UITableView with the new data. I make a server call to get the new dataset, use it to refresh the NSArray that is the data source for the table, and then attempt to reload the table. Here is the method that is called when the data comes back from the server:
- (void) logEntriesRefreshed : (NSNotification *) notification {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:#"log_entries_refreshed"
object:nil];
NSLog(#"returned from log entries fetch");
_logEntriesArray = [LogEntriesDataFetcher getLogEntriesArray];
[_tableView reloadData];
_activityIndicator.hidden = YES;
[_activityIndicator stopAnimating];
NSLog(#"log entries array count: %lu", [_logEntriesArray count]);
[_tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
animated:NO
scrollPosition:UITableViewScrollPositionNone];
}
It's this last line that is the problem. I want to programmatically select the first row in the table (there has to be at least one, since I just added a row). But it appears that this line never executes. Note this method, which should go next:
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"here");
UITableViewCell *previousCell = (UITableViewCell *)[_tableView cellForRowAtIndexPath:_previousIndexPath];
previousCell.backgroundColor = [UIColor clearColor];
previousCell.textLabel.textColor = [SharedVisualElements primaryFontColor];
UITableViewCell *cell = (UITableViewCell *)[_tableView cellForRowAtIndexPath:indexPath];
cell.contentView.backgroundColor = [SharedVisualElements secondaryFontColor];
cell.textLabel.textColor = [SharedVisualElements primaryFontColor];
_previousIndexPath = indexPath;
// get the file attributes for the cell just selected
_currentEntry = (LogEntry *)[_logEntriesArray objectAtIndex:[indexPath row]];
NSLog(#"array count: %lu", (unsigned long)[_logEntriesArray count]);
NSLog(#"current entry: %ld", (long)[indexPath row]);
_isExistingEntry = YES;
_arrayPositionOfEntryBeingEdited = [indexPath row];
[self initializeValues];
[self initializeObjects];
[self captureStartingValuesForStateMachine];
}
I have break points set on the selectRowAtIndexPath line and also on the first NSLog(#"here") line in didSelectRow.... I get to the selectRowAtIndexPath line but never to the didSelectRow method. My console output is consistent with that:
returned from log entries fetch
log entries array count: 7
and that is the end of it. Nothing from the didSelectRow... method. There are no errors thrown, either.
What am I missing. Seems pretty straightforward, but nothing I do seems to work.
As per Apple's documentation, calling selectRowAtIndexPath will NOT invoke the didSelectRowAtIndexPath. Take a look here.
Calling this method does not cause the delegate to receive a
tableView:willSelectRowAtIndexPath: or
tableView:didSelectRowAtIndexPath: message, nor does it send
UITableViewSelectionDidChangeNotification notifications to observers.
To specifically invoke the didSelectRowAtIndexPath delegate method, use the following code:
[[tableView delegate] tableView:tableView didSelectRowAtIndexPath:indexPath];
Hope this helps.

Animate UITableViewCells with repeat and scroll

I have a simple table view, I'm using custom cells with one UIImage and some labels.
I want to animate the image size, for that I've added the following in the cellForRowAtIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"customCell";
NotificationTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[NotificationTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
if( [[[notification objectAtIndex:[indexPath section]] valueForKey:#"readed"] isEqualToString:#"readed"])
{
[UIView animateWithDuration:0.9
delay:0.3
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionRepeat
animations:(void (^)(void)) ^{
cell.imgIcon.transform=CGAffineTransformMakeScale(0.8, 0.8);
}
completion:^(BOOL finished){
cell.imgIcon.transform=CGAffineTransformIdentity;
}];
}
return cell;
}
When I launch the app it works ok, only the "readed" cells are animating in a bucle with the UIViewAnimationOptionRepeat option.
The problem begins when i scroll over the tableview. Some "readed" cells stops animating, and the cells animation time loop is different in each cell.
Any idea?
Thanks
EDITED: I think that the problem is something about reusable cells.
You said "I try reloadind the tableview but this dont work." (sic)
You tried reloading the table view when and how? In response to a
UIApplicationWillEnterForegroundNotification message handler being called? Post that code.
I would add a log statement in your cellForRowAtIndexPath method to make sure it's being called, and log the indexPath(s) that are being updated.
Are you sure you want the images in every cell of your table view to animate every time your app comes back to the foreground. That seems like it would be annoying.
Reloading the tableView should cause the animation to occur, as Duncan has said. Make sure you breakpoint/log the animation to make sure that the code is actually getting there. Once you figure out how to cause the animation to occur again by reloading the table, you can add two NSNotificationCenter callouts to let the app know to reload the table once the app resumes activity from the background.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appDidEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
Just set up the proper selectors to handle and reload your table:
e.g
- (void)appDidBecomeActive:(id)sender {
[_tableView reloadData];
}

UITableView reloadData not working on MainTable in Split View Controller

I have a feature in my app where the user can change the color scheme of the app. The app uses a Split View Controller, with a MainTable and DetailView table. Everything works fine except for the MainTable. What is failing is that the MainTable reloadData method is not causing the cells to be redrawn.
It should be noted that I am changing globalHighContrast and sending the notification from a UIModalPresentationFormSheet viewController, so the tables are kind of visible on the screen while the viewController is active.
I am triggering the screen update from a notification, like this:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(reloadAllTables)
name:#"contrastModeChanged"
object:nil];
Then, to make sure that I call reloadData on the main thread, I am handling the notification like this:
-(void)reloadAllTables{
[self performSelectorOnMainThread:#selector(doReloadAllTables) withObject:nil waitUntilDone:NO];
}
-(void)doReloadAllTables{
[self showIcon];
if( globalHighContrast ){
theTable.backgroundColor = [Colors lightBkgColor];
self.view.backgroundColor = [Colors lightBkgColor];
} else {
theTable.backgroundColor = [Colors darkBkgColor];
self.view.backgroundColor = [Colors darkBkgColor];
}
[detailViewController configureView:currentMainMenu];
[detailViewController.subTable reloadData];
[theTable reloadData];
// desperate try to force it to work
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:currentMainMenu inSection:0];
[self tableView:theTable didSelectRowAtIndexPath:indexPath];
}
Both reloadAllTables and doReloadAllTables are being called, but
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
is not being called.
As soon as I tap a cell on the MainTable it does update correctly to the new color scheme.
Also, there is a desperate attempt to workaround this by trying to simulate the MainTable touch, but that doesn't work either.
You can try to put code for updating you scheme in -(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath method...

UITableViewCell will disappear

I made an UITableView and contained some custom UITableViewCells, in the fist cell (named cell0 for example) there are some UITextFields for input, when I scroll the tableView, cell0 will disappear from the top of screen, then How can I get the UITextField's text in cell0?
cellForRowAtIndexPath will return nil.
The only method I found is a tableview delegate
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
[cell dealloc]
}
According to Apple Documentation about cellForRowAtIndexPath:, it returns "An object representing a cell of the table or nil if the cell is not visible or indexPath is out of range."
A UITableViewCell is a view, according to MVC Pattern. So I'd prefer maintaining a model object -- maybe it is as simple as a NSString instance -- to hold the text in the cell if I were you. You can observe UITextField's change by adding an observer of UITextFieldTextDidChangeNotification key to your controller.
- (void)textFieldDidChangeText:(NSNotification *)notification
{
// Assume your controller has a NSString (copy) property named "text".
self.text = [(UITextField *)[notification object] text]; // The notification's object property will return the UITextField instance who has posted the notification.
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Dequeue cell...
// ...
if (!cell)
{
// Init cell...
// ...
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textFieldDidChangeText:) name:UITextFieldTextDidChangeNotification object:yourTextField];
}
// Other code...
// ...
return cell;
}
Don't forget to remove the observer in your -dealloc.
As UITableViewCells leave the viewable area of the UITableView it is actually removed from the tableview and placed back into the reuse queue. If it is the selected for reuse it will be returned by dequeueReusableCellWithIdentifier:.
There is no callback for when a cell is removed from the view. However, prepareForReuse is called on the cell just before it is returned by dequeueReusableCellWithIdentifier:.
What are you ultimately trying to do?
You need to save the text somewhere (e.g. an NSArray) the moment it gets changed.
You can init textfield as instance variable.
look like:
.h
UITextField *textfiled;
.m
-(void)viewDidLoad
{
//init textfield
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//init cell...
[cell addSubview:textfield];
return cell;
}

Resources