I'm using a webservice to fill an array of items, which then are used to fill cells of my table view.
Now i'm having an issue with the table view itself. When I check the " numberofrows" method, the array isn't loaded yet. For some reason it loads "right after" that (according to my nslogs & breakpoints), even though i've put every loading method as early as i could in that controller.
Now what i don't know is :
is there a way to delay the table view creation so i'm sure my array is loaded?
is there a way to load my array earlier? It's currently my first line in the viewDidLoad. (calling another class, which then calls the webservice and returns an array, but by then, the table view is already loaded and empty).
What i've tried : Putting a tableview reloadData. But it simply doesn't work. For some reason the compiler reads the line but doesn't load anything new, even though at that point the array is full.
Am i missing something?
(My tableview works just fine if I put hardcoded objects in my array)
I can edit and add some code that you guys would request, but since this looks like a school problem here, maybe i just forgot something.
Guys, i'm all ears !
Edit :
My different methods ; i've removed unecessary code for clearer reading.
The compiler NEVER goes in the cellForRow because numberOfRows is returning a zero number.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"CustomCellTableViewCell";
CustomCellTableViewCell *cell = [self.tbUpcomingEvents dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
cell.lbTitle = [_eventList objectAtIndex:indexPath.row];
return cell;
}
number of rows ; the nslog returns zero.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"numOfRows : %i", [_eventList count]);
return [_eventList count];
}
my webservice method, this is the method calle from the "webservice class" to load the data.
Here, the NSLog shows a full array.
- (void)loadData:(NSMutableArray*)arrayEvent
{
//arrayEvent is full from the internet data. eventList is also full on the NSLog.
_eventList = arrayEvent;
NSLog(#"LoadData : %i", [_eventList count]);
[self.tbUpcomingEvents reloadData];
}
my viewdidLoad method
- (void)viewDidLoad
{
[super viewDidLoad];
//Calling my UseCaseController to load the data from the internet.
UCC *sharedUCC = [UCC sharedUCC];
[sharedUCC getUserEventsInDbDis:self :_user];
}
As you said you are using webservices. So it is executing your code in the block which means it is running in separate thread. Now as per apple documentation UI updates should happen in main thread. So for table creation you need to include the same in below GCD code:-
dispatch_async(dispatch_get_main_queue,()^{
// write your code for table creation here
});
Related
My problem's pretty simple.
I have a UITableViewController (well, I subclassed it, but that's not the issue), with a static layout, and it's large enough it doesn't fit on the screen all at one time.
I'm using viewWithTag to retrieve the values of a couple UISwitches, but they're just off the screen, so viewWithTag is infuriatingly returning nil.
Frankly, I neither know nor care about the memory overhead of leaving them in memory; it's not much memory to leave lying around, and I'm short on time.
How can I prevent scrolling from triggering deallocation?
EDIT: I know exactly what's wrong, as explained above, just not how to fix it (my usual google-fu came up dry). But since you asked to see the code...
int tag=200
int prefs = 0;
for (int i=0; i != 3; ++i) // There are only 3 preferences
{
prefs = prefs << 1;
UISwitch *swt = (UISwitch *)[self.view viewWithTag:tag + i];
NSLog(#"%#", swt);
if ([swt isOn])
++prefs;
NSLog(#"%d", prefs);
}
The above code works in viewDidAppear (because the switches are at the top of the table), but not once I have scrolled to the bottom of the table (viewWithTag returns null).
All objects of your cells are available and not destroyed whether on or off the screen. TableView simply reuses the cell.
Hence, you can get any cell's object by:
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:<requiredIndexPath>];
// Check the status of your cell's switch.
Loop through all the cells of the tableView and you will get it
Take a look at the documentation by Apple
If you are using UITableView, then this is absolutely not how it works.
For a UITableView, implement numberOfRowsInSection and cellForRowAtPathIndex, and when you want to change one of the cells, call reloadRowsAtIndexPath.
If you want to access the switch, you should access the object you pass to the data source. There you can access the switch value.
I think you are trying to make a setting in tableView. Here on what I usually do
// create an array to hold the setting data
NSArray *settingArray = #[#{#"title":#"Frequently Asked Questions"},#{#"title":#"Need Help?"},#{#"title":#"Push Notification",#"hasSwitch":[NSNumber numberWithBool: YES],#"switchValue":[[NSUserDefaults standardUserDefaults]boolForKey:#"kPushPreference"]}];
// data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.settingsArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
SettingCell *cell = [tableView dequeueReusableCellWithIdentifier:#"SettingCell" forIndexPath:indexPath];
NSDictionary *dictionary = self.settingsArray[indexPath.row];
[cell.settingName setText:dictionary[#"title"]];
if (dictionary[#"hasSwitch"]) {
[cell.settingSwitch setHidden:NO];
[cell.settingSwitch setOn:dictionary[#"switchValue"]];
}
return cell;
}
`
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.
Is it possible to 'append' more data to the datasource when a UITableView scrolls and asks for new cells? Similarly like Google image search shows 'infinitly' more images?
I could ask the controller to fetch more data from the network possibly through cellForRowAtIndexPath I figured.. But since tableView:numberOfRowsInSection: is essentialy a fixed number when the table loads or reloads it sounds like a catch22, the tableview wants to know how many rows it should have and you give it more and more. What is the approach and isn't this asking too much from the UITableView design?
This is a test from a future employer btw...
You should call reloadData (it is smart enough to re-query only visible cells) or reloadSections:withRowAnimation or more complex beginUpdates/insertRowsAtIndexPaths:withRowAnimation:/endUpdates to make UITableView update rows.
tableView:numberOfRowsInSection: does not have to a fixed number. You can do something like this:
NSMutableArray *data
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [data count];
}
So when you have new items to be displayed, first add it your data array and then call [tableView reloadData]. You could get the data asynchronously but when adding it to your array and reloading the tableview, I would suggest doing it on the main thread or you might observe some strange behaviour.
For infinite display of data I use this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//Your cell code
if (indexPath.row>[data count]-5) {
[self getNewData];
}
}
Your get new data function will be called when you are near the end of your tableview. There you can append your array in any way you want. Also make sure to handle it so that the function isn't called multiple times in one go.
The code below is creating a search for many strings. Initially there are 5 rows, when you reach row five, it adds another row. Instead of just directly editing the row, i load a filter controller (another view controller that as you type it completes words for you). When the user finishes finding a word he clicks it and comes back to this view controller. Now i want to fill the cell that was originally tapped with the text from the filter.
I tried asking earlier and didn't get any concrete answers.
I am running into a problem where when i scroll (after adding a new row), it starts filling in those rows with info already in the table, (as opposed to staying blank)
Please help me where i am going wrong
//global indexpath to remember which cell tapped
NSIndexPath *globalPath;
#interface SearchViewController ()
#end
#implementation SearchViewController
//Load implementation once per launch
- (void)viewDidLoad
{
[super viewDidLoad];
[self linkInputTableToDelegate];
_temporaryResultsArray =[[NSMutableArray alloc]init];
_flurryArray=[[NSMutableArray alloc]init];
_numberOfSections=6;
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:NO];
[InputTable reloadData];
textFromUserDefaults=[[[HelperMethods alloc]init]getObjectUserDefault:#"textFiltered"];
[self addTextToFlurryArrayForFlurryAndSavedLists:_textFromUserDefaults];
}
-(void)viewDidDisappear:(BOOL)animated{
}
- (IBAction)searchButtonPressed:(UIButton *)sender {
self.tabBarController.selectedIndex = 1;
}
//Makes the input table respond to delegate table view methods
-(void)linkInputTableToDelegate{
_inputTable.dataSource=self;
_inputTable.delegate=self;
}
-(void)performSearch:(NSString*)text{
//do search
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
int numberOfRows=_numberOfSections;
//Rows for iPhone 4
if ([[UIScreen mainScreen]bounds].size.height==480) {
numberOfRows=numberOfRows;
//Rows for iPhone 5
}else if ([[UIScreen mainScreen]bounds].size.height==568){
numberOfRows=numberOfRows+1;
}
return numberOfRows;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//In reality groups are created with 1 row inside, this is to allow spacing between the rows
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *kCellID = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellID];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellID];
}
//Is the cell the same as the one clicked when going to ingredient filter
BOOL cellIndexPathSameAsSelected=[self isCellIndexSameAsPreviousClicked:indexPath];
cell.textLabel.textColor=[UIColor blackColor];
if (cellIndexPathSameAsSelected && _textFromUserDefaults!=nil) {
if (![cell.textLabel.text isEqualToString:_textFromUserDefaults]) {
cell.textLabel.text=_textFromUserDefaults;
[self performTextSearch:_textFromUserDefaults];
}
}
return cell;
}
//Compares the previous clicked cell with the cell now selected
-(BOOL)isCellIndexSameAsPreviousClicked: (NSIndexPath*)cellPath{
if (cellPath.row == globalPath.row && globalPath.section==cellPath.section) {
return YES;
}
else{
return NO;
}
}
- (void)updateTableViewWithExtraRow :(NSIndexPath*)rowSelected{
NSLog(#"number of sections =%i",_numberOfSections);
if (rowSelected.section == _numberOfSections) {
_numberOfSections ++;
}
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellText = [tableView cellForRowAtIndexPath:indexPath].textLabel.text;
[[[HelperMethods alloc]init]saveObjectToUserDefaults:cellText :#"textFiltered"];
globalPath = indexPath;
[self updateTableViewWithExtraRow:indexPath];
}
-(void)addTextToFlurryArrayForFlurryAndSavedLists:(NSString*)text{
if ([_flurryArray count]==0 &&[text length]>0) {
[_flurryArray addObject:text];
}
for (int i=0;i<[_flurryArray count];i++) {
NSString *textInArray=[_flurryArray objectAtIndex:i];
if (![textInArray isEqualToString:text]) {
[_flurryArray addObject:text];
}
}
NSLog(#"Total number of saved items = %i",[_flurryArray count]);
}
// Dispose of any resources that can be recreated.
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
I have a couple of reactions looking at the code:
A couple of observations about the proper use of the UITableViewDataSource methods, specifically numberOfRowsInSection, numberOfSectionsInTableView, and cellForRowAtIndexPath:
These really should be driven by some model data structure (e.g. a NSMutableArray) and nothing else;
These methods should be stateless. They should not relying on the value of some NSString instance variable, like _textFromUserDefaults) but rather always look up the value in the NSMutableArray model structure on the basis of the value of the indexPath parameter. You simply cannot make any assumptions about when cellForRowAtIndexPath will be called. This may well account for your duplicate values.
None of these should be doing anything besides responding to the UITableView inquiry. For example, your cellForRowAtIndexPath is invoking performTextSearch. It really shouldn't do anything except return the cell.
Your cellForRowAtIndexPath currently has conditional logic and only updates the cell if certain conditions holds. Because cells are reused, you really want to make sure that you initialize the cells regardless. You can't be assured that the cell is blank when you get it, nor that the previous contents are the previous values for that indexPath. Because cells are reused, it could be for an entirely different row. This could also account for your duplicative entries.
Regarding the interaction of the master view controller and the details view controller, there are more elegant ways than passing data back and forth via NSUserDefaults. For example when you initiate the details view controller, you could just pass it the information it needs. And when it's done, it should call a method in the master view controller to update the data in the master view. To do that, the master view controller should conform to some protocol of your own creation. If you see the example I shared via chat, you can see what that might look like. Anyway, by having some delegate method in the master view controller that the detail view controller calls when it's done, that eliminates the rather fragile technique of using viewDidAppear to control the updating of the master table view.
You might want to contemplate employing "edit" (which allows you to delete, possibly also edit a particular row) and "add" buttons like the standard "master-detail" template that Xcode provides. There are a number of standard conventions here that might be better than having an array of blank cells that you can then tap on. Clearly, your user experience is entirely up to you, but you can always contemplate whether there are existing, familiar conventions that you might employ.
Rob's feedback is good. In broader terms, you can't rely on the cells in a UITableView to hold onto their data. For efficiency, it will be creating, using, and destroying cells at will, and using cellForRowAtIndexPath to figure out what they should look like. Instead of testing what's in a cell, you need to have your own set of data which describe the value of each cell, and just set the value based on the indexPath. I'd recommend storing all your cell information in an NSMutableArray which contains NSStrings or something more complicated if necessary. It will be easy to set default values when you add cells to the array. Then cellForRowAtIndexPath can just access the array rather than attempting its own logic based on current cells.
I want to do the following:
I have to create a UITableView at the runtime depending upon some conditions, at the run time only I will come to know from which database table the data has to be pulled from to be presented in the UITableView to make this problem more complicated I will have to create a custom UITableViewCell also at the run time.
I am not able to think how do I create this UITableView and then how do I create all those delegate method at the runtime.
I will give some more background that will help understand this problem, I am making a request to my server, and the server returns me an xml response object, after parsing I figure out that I have to present a table to the user on a particular action and the table will have custom cell, for which the values are available in the xml response object.
Please help I have been trying to figure out this thing for a while now.
EDIT:
I will try to explain in a different way, if that helps people understand my problem.
Hi, I want, to do the following:
On the runtime(which means while my app is running) I have to create a UITableView depending upon some conditions(some action taken by the user), I will make a server call and will get the data for the UITableView.
There has to be a a custom UITableViewCell for this UITableView that I have created at the run time, I will get the information for the custom UiTableViewCell also at the run time.
So basically I don't have any thing at the compile time except that I may have to create a UITableView and a custom UITableViewCell.
I am trying to figure out that how do I create the delegate method and custom UITableViewCell at the run time.
One thing that I thought was to have a default class with all the delegate method and when I create UITableView at the run time associate this class as the delegate for the newly created UITableView, let me know if this is an ok solution.
But how do I associate the custom UITableViewCell to this delegate method at the run time is still an issue.
You just need to get the new values that you would come to know during the run time and then use [tableViewObject reloadData];
The delegate functions will remain the same. In IB just place the UITableView wherever you want and set the delegate and datasource to the file owner. Set the hidden property to yes by checking the check box.
Once the user does some action change the hidden property in the action function as tableViewObject,hidden = NO;
Your delegate methods will look like
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [tableData count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath (NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"acell"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:#"acell"] autorelease];
}
cell.textLabel.text = [tableData objectAtIndex:indexPath.row];
return cell;
}
In this tableData will be your datasource that could be declared in your .h file. In the function that captures the action of the user, you can get the data from the server and add it to the tableData and then as suggested earlier call [tableViewObject reloadData];
After you are done parsing the reply from the server you should call
[tableView reloadData];
And in the method [tableView:cellForRowAtIndexPath:]
Depending upon some values or identifiers that you have for each database load values from that particular database and render it into tableView cell.