I can't load more than 40 UITableViewCells in my UITableView - ios

I am parsing XML that gets the first 25 items in my MySQL database using PHP - LIMIT and GET. When I click on the "Load More" cell that I append to the bottom of my tableview, it successfully gets the next 25 items, but only loads the first 40 and leaves off the last 10. Each time I click on the "Load more" cell it add 25 to my range (ie 0-25,25-50), but it seems that my range caps at 65 and the display caps at 40.
Here is my load more function thats not working:
-(void) getNewRange{
int currentRange = [allItems count];
int newRange = currentRange + 25;
if(newRange > [xmlParser total]){
NSLog(#"evaluating as greater than the total, which is 837");
newRange = [xmlParser total];
}
NSString *range = [[NSString alloc] initWithFormat:#"?range=%d&range2=%d",currentRange,newRange];
NSString *newUrl =[[NSString alloc] initWithFormat:#"http://localhost/fetchAllTitles.php%#",range];
XMLParser *tempParser = [[XMLParser alloc] loadXMLByURL:newUrl];
[allItems addObjectsFromArray:[tempParser people]];
NSMutableArray *newCells = [[NSMutableArray alloc] initWithCapacity:25];
for(int i=currentRange;i<newRange;i++){
NSLog(#"%d",i);
NSIndexPath *indexpath=[NSIndexPath indexPathForRow:i inSection:0];
[newCells addObject:indexpath];
}
NSLog(#"%#",newUrl);
[self.tableView insertRowsAtIndexPaths:newCells withRowAnimation:UITableViewRowAnimationAutomatic];
}
I'm getting closer, but I get this new error:
*** Assertion failure in -[_UITableViewUpdateSupport _computeRowUpdates], /SourceCache/UIKit_Sim/UIKit-1912.3/UITableViewSupport.m:386

Read up on how you can Reuse your table view's cells.
Your data does not need to be 'owned' by the cell.

UITableView isn't a class to contain your data, and you shouldn't try to directly micromanage what cells it displays. As another poster stated, read up on how to use it. What you should do is:
-(void)loadNewData
{
NSIndexPath *index;
XMLParser *tempParser = [[XMLParser alloc] loadXMLByURL:newUrl];
NSArray *people=[tempParser people];
for(id *person in people)
{
[self.dataArray addObject:person];
indexPath=[NSIndexPath indexPathForRow:[self.dataArray indexForObject:person] inSection:0];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [AnArray count];//use whatever array stores your data
}
//If you've subclassed the cell, adjust appropriately.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:#"cell"];
if (!cell) {
cell=[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
}
//Customize the cell
return cell;
}
The table view will take care of all the logic involved in displaying the cells, if you let it. This way, you only have a limited number of cells taking up memory at any given time, and you don't have to handle that -- the table view automagically handles reusing the cells, and knowing how many are needed as a buffer before / after.

you should not set numberOfRowsInSection inside your method. The number of rows should get returned from the tableView's datasource method - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section. Just return [allItems count] there.

Related

Obj-C- Add new row to TableView when cell is tapped?

I've got a tableview that allows users to add an item (a row) to an invoice (the tableview) when an existing row is tapped. That said, I can't seem to add an empty row because my code is trying to set the information in the cell with data from my specified array, but naturally, the count in the array is different from my data source (as I want the count to be +1).
E.g. I want to return 3 cells even if there are only 2 dictionaries in my array, and the third cell should be empty.
I want this because the third cell allows my user to fill out empty fields, while the fields in the previous two rows are populated with their already input data. Here's how I'm trying to return the extra row right now, but as mentioned above, it crashes my app due to the imbalance of dictionaries returned in my array.
Here's my code so far:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.allItems = [[NSMutableArray alloc] init];
self.itemDetails = [[NSMutableDictionary alloc] init];
}
//TableView delegates
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.allItems.count + 1;
}
-(UITableViewCell *)tableView:(UITableView*)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
static NSString *ClientTableIdentifier = #"InvoiceDetailsTableViewCell";
InvoiceDetailsTableViewCell *cell = (InvoiceDetailsTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:ClientTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"InvoiceDetailsTableViewCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
if (self.allItems.count == 0) {
} else {
cell.itemName.text = [self.allItems valueForKey:#"Item Name"][indexPath.row];
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
InvoiceDetailsTableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSString *itemTitle = cell.itemName.text;
NSString *itemDescrip = cell.itemDescrip.text;
NSString *itemCost = cell.itemCost.text;
NSString *itemTax = cell.itemTax.text;
[self.itemDetails setValue:itemTitle forKey:#"Item Name"];
[self.itemDetails setValue:itemDescrip forKey:#"Item Description"];
[self.itemDetails setValue:itemCost forKey:#"Item Cost"];
[self.itemDetails setValue:itemTax forKey:#"Item Tax Rate"];
[self.allItems addObject:self.itemDetails];
[self.tableView reloadData];
}
One significant problem is the line that says:
cell.itemName.text = [self.allItems valueForKey:#"Item Name"][indexPath.row];
Since your row count exceeds the number of items in your array, you will want to check the row number before accessing the array:
NSInteger row = indexPath.row;
if (row < self.allItems.count) {
cell.itemName.text = self.allItems[row][#"Item Name"]; // personally, I’d get row first, and then keyed value second
} else {
cell.itemName.text = #"";
}
You want to check to make sure that the current row is not the last (blank) row.

Problem with adding a new row and populating it

I am trying to add a new row to my custom cell with an NSMutablearray from another viewcontroller but I am getting an error when a new row is added. itemsTableView is visible in photoCaptureView which is the view for photoCaptureViewController. So when ScannerModalViewController (which is being called also in photoCaptureViewController) is called and capture the item/data and once it is dismissed scannedBarcode is called to add a new row to my custom cell and populate it I'm getting this error.
I am getting a warning and an error. The warning is
Warning once only: UITableView was told to layout its visible cells and other contents without
being in the view hierarchy (the table view or one of its superviews has not been added to a
window). This may cause bugs by forcing views inside the table view to load and perform layout
without accurate information (e.g. table view bounds, trait collection, layout margins, safe
area insets, etc), and will also cause unnecessary performance overhead due to extra layout
passes. Make a symbolic breakpoint at UITableViewAlertForLayoutOutsideViewHierarchy to catch
this in the debugger and see what caused this to occur, so you can avoid this action altogether
if possible, or defer it until the table view has been added to a window. Table view:
<UITableView: 0x1038a9c00; frame = (10 70; 398 794); clipsToBounds = YES; gestureRecognizers =
<NSArray: 0x28165f0c0>; layer = <CALayer: 0x2818ebb20>; contentOffset: {0, 0}; contentSize:
{398, 60}; adjustedContentInset: {0, 0, 0, 0}; dataSource: <PhotoCaptureViewController: 0x10364a880>>
error is
Invalid update: invalid number of rows in section 0. The number of rows contained in an existing
section after the update (3) must be equal to the number of rows contained in that section
before the update (1), plus or minus the number of rows inserted or deleted from that section
(1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section
(0 moved in, 0 moved out).
My code is photoCaptureViewController.h
#interface PhotoCaptureViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, ScannerModalViewControllerDelegate> {
PhotoCaptureView* photoCaptureView;
NSMutableArray* barcodeItems;
ScannerModalViewController* scannerModalViewController;
}
-(void) scanBarcode;
-(void) scannedBarcode:(NSString *) barcode;
#end
photoCaptureViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
[photoCaptureView.itemsTableView registerNib:[UINib nibWithNibName:#"BarcodeItemsTableViewCell" bundle:nil] forCellReuseIdentifier:#"BarcodeItemsCell"];
photoCaptureView.itemsTableView.rowHeight = 60;
photoCaptureView.itemsTableView.dataSource = self;
photoCaptureView.itemsTableView.delegate = self;
[photoCaptureView.itemsTableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
}
#pragma mark scannerModalViewController Methods
-(void) scanBarcode {
NSLog(#"[%#] Scan Barcode Requested", self.class);
scannerModalViewController = [[ScannerModalViewController alloc] init];
scannerModalViewController.delegate = self;
scannerModalViewController.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:scannerModalViewController animated:YES completion:nil];
}
-(void) scannedBarcode:(NSMutableArray *) barcodes {
barcodeItems = [[NSMutableArray alloc] init];
[barcodeItems addObjectsFromArray:barcodes];
[barcodeItems addObject:#"test"];
[barcodeItems addObject:#"test1"];
NSLog(#"%#", barcodes);
NSLog(#"%#", barcodeItems);
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:barcodeItems.count-1 inSection:0];
[photoCaptureView.itemsTableView beginUpdates];
[photoCaptureView.itemsTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[photoCaptureView.itemsTableView endUpdates];
}
#pragma mark UITableViewDataSource methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return barcodeItems.count + 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Cell Initialized");
static NSString *cellIdentifier = #"BarcodeItemsCell";
BarcodeItemsTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
if (cell == nil) {
cell = [[BarcodeItemsTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
// Configure the cell...
cell.barcodeLabel.text = [barcodeItems objectAtIndex:indexPath.row];
UIImage *btnImage = [UIImage imageNamed:#"barcodeIcon"];
[cell.leftButton setImage:btnImage forState:UIControlStateNormal];
NSLog(#"Cell Populated");
return cell;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
return true;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[barcodeItems removeObjectAtIndex:indexPath.row];
[photoCaptureView.itemsTableView beginUpdates];
[photoCaptureView.itemsTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[photoCaptureView.itemsTableView endUpdates];
}
}
#end
If you insert the items with insertRowsAtIndexPaths you have to specify all paths.
And beginUpdates /endUpdates` has no effect for a single insert/delete/move operation.
-(void) scannedBarcode:(NSMutableArray *) barcodes {
barcodeItems = [[NSMutableArray alloc] init];
[barcodeItems addObjectsFromArray:barcodes];
[barcodeItems addObject:#"test"];
[barcodeItems addObject:#"test1"];
NSLog(#"%#", barcodes);
NSLog(#"%#", barcodeItems);
NSMutableArray<NSIndexPath *>* indexPaths = [[NSMutableArray alloc] init];
for (i = 0; i < barcodeItems.count; i++) {
[indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
[photoCaptureView.itemsTableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
}
And numberOfRowsInSection must return
return barcodeItems.count;

How do I filter a collection with grand central dispatch and update a UITableView when done?

I'm trying to filter an NSArray using grand central dispatch. I'm able to filter the array and when I call [tableView reloadData] the correct values are being printed by NSLog; however the view shows the previous values.
For example, if my collection of items is Red, Orange, Yellow and I filter for r, the NSLogs will print that there are 2 rows and the cells are Red and Orange, but all three cells will be shown. When the search becomes ra, NSLog shows there is only 1 row called Orange, but the cells Red and Orange are shown;
- (void)filterItems:(NSString *)pattern{
__weak MYSearchViewController *weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSMutableArray *items = [weakSelf.items copy];
//lots of code to filter the items
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.items = [items copy];
[weakSelf.tableView reloadData];
});
});
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"Rows: %d",[self.items count]);
return [self.items count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"MYCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
NSInteger row = [indexPath row];
MYItem *item = [self.items objectAtIndex:row];
//code to setup cell
NSLog(#"Row %d, Item %#, Cell %d", row, item.info, cell.tag);
return cell;
}
Try this:
- (void)filterItems:(NSString *)pattern
{
NSMutableArray *array = [NSMutableArray arrayWithArray:items];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//lots of code to filter the items using "array", NOT items
dispatch_async(dispatch_get_main_queue(), ^{
items = array; // or [NSArray arrayWithArray:array] if you really don't want a mutable array
[tableView reloadData];
});
});
}
Comments: you don't need to use self. Yes, self will be retained while the block runs, but it will be released again when the block finishes. If this object can really go away while this runs, then OK, use a weak reference to self.
You used "items" as a name locally and in the block, I changed the local variable name to array just to be sure.

trying to get a 'Search Bar and Display Controller' to repopulate a table view

I am trying to get the 'Search Bar and Display Controller' functionality to work in an iOS app. I am able to NSLog in repsonse to a search query and hardcode in a new array but am unable to get the table view to repopulate. I have the following for in response to a user submit on the search button:
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar{
NSLog(#"You clicked the search bar");
NSMutableArray *filteredResults=[[NSMutableArray alloc] init];
Person *filteredPerson=[[Person alloc] init];
filteredPerson.firstName=#"Josie - here i am";
[filteredResults addObject:filteredPerson];
_objects=filteredResults;
self.tableView.dataSource = _objects;
[self.tableView reloadData];
}
Any ideas on making this repopulate would be appreciated.
thx
edit #1
It looks like this is populating the _objects NSMutableArray:
- (void)insertNewObject:(id)sender
{
if (!_objects) {
_objects = [[NSMutableArray alloc] init];
}
[_objects insertObject:[NSDate date] atIndex:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
Should I just create a new _objects and use insertNewObject rather than the addObject code I have above? Would this bypass the need to deal with the dataSource property of the table view?
edit 2
per #ian
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
/*
NSDate *object = [_objects objectAtIndex:indexPath.row];
cell.textLabel.text = [object description];
*/
Person *rowPerson = [_objects objectAtIndex:indexPath.row];
cell.textLabel.text = [rowPerson firstName];
return cell;
}
thx
I have a UITableView that uses an NSMutableArray to hold the data. Here's how it works: set the UISearchDisplayController's delegate to your TableView controller, and when the UITableViewDelegate methods are called (numberOfRows, numberOfSections, cellForRowAtIndexPath, etc) you can do the following to serve up the search data when appropriate:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSInteger numberOfRows = 0;
if (tableView == self.searchDisplayController.searchResultsTableView)
{
//This is your search table --- use the filteredListContent (this is the NSMutableArray)
numberOfRows = [filteredListContent count];
}
else
{
//Serve up data for your regular UITableView here.
}
return numberOfRows;
}
You should take a look at the UISearchDisplayDelegate documentation. You can use these methods to update your filteredListContent array, as follows:
#pragma mark -
#pragma mark Content Filtering
- (void)filterContentForSearchText:(NSString*)searchText
{
//In this method, you'll want to update your filteredListContent array using the string of text that the user has typed in. For example, you could do something like this (it all depends on how you're storing and retrieving the data):
NSPredicate *notePredicate = [NSPredicate predicateWithFormat:#"text contains[cd] %#", searchText];
self.filteredListContent = [[mainDataArray filteredArrayUsingPredicate:notePredicate] mutableCopy];
}
#pragma mark UISearchDisplayController Delegate Methods
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
[self filterContentForSearchText:searchString];
// Return YES to cause the search result table view to be reloaded.
return YES;
}
The line self.tableView.dataSource = _objects; is setting your array as the datasource for the UITableView. I assume that you don't have an NSArray subclass that implements the UITableViewDataSource protocol?
Try removing that line, and letting your existing datasource handler deal with the change in data.

Reload UITableView

I'm a new programmer in Objective-C and I have a terrible problem in my first application.
I have one class called Places (NSObject) where I found places in a Google places and return an NSArray.
I have another classe called DisplayPlaces (UiViewController). This class imports my "Place.h".
In my viewDidLoad I have this code:
Places *places = [[Places alloc]init];
[places.locationManager startUpdatingLocation];
[places LoadPlaces:places.locationManager.location];
[places.locationManager stopUpdatingLocation];
[places release];
In my method LoadPlaces I load JSON URL and put a result in NSDictionary after I get only places and put in NSArray and return.
Into my Places I alloc my DisplayPlaces and call a method ReturnPlaces: NSArray to return places that I found.
- (void)ReturnPlaces:(NSArray *)locais{
placesArray = locais;
[self.UITableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [placesArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIndentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIndentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIndentifier] autorelease];
}
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = [placesArray objectAtIndex:indexPath.row];
return cell;
}
It all works.
My problem is:
In my ReturnPlaces: NSArray I call [MyUiTableView reloadData] but I can't refresh my UiTableView.
What can I do?
Set your tableview yourTableView as your property and use
self.yourTableView = tableView; // for assigning the tableView contents to your property
if you are reloading inside any method for tableView, just use
[tableView reloadData];
if you are reloading outside your tableView methods, use
[self.yourTableView reloadData];
Are you calling reloadData on the instance of your tableView. In other words if you have
MyUITableView *mytableView;
then reloading it would require you call
[mytableView reloadData];
not
[MyUITableView reloadData];

Resources