I want to create a TableView in a TableViewController and initialize it with a dic. The number of items in the dic should determine the number of rows of my TableView. In the Initializer, I display it's count and getting: "resource table created with 8 entries." but when the Table built and the method 'numberOfRowsInSection' is called it returns "Now: 0 entries."
I tried making the dictionary static with no success and also tried "#property(strong,nonatomic,retain)". How can I make the instance not to forget it's content?
edit: moved here: Variable set in the TableView initializer is forgotten when numberOfRowsInSection is called
edit 2: Problem solved in the already linked thread, so is this one.
Relevant code snipplets:
// somewhere in ViewController.m:
...
resourceTableViewController = [[ResourcesTableViewController alloc] initWithDictionary:dictionary];
// ResourcesTableViewController.h:
#interface ResourcesTableViewController : UITableViewController
#property(nonatomic,retain)NSDictionary *resourcesAsDictionary;
- (id)initWithDictionary:(NSDictionary*)dic;
// ResourcesTableViewController.m:
- (id)initWithDictionary:(NSDictionary*)dic {
if (self = [super init])
[self setResourcesAsDictionary:dic];
NSLog(#"resource table created with %i entries.",[_resourcesAsDictionary count]);
return self;
}
...
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
NSLog(#"Now: %i entries.",[_resourcesAsDictionary count]);
return [_resourcesAsDictionary count];
}
Related
I am populating a tableview with the contents of an array. Right now, I create the array in viewdidload and I calculate the number of rows in the delegate method
//in viewdidload
dispatch_async(kBgQueue, ^{
NSData* data = [NSData dataWithContentsOfURL: kItemsURL];
[self performSelectorOnMainThread:#selector(fetchedData:) withObject:data waitUntilDone:YES];
});
[self.tableView reloadData];
//method called in viewdidload to create array...
- (void)fetchedData:(NSData *)responseData {
NSError* error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:responseData //1
options:kNilOptions
error:&error];
NSLog(#" %#",json);
NSArray* latestItems = [json objectForKey:#"items"];
NSLog(#" array:%#",latestItems);
//getItems is a property in .h file
self.getItems = latestItems;
NSLog(#"getItems %#",_getItems); //logs out array ok
int size = [_getItems count];
NSLog(#"there are %d objects in the array", size);//provides correct number
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"getItems %#",_getItems); //logs (null)
int size = [_getItems count];
NSLog(#" %d objects in the array", size);//logs 0
When I count rows in viewdidload after creating the array, I get the correct number, however, when I call count on the array in the delegate method, it returns zero possibly because the tableview is created before Viewdidload is called.
Where should I create the array so that is known by the time numberofrows counts the number of rows in the array?
Edit:
After constructing the array, I save it to a property. However, I have discovered that this property is empty when I then log it to console in the numberofrowsinsection method so the problem seems to lie in how I am storing this array.
Right now, I have a property in the .h file and I've also tried it in the implementation but either way it is not persisting for some reason.
I'm not to familiar with obj-C, but I know you need to initialize your array outside your viewDidLoad() function. The reason why your .count is returning zero, is because your array is acting as a local variable to your viewDidLoad() function. Instead you could initialize the array as field in your UITableViewController class. This is how you would do so in swift, but it applies to obj-C as well:
class YourTableViewController: UITableViewController {
var yourArray = [AnyObject]()
override func viewDidLoad() {
//You can still do any programming to set up values and elements in yourArray[] here
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return yourArray.count
}
//Plus all your other tableView functions...
}
Also if you are passing information to your array between other UIViewController's you can add this function to your class, so every time you come back to your table view it loads the correct table cell count:
override func viewWillAppear(animated: Bool) {
self.tableView.reloadData()
}
I suggest to load the content of your array in viewDidLoad(), that is called once and before the table view use the array. The table view do not load the items before viewDidLoad. Are you doing something much different than this example structure below?
#implementation ViewController {
NSArray *arrayList;
}
- (void)viewDidLoad {
[super viewDidLoad];
arrayList = #[#"item 1", #"item 2"];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
cell.textLabel.text = [arrayList objectAtIndex:indexPath.row];
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return arrayList.count;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
Consider abstracting the creation of your data - the array instantiated in your tableview, into a model instead. This is generally considered to be a better software engineering practice (read : https://developer.apple.com/library/ios/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html ).
What I would do is have another file as a Class or Struct, and populate the data for the array used in your table view in there. I would also recommend having setter/getter methods in your array class/struct as well. When loading the tableview, we would then grab the data from the class/struct in your tableview viewDidLoad() method. The data from your array would also be available at any other point of your application as well, as it is no longer dependant on the tableview.
Side note : You can consider making the class/struct a singleton as well, if the model is supposed to only get instantiated once.
I Have a one table view and i have two array. My arrays name AllItems and SpecialItems. I Use segment control. I wantto if segment value is 0 tableview load AllItems Array, When change segment value and value is = 1 than mytableview reload tada but SpecialItems array. Can u help me please. Thanks.
I solved this problem with table tag.
- (IBAction)segmentControlChanged:(UISegmentedControl *)sender {
if (sender.selectedSegmentIndex == 1) {
mytable.tag = 1;
}
else
{
mytable.tag = 0;
}
[mytable reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(tableView.tag==1)
{
return [specialItems count];
}
else
return [allItems count];
}
You could create two data source classes that implement all the UITableViewDataSource methods: one for AllItems and one for SpecialItems. To switch between the two, connect a valueChanged action. In the method that is called, set the data source and reload the table view.
- (void)valueChange:(UISegmentedControl *)sender
{
if (/* condition for all items */) {
self.tableView.dataSource = self.allItemsDataSource;
} else {
self.tableView.dataSource = self.specialItemsDataSource;
}
[self.tableView reloadData];
}
I would personally create an array which the data is loaded from. Put this in your implementation:
NSArray * _tableData
Then in your viewDidLoad just allocate this for the array which we want it to start on.
_tableData = [[NSArray alloc] initWithArray:allItems];
This initially loads the data we will always see as the segment control starts on index 0. We have to set the initial data somewhere so the tableView loads with some data in it.
Then set the number of rows and the cellForRowAtIndex to pick up from the _tableData array
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _tableData.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView_ cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell * cell = [tableView_ dequeueReusableCellWithIdentifier:bCell];
// Here we use the specific array as we would normally
return cell;
}
This step means the tableView will load with the array. Even if the array is empty the view will still load as the number of cells will be zero.
Now in our value changed function we can reset the array as we need to:
- (IBAction)segmentControlChanged:(UISegmentedControl *)sender {
if (sender.selectedSegmentIndex == 1) {
_tableData = allItems;
}
else {
_tableData = specialItems;
}
[self.tableView reloadData];
}
You just need to make sure the segment control changed is linked up in the XIB file (or programatically) and that you reload the table after choosing the array.
This kind of thing is actually really easy to do. I would definitely recommend working it through step by step if you're having trouble. Make sure each step is working before applying the next:
Get the tableView loading with both sets of data individually
Confirm that the segment control is calling the change function when clicked
Then that should do it
I know that this question have been asked so many times but unfortunately non of the solutions in those questions worked for me.
Here are some of the things I tried:
making sure that numberOfSectionsInTableView is not returning 0
making sure that numberOfRowsInSection is not returning 0.
insuring that reloadData is being called on the main thread by using performSelectorOnMainThread as shown below:
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO]
setting the delegate in viewDidLoad, as shown below:
(void)viewDidLoad {
[super viewDidLoad];
// .....
self.tableView.dataSource = self;
self.tableView.delegate = self;
}
I'm creating an iOS app that keeps track of events. The app's main view is a UITableViewController (Calling it main list) that is embedded into a UINavigationController which is embedded into a UITabViewController. The user can create two objects a list or an event. When a new list is created and selected from the current UITableViewController, a new UITableViewController is pushed into the same UINavigationController.
Each list (UITableViewController) has an edit button that allows the user to delete the list.
Assuming that the user chooses to delete "list 3" that belongs to the main list, that is there are 3 view controllers pushed to the UINavigationController:
UITableViewController for the main list
UITableViewController for the selected list "list 3"
UITableViewController for the edit list view
when the delete button is tapped on, an alert is shown. If the user chooses to proceed the following code gets executed:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
// .....
//here I'm calling unwind which is going to pop the UITableViewController
//for the edit table view
[self performSegueWithIdentifier:#"unwindFromDelete" sender:nil];
//here I'm calling a method i wrote to pop the UITableViewController
//of the deleted list (in this example "list 3")
[self setNavigationControllerViewControllers];
// .....
}
- (IBAction)unwindFromEditListToList:(UIStoryboardSegue *)segue {
.....
}
- (void)setNavigationControllerViewControllers {
NSMutableArray *viewControllers = [[self.navigationController viewControllers] mutableCopy];
[viewControllers removeLastObject];
[self.navigationController setViewControllers:viewControllers];
}
After that code is executed, viewWillAppear for the main list UITableViewController is executed as shown below:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// ..... update the data
[self.tableView reloadData];
}
[self.tableView reloadData] is going to cause the following code to be executed:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// .....
return someValue; // Something greater than zero
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
// .....
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// .....
return someValue; //Something greater than zero
}
#pragma warning "Not getting called"
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// .....
}
All the methods are executed but cellForRowAtIndexPath is not. The problem is gone if setNavigationControllerViewControllers mentioned above is not called. Not calling setNavigationControllerViewControllers will result in UITableViewController for "list 3" to show which is bad since "list 3" is no longer stored in the core data I'm using to store the data.
If you have any idea what to do, please let me know.
Thanks
numberOfSectionsInTableView must return at least 1.
See this link below.
You don't even need to override it if you're not using multiple sections.
If the height of table is dynamic. Make sure the table is in a view, visible and height is
atleast 1px when reload is called.
I put an answer on this because I've been browsing for a solution for sometime and this question is the closest to the one I was asking myself...
It turns out that I was using auto-layout with visual format.
Seems that UITableView and NSLayoutContraint don't play well together. As soon as I commented the method which apply constraints, the cellForRowAtIndexPath was called and the tableview displayed...
Please put debug point on this,
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
method and log someValue value may it return 0 that's why cellForRowAtIndexPath method not called.
Thanks.
Don't know who down voted Avalerion answer, but his statement is important too.
I had same problem, delegate method cellForRowAtIndexPath was not called though the following conditions were matched:
Delegate and Datasource was set
numberOfSectionsInTableView method was returning 1
numberOfRowsInSection returned correct number and data was available.
Reason:
I didn't set height constraint for the table, so it had no height, thus app didn't call cellForRowAtIndexPath even though it called eg numberOfRowsInSection.
The first time the view controller is pushed (from the previous view controller) all the delegate methods are called (inside a navigation controller).
When pressing back to return to the previous view controller , and then pushing it again (for the second time)
cellForRowAtIndexPath isn't called but numberOfRowsInSection and numberOfSectionsInTableView are called.
The reloadData is called within
-(void)viewWillAppear:(BOOL)animated
{
[self.tableView reloadData];
}
and I have tried in
-(void)viewDidAppear:(BOOL)animated
{
[self.tableView reloadData];
}
and it doesn't help.
Edit
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 1; // called
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 3; // called
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// relevant code for cell - THIS METHOD IS NOT CALLED SECOND TIME
}
If you don't find any valid reason as explained above I will highlight
another mistake one can make is (as I once did).
The steps to note are
1) initialise your custom table view as first step
2) set the delegate and datasource before you add the tableView to you view.
If one has done these steps mistakenly after adding to the tableview to your self.view object, then it would be a silent way to make yourself struggle.Correct order is as under:
self.myTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, myViewwidth, 120) style:UITableViewStylePlain];
self.myTableView.tag = MY_TABLEVIEW_TAG;
self.myTableView.delegate = self;
self.myTableView.dataSource = self;
[self.view addSubview:self.myTableView];
This always happens when you define your underlying data source (array or Dictionary) as weak, the first time it gets pushed, the data is there, when deallocated, it will release and you lose control over it.
Double check the weak/strong condition and optionally set the data source before pushing again.
Please check if you have set/connected Delegate and Datasource to the File's Owner.
And check the array count of your model, if it contains value of not?
NSLog in the numberOfRowsInSection method and check it by using breakpoints and step over.
I have a custom view EMViewController with a tableview as a subview. I want EMViewController to double as the controller for the subview, so I have it set as the delegate and dataSource.
// EMViewController.h
#interface EMViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
...
// EMViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.eTableView.delegate = self;
self.eTableView.dataSource = self;
}
I'm populating the data with an asynchronous array/dictionary from Facebook, so once I have the data, I run
[self.eTableView reloadData]
For the UITableViewDataSource protocol methods, I implemented the following:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSLog(#"returns sections 0"); // This appears in my log
return 0;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"returning count %d", [self.eList count]); // never shows up
return [self.eList count];
}
- (EMTableViewCell *)tableView:(EMTableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// configure the cell
NSLog(#"index: %d", indexPath); // never shows up
...
}
So far, I have already tried:
making sure my storyboard is connected up
checking/rechecking the delegate/dataSource being set in viewDidLoad
completely restarting/clean-building my app just in case
creating a placeholder UIView subclass (although I'm not changing much)
creating a customUITableViewCell class with outlets for my custom layout (images, 3 labels)
testing out strong/weak references to the tableView outlet
Any advice would be appreciated. I think my problem is similar to this question, except I'm trying to add the tableview as a subview. My end goal is to populate the cells with a custom format that includes an image and three labels. Thanks!
Return 1 section instead of returning 0 sections
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}