Wrong object added to UITableView - ios

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.

Related

Model object not being released after removing observer

My UIViewController has a UITableView. Each custom cell is given a model object with weak association.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
List *list = self.lists[indexPath.row];
ListCoverTableViewCell *cell = (ListCoverTableViewCell *)[tableView dequeueReusableCellWithIdentifier:NormalCell forIndexPath:indexPath];
cell.list = list;
return cell;
}
Each cell then observes a property on the model object.
- (void)addProgressObserverToCell:(ListCoverTableViewCell *)cell
{
#try {
[cell.list.tasksManager addObserver:cell
forKeyPath:NSStringFromSelector(#selector(fractionCompleted))
options:0 context:nil];
} #catch (NSException *__unused exception) {}
}
addProgressObserverToCell: is called from viewWillAppear (in case the user taps on a cell and comes back), and in tableView's willDisplayCell: (for when the user scrolls).
A similar method removeProgressObserverFromCell gets called in viewWillDisappear (for when the user taps a cell and navigates away) and in tableView's didEndDisplayingCell (for when the user scrolls).
- (void)removeProgressObserverFromCell:(ListCoverTableViewCell *)cell
{
#try {
[cell.list.tasksManager removeObserver:cell
forKeyPath:NSStringFromSelector(#selector(fractionCompleted))
context:nil];
} #catch (NSException *__unused exception) {}
}
So far, everything is balanced. I add observers in viewWillAppear/willDisplayCell, and remove them in viewWillDisappear/didEndDisplayingCell.
To be safe (and defensive), I updated my ViewController's dealloc method to also remove all observers. I simply loop through the tableView's visible cells and call removeProgressObserverFromCell:.
By running this code in the dealloc, I'm finding the model objects stored within my UITableView's visibleCells are never released. How is my defensive removal of observers causing my model object to be retained?

NSNotification not returning

I have a modalUIViewController that has a UITableView on it. For whatever cell the user selects, I want to return that text to the previous view controller and dismiss the modal view. I'm using NSNotifications to send the value back. Problem is, my notification is never received.
Here is the code from the 'parent' view:
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(choiceReceived:)
name:#"selectionMade"
object:nil];
[self performSegueWithIdentifier: #"locationsDetailsSegue" sender: self];
}
- (void) choiceReceived: (NSNotification *) notification
{
NSLog(#"test");
NSDictionary *dict = [notification userInfo];
NSString *user_choice = [dict objectForKey:#"choice"];
NSLog(#"%#", user_choice);
[[NSNotificationCenter defaultCenter] removeObserver:self
name: #"selectionMade"
object:nil];
}
And in the modal view controller:
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
NSString *choice = cell.textLabel.text;
// send a notification of this choice back to the 'parent' controller
NSDictionary *dict = [NSDictionary dictionaryWithObject:choice forKey:#"choice"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"selectionMade" object:nil userInfo:dict];
NSLog(#"%#", [dict objectForKey:#"choice"]);
[self dismissViewControllerAnimated:YES completion:nil];
}
I get the correct output from the notifier, but I get no output whatsoever from the receiver. Am I missing something obvious? Thanks!
Well, i don't like use NSNotificationCenter in such scenario (Its just my suggestion). I'm always recommend delegate pattern in such case. Delegation pattern working or communicate one-to-one object notification so it give 100% precise output and removing other conflicts.
Create protocol methods in childviewcontroller and delegate property for confirmation in parentclassviewcontroller.
Consume chileviewcontroller protocol in parentviewcontroller. Implement required delegate methods of protocol in parentviewcontroller class. Also you can send multiple types of arguments through delegates method.
for more info go through this doc.

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...

Where to addObserver to NSNotificationcenter in a custom UITableViewCell?

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.

NSMutableArray property automatically reset in TableViewController

I am currently trying to get a NSMutableArray property on my TableViewController updated from an NSNotification but facing issues.
I have declared my property in the Observer class .h file as below:
#property (nonatomic,strong) NSMutableArray *cart;
Synthesize in Observer class .m file:
#synthesize cart = _cart;
I am receiving the notification in the AwakeFromNib method of the Observer Class:
- (void)awakeFromNib{
if (!self.cart){
NSLog(#"self.cart == nil");
self.cart = [[NSMutableArray alloc] init];
}else{
NSLog(#"self.cart != nil");
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(addCurrentItemToCartFromNotification:) name:#"ItemAddedToCart" object:nil];
}
Please note that I am performing the alloc init of my NSMutableArray property in the above AwakeFromNib method before receiving the notification.
This is the method that is called upon receipt of the notification:
- (void)addCurrentItemToCartFromNotification:(NSNotification *)notification{
NSDictionary *currentItem = [notification.userInfo objectForKey:#"CART_ITEM_INFORMATION"];
[self.cart addObject:currentItem];
[self.tableView reloadData];
}
I then have my tableview datasource methods based on my NSMutableArray property that is updated in the above methods from the notification.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.cart count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"itemInCart";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
NSDictionary *cartItem = [self.cart objectAtIndex:indexPath.row];
cell.textLabel.text = [cartItem objectForKey:#"ITEM_NAME"];
return cell;
}
My expected behavior from the program is to update my NSMutable array property very time the notification is received(The alloc init should happen only the very first time because of the if (!self.cart) condition)
But what is happening is every time I receive a notification, the object in the NSMutableArray is deleted and the new one is added instead of appending. Hence at any point in time, the NSMutableArray only contains the object received fomr the most recent notification.
I am thinking the alloc init is happening every time as opposed to just the very first time.
Could you please tell me what I am missing here. I would really appreciate your inputs on this issue.
Thanks,
Mike
Not sure why you're seeing that array reallocated (if that's what's going on), but this calls for a different pattern anyway: I'd lazy init your cart property by replacing the synthesized setter...
- (NSArray *)cart {
if (!_cart) {
_cart = [NSMutableArray array];
}
return _cart;
}
Delete that cart stuff in awakeFromNib, and always refer to self.cart (except in init and dealloc).
The fact that you're getting "self.cart == nil" logged each time you add an entry means that awakeFromNib is being called every time you add an entry, which in turn means that you're creating a new instance of Observer class each time. So that's the problem, not any of the code that's in your post. To fix the problem, we need to know how you create this class.

Resources