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
Related
I have created a tier of three UITableViews using Storyboard and am able to move from the first to the second and then to the third. I now want to create a fourth UITableView programmatically. By selecting a Cell in the third UITableView a fourth tier UITableView must be created and displayed so that I can populate it with data. Please help. Thanks very much. Herewith included is the portion of the code that I want to execute :
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
cell.textLabel.text = [self.grapes objectAtIndex:indexPath.row];
if ([cell.textLabel.text isEqual: #"Grape variety"]) {
//create the navigation controller and add the controllers view
navigationController = [[UINavigationController alloc] init];
[self.window addSubview:[self.navigationController view]];
//check if the display viewcontroller exists, otherwise create it
if(self.displayViewController == nil) {
ViewController *inputView = [[ViewController alloc] init];
self.displayViewController = inputView;
}
//push the display viewcontroller into the navigation view controller stack
[self.navigationController pushViewController:self.displayViewController animated:
}
The "-(void)tableView ... is from the TableView created using Storyboard and the last portion of the code is the TableView I want to create programmatically.
If I excecute the code the newly created TableView corrupts the one created in the Storyboard and it doesn't display the one I want to create programmatically, somehow one must be able to change the priority of the display and get the latest created one to be the one that is active
I would use a drill down approach where the segue on the storyboard effectively loops back on itself (see image below):
You could then provide an abstracted datasource and delegate implementation to make the view controller as light of logic as possible. Eg
#implementation ArrayDataSource
- (id)itemAtIndexPath:(NSIndexPath*)indexPath {
return items[(NSUInteger)indexPath.row];
}
- (NSInteger)tableView:(UITableView*)tableView
numberOfRowsInSection:(NSInteger)section {
return items.count;
}
- (UITableViewCell*)tableView:(UITableView*)tableView
cellForRowAtIndexPath:(NSIndexPath*)indexPath {
id cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier
forIndexPath:indexPath];
id item = [self itemAtIndexPath:indexPath];
configureCellBlock(cell,item);
return cell;
}
#end
You could then configure it according to which tableview you are using by keeping a track of where you are:
void (^configureCell)(PhotoCell*, Photo*) = ^(PhotoCell* cell, Photo* photo) {
cell.label.text = photo.name;
};
photosArrayDataSource = [[ArrayDataSource alloc] initWithItems:photos
cellIdentifier:PhotoCellIdentifier
configureCellBlock:configureCell];
self.tableView.dataSource = photosArrayDataSource;
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.
1) I started a new project in xcode using the single view application.
2) I deleted the default view controller and added a new UITableViewController
3) In storyboard, I dragged out a UITableViewController and set it to the one I just created
4) Set the reuse identifier
In my code I tried to override the init method to do some setup. Why is my custom init method not being called? When you are using storyboard, and you drag out a UITableViewController and set it to a custom class, can you not override the initWithStyle: method? When I put the setup in viewDidLoad then it worked.
Here is the code for the view controller:
#import "ItemsViewController.h"
#import "BNRItem.h"
#import "BNRItemStore.h"
#implementation ItemsViewController
- (id)init
{
// Call the superclass's designated initializer
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
for (int i = 0; i < 10; i++) {
[[BNRItemStore defaultStore] createItem];
NSLog(#"Test init");
}
}
return self;
}
- (id)initWithStyle:(UITableViewStyle)style
{
NSLog(#"test init style");
return [self init];
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
NSLog(#"test tableview rowsinsection");
return [[[BNRItemStore defaultStore] allItems] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"test tableview cellforrow");
// Create an instance of UITableViewCell, with default appearance
// Check for a reusable cell first, use that if it exists
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:#"itemsCell"];
// If there is no reusable cell of this type, create a new one
if (!cell) {
cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"itemsCell"];
}
// Set the text on the cell with the description of the item
// that is at the nth index of items, where n = row this cell
// will appear in on the tableview
[[cell textLabel] setText:#"Hello"];
return cell;
}
#end
init is only called when you using [[class alloc] init]
you can override
- (void)awakeFromNib
awakeFromNib
Prepares the receiver for service after it has been loaded from an Interface Builder archive, or nib file.
- (void)awakeFromNib
Discussion
An awakeFromNib message is sent to each object loaded from the archive, but only if it can respond to the message, and only after all the objects in the archive have been loaded and initialized. When an object receives an awakeFromNib message, it is guaranteed to have all its outlet instance variables set.
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