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.
Related
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.
I'm new to Objective-C and iOS programming, using storyboards.
Scene 1: Contains a Table View. (ViewController.m)
Displaying a Table View populated by NSMutableArray from a singleton class.
Add button -> Scene 2.
Scene 2: Create and add object. (AddObjectViewController.m)
Creating and saving a custom object to NSMutableArray in a singleton class to access it from different Scenes/View Controllers.
Save button -> Scene 1.
Cancel button -> Scene 1.
Problem
Upon return to Scene 1, the Table View is blank.
At the time of - (void)viewDidLoad in Scene 1 (ViewController.m, where table is populated), array in singleton class is indeed empty.
However, shortly after, the array do contain the newly created object.
Leaving Scene 1 and returning to it will update the Table View and list the object that was just added.
It appears more time is needed to write data before initializing singleton class in ViewController.
To that end, I tried to add Wait/Sleep time after writing to the array in Scene 2 (AddObjectViewController.m) and before initializing singleton class in Scene 1 (ViewController.m).
It does not work. Seems like everything sleeps.
What to do? It should already be working by my logic, and from my research, a singleton class should be perfect for this scenario.
Edit: The important code.
Code
ViewController2.m
- (IBAction)saveButtonPressed:(id)sender {
// Custom initialization.
Computer *object = [[Computer alloc] initWithName:nameField.text IP:ipField.text Port:portField.text Password:passwordField.text];
// Add Computer to array of Computers via "Singleton" (shared array).
Computers *sharedComputers = [Computers sharedComputers];
[sharedComputers addComputer:object];
}
ViewController.m
#implementation ViewController{
NSMutableArray *tableData;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *tableIdentifier = #"TableItem";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:tableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:tableIdentifier];
}
cell.textLabel.text = [tableData objectAtIndex:indexPath.row];
cell.imageView.image = [UIImage imageNamed:#"Computer Filled-25.png"];
return cell;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Initialize table data
tableData = [[NSMutableArray alloc] init];
// Add objects to table
Computers *sharedComputers = [Computers sharedComputers];
for (Computer* item in [sharedComputers getArray]) {
[tableData addObject:item.name];
}
}
Computers.m (Singleton class)
- (void)addComputer:(Computer *)aComputer {
[computerArray addObject:aComputer];
}
If you are filling your dataSource array of table view in Scene 1 in viewDidLoad method then you need to know that viewDidLoad will not be called again once you return back to Scene 1 from Scene 2. The solution is to move the dataSource implementation to viewWillAppear method in Scene 1
-(void)viewDidLoad{
[super viewDidLoad];
dataSource = [[NSMutableArray alloc]init];
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[dataSource removeAllObjects];
dataSource = singletonClass.MutableArray; // fill dataSource from your singleton class
}
This will make sure your tableview data is refreshed every time you view Scene1
I am using singletone class for fetch json data and i want to show in tableview but when i am click view first time then data not showing, on second time it's shows and cell repeating data on every click.
- (void)viewDidLoad
{
[super viewDidLoad];
singCls = [SingletoneClass sharedInstanceMethod]; // Declared Class for instance method of singletone class
webserviceclas = [[WebserviceUtility alloc] init];
[webserviceclas getorchardslist];
}
#pragma mark - TableView Delegates
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return singCls.orcharsList.count; // Get json data of orchards list in array
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *orchardscell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(orchardscell == nil)
{
orchardscell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
orchardscell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
NSString *strcell = [singCls.orcharsList objectAtIndex:indexPath.row];
orchardscell.textLabel.text = strcell;
// Display Orchards list pass from json
return orchardscell;
}
Your tableview controller was not notified when actual data was fetched by WebserviceUtility class. I'm assuming that your WebserviceUtility class calls webservices and once the data is received in this class, the singleton class' orcharsList array will be updated. So if I'm correct till this point, then you need to notify the tableviewcontroller after updating orcharsList. And on receiving notification here, the tableView need to be reloaded and then your tableview delegates will be called.
What you need to do is:
Add protocol method in your WebserviceUtility class.
#protocol WebServiceDelegate
-(void)fetchedData:(WebserviceUtility*) self;
#end
Add a delegate property to which notifications need to be send
#interface WebserviceUtility {
id<WebServiceDelegate>delegate;
}
#property (nonatomic, assign) id <WebServiceDelegate> delegate;
Then notify the delegate once data is available
if ([delegate respondsToSelector:#selector(fetchedData:)]) {
[delegate fetchedData:self];
}
Implement those protocols in your tableviewcontroller class and add this class as delegate to WebserviceUtility
In .h file
#interface YourTableViewController: UITableViewController<WebServiceDelegate>
In .m file, assign your tableviewcontroller as delegate for WebserviceUtility
webserviceclas = [[WebserviceUtility alloc] init];
webserviceclas.delegate = self;
Implement the protocol method in your controller
-(void)fetchedData:(WebserviceUtility*) self{
//Reload your tableview here
}
Hope this helps.
I assume that you did not embed a callback that notifies your UITableViewController to reload data when your singleton finished loading data. Right now there is no data available when first presenting the tableView and it does not refresh automatically.
I am calling a method in my TableViewController class from another class.
To call the method of displaying the tableview, I do this:
TableViewController *tableVC = [[TableViewController alloc]init];
[tableVC setTableViewContent];
then in TableViewController.h
#interface TableViewController : UITableViewController <UITableViewDelegate, UITableViewDataSource>
{
NSMutableArray *nameArray;
}
-(void)setTableViewContent;
#property (nonatomic, strong) IBOutlet UITableView *tableView;
#end
TableViewController.m
#implementation TableViewController
#synthesize tableView;
- (void)viewDidLoad
{
nameArray = [[NSMutableArray alloc]init];
[super viewDidLoad];
}
-(void)setTableViewContent{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
for(int i=0;i< [appDelegate.businessArray count];i++)
{
NSDictionary *businessDict = [[appDelegate.businessArray objectAtIndex:i] valueForKey:#"location"];
nameArray = [appDelegate.businessArray valueForKey:#"name"];
}
NSLog(#"%#", nameArray);
NSLog(#"tableview: %#", tableView);
// here tableview returns null
[tableView reloadData];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [nameArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"updating tableview...");
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell =[tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
cell.textLabel.text = [nameArray objectAtIndex:indexPath.row];
return cell;
}
For some reason when I try to log the tableview, it returns null, so the ReloadData doesn't work. The delegate and datasource is connected properly in IB, and there is a referencing outlet for tableView.
Any idea what is going on here? Thanks in advance
If you added the table view controller to a container view, then you can get a reference to that controller in prepareForSegue. For a controller in a container view, prepareForSegue will be called right before the parent controller's viewDidLoad, so you don't need to do anything to invoke it. In my example below, I've called the segue "TableEmbed" -- you need to give the segue that identifier in IB.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([segue.identifier isEqualToString:#"TableEmbed"]) {
TableViewController *tableVC = (TableViewController *)segue.destinationViewController;
[tableVC setTableViewContent];
}
}
Be aware that prepareForSegue:sender: is called before either controller's viewDidLoad is called, so you should move the initialization of your array to setTableViewContent, and your reloadTable should go into viewDidLoad.
BTW, it's not clear to me why you want to call setTableContent from your other class anyway. Why not move all the code in that method to the viewDidLoad method of the table view controller?
This is happening because you are calling a method on tableView before it actually exists. Simply initializing that class doesn't draw the table itself, so using reloadData before the table has actually been created doesn't really make any sense.
What you want to do in this situation is create your nameArray in whatever class is calling setTableViewContent, and then pass it in either via a custom init method, or by setting tableVC.nameArray before loading that table view controller.
What I would do is make custom init method like - (id)initWithArray:(NSMutableArray *)nameArr
Which should look something like this:
if (self = [super init]) {
nameArray = [nameArr copy];
}
return self;
Then where you have TableViewController *tableVC = [[TableViewController alloc]init]; put TableViewController *tableVC = [[TableViewController alloc]initWithArray:theNameArray]; where theNameArray is the content in setTableViewContent (which you are now generating in the same class that calls the table view instead of in the table view itself).
Make sense?
I solved a similar situation by creating a "safe" reload method on the UITableViewController:
- (void)reloadTableViewData
{
if ([self isViewLoaded])
[self.tableView reloadData];
}
According to the docs for isViewLoaded:
Calling this method reports whether the view is loaded. Unlike the view property, it does not attempt to load the view if it is not already in memory.
Therefore it is safe to call reloadTableViewData on the table view controller at any time.
I've read all similar questions and tried all suggestions, still nothing. Maybe someone can spot my flaw.
My view controller is initiated from another view controller, by one of two buttons. Button taps send NSNotification (with attached arrays), and this view controller anticipates this notification and then calls this method:
- (void)addContentToArray:(NSNotification *)aNotification {
array = [NSArray arrayWithArray:[aNotification object]];
([array count] == 6) ? (category = YES) : (category = NO);
[myTableView reloadData];
NSLog(#"%d", [array count]);
NSLog(#"%#", myTableView);
}
The method gets called every time, I can see that from changing array count. Here notification object is the array passed from previous view controller, and I assign these objects to my local array property - this is my UITableView source. So what I do is I try to reuse the UITableView to display elements of whatever array is being passed. And it works nicely for the first array passed (whichever first).
When I tap the second button, the new array is passed successfully (as mentioned before, I know that from log of [array count] which is different: 3 vs 6 objects in different arrays). However, what is not happening is that UITableView does not refresh (although the values passed when I select a row in the table are from the correct arrays, even though wrong values are displayed).
Here are UITableView data source methods:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [array count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Identifier"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"Identifier"];
}
cell.textLabel.text = [[array objectAtIndex:indexPath.row] objectForKey:#"name"];
if (category) {
cell.detailTextLabel.text = [[array objectAtIndex:indexPath.row] objectForKey:#"description"];
}
return cell;
}
So, what am I doing wrong?
A few other considerations that might help:
NSLog(#"%#", myTableView); returns (null), which is a bit worrying. myTableView here is UITableView from my nib file, which is correctly connected to the view controller, declared as property and synthesized
The view controller in question is a rightViewController of the PKRevealController, so when it is called repeatedly, viewWillAppear method is called, but not viewDidLoad (although, as I already mentioned, addContentToArray: method is being called every time as well)
Also, for those somewhat familiar with PKRevealController - when I try and log focusedController from my view controller, it says that frontViewController - the one that moves to reveal my view controller - is the one that is focused. Can that be the reason why myTableView is (null)?
I'd be grateful for any insight and help!
Need more code, that part where you created and call Myviewcontroller's addContentToArray method.
I think you used release code there for Myviewcontroller's object, try once with hide that part.
I managed to solve the issue by editing initWithNibName method (old line commented out)
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
//self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
self = [super initWithNibName:#"PurposeCategoryViewController" bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
Apparently, it did have something to do with the fact that my view controller (called PurposeCategoryViewController) was not the top/focused view controller in PKRevealController hierarchy. So, I just needed to specifically indicate my nib file.
What value are you returning in this delegate method:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
make sure it is 1