I have an UITableview with custom UITableviewCell that has 2 Text Fields.
I have a button that adds new cells to this table. Initially i have 3 cells then I add and I add and when i add the sixth (6th) cell it crashes saying the cell is empty when i iterate over it like this:
NSMutableArray *cells = [[NSMutableArray alloc] init];
for (NSInteger j = 0; j < [shtoFushaTableView numberOfSections]; ++j)
{
for (NSInteger i = 0; i < [shtoFushaTableView numberOfRowsInSection:j]; ++i)
{
NSLog(#"%#", [shtoFushaTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:j]]);
[cells addObject:[shtoFushaTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:j]]]; //this is where i get an exception to add a null object
}
}
The point of this iteration is to store the values of the text fields before adding a new one.
Any Idea what im doing wrong ?
The problem is probably the re-use of table view cells:
When they are scrolled out of view, they are put back in a cell pool, and all data in the cells might disappear.
For this reason you should never store anything in a table view cell except for display.
You have to store your data in a data model, and read them from there for display in the cells.
Related
Then I get json update I reload my tableView, but I have tableView with collectionView inside it and I have little lag in visible cell at the moment of reload. I want to reload only invisible cell of tableView. But there is no 'visibleSections' method like 'visibleCells'.
I get all visible sections
NSArray *visibleSections = [[self.tableView indexPathsForVisibleRows]valueForKey:#"section"];
Not I want get all sections, then remove all visibleSections and reload this new array. But I can not find the way to get all sections. How can I do it?
You could use one of these methods:
- (void)reloadRowsAtIndexPaths:withRowAnimation:
- (void)reloadSections:withRowAnimation:
Edit
When you try to get the indexPaths of all invisible cells, you could try something like this:
NSMutableArray *invisibleCells = [NSMutableArray array];
for (int i = 0; i < [tabsTableView numberOfSections]; i++) {
for (int n = 0; n < [tabsTableView numberOfRowsInSection:i]; n++) {
for (NSIndexPath *indexPath in [tabsTableView indexPathsForVisibleRows]) {
if ([indexPath section] == i && [indexPath row] == n) {
goto nextIteration;
}
}
[invisibleCells addObject:[NSIndexPath indexPathForRow:n inSection:i]];
nextIteration:;
}
}
More important is why you would like to do that?! Due UITableViewCells get reused, they won't be updatable. When the cell is "invisible", it changes its content and is used at a different position within the visible cells. This is how UITableView works.
You will (also currently) use this method to set the content of the cell:
- (void)tableView:tableView cellForRowAtIndexPath:indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:_cellID];
if (!cell) {
cell = (UITableViewCell *)[[[NSBundle mainBundle] loadNibNamed:#"cellTemplate" owner:nil options:nil] objectAtIndex:0];
}
// configure cell content here
}
When the cell get's reused, this method gets called and you create the cell only if UITableView does not reuse an invisible cell. If it does, you change the content.
So there is no need to reload invisible cells and it's impossible. From the docs:
Return Value
An object representing a cell of the table or nil if the cell is not visible or indexPath is out of range.
I am trying to trigger some code based on if the cell I selected becomes the first or last cell on the users screen. I'm not trying to capture the index value of the data array. Just the index value of the cell that is visible on the screen. I'm sure this to create an array of visible cells.
NSArray *indexPathsForVisibleRows = [myTableView
indexPathsForVisibleRows];
But I keep hitting a dead end trying to then capture an index value based on that array.
I tried to use a CGPoint and convert that, but I keep getting an error. Any insight would be most helpful!
As per the documentation for the return value of that method:
An array of NSIndexPath objects each representing a row
index and section index that together identify a visible row in the
table view. Returns nil if no rows are visible.
The array returned from that method contains NSIndexPaths.
NSArray *indexPathsForVisibleRows = [myTableView indexPathsForVisibleRows];
for(NSIndexPath *eachIndexPath in indexPathsForVisibleRows)
{
NSInteger row = eachIndexPath.row;
NSInteger section = eachIndexPath.section;
UITableViewCell *cell = [myTableView cellForRowAtIndexPath:eachIndexPath];
if(cell.isSelected)
{
// this is our selected cell
}
}
I have been wondering why my code works well with cellForItemAtIndexPath: & not with dequeueReusableCellWithReuseIdentifier: while fetching collection view cells.
Here is my code:
This one works Fine:
NSInteger numberOfCells = [self.collectionView numberOfItemsInSection:0];
for (NSInteger i = 0; i < numberOfCells; i++) {
myCustomCollectionCell *cell = (myCustomCollectionCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
//here I use the cell..
}
While this compiles well, but not working (the changes I perform on cell is not depicted)
NSInteger numberOfCells = [self.collectionView numberOfItemsInSection:0];
for (NSInteger i = 0; i < numberOfCells; i++) {
myCustomCollectionCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:#"myCell"forIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
//here I use the cell..
}
Tried this too, but no use:
NSInteger numberOfCells = [self.collectionView numberOfItemsInSection:0];
for (NSInteger i = 0; i < numberOfCells; i++) {
myCustomCollectionCell *cell = (myCustomCollectionCell *)[self.collectionView dequeueReusableCellWithReuseIdentifier:#"myCell"forIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
//here I use the cell..
}
Any ideas?
These two are basically two very different methods.
dequeReusableCellWithReuseIdentifier:
Suppose that you have a list of articles to view. Say you have 50 articles. The screen won't show you all 50 articles on screen at once. It will show you limited cells at once, based on the height you given to the rows. Lets say the screen shows only 5 articles at once and now you are at the top of the list. The list will show items 1-5. Now when you scroll, inorder to display the 6th item, the list reuses your 1st cell, configures it for the 6th one and display it. By this time your 1st cell is out of the view.
2.cellForRowAtIndexPath :
On the other hand cellForRowAtIndexPath returns you the cell which is already in the view or from IndexPath you provide. In this case if the cell is already in the memory, it will just return that or it will configure a new cell and return.
The example below is for UITableViews, but UICollectionViews can be handled in the same way.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
/*
* This is an important bit, it asks the table view if it has any available cells
* already created which it is not using (if they are offscreen), so that it can
* reuse them (saving the time of alloc/init/load from xib a new cell ).
* The identifier is there to differentiate between different types of cells
* (you can display different types of cells in the same table view)
*/
UITableViewCell *cell = [tableView dequeueReusableCellWithReuseIdentifier:#"MyIdentifier"];
/*
* If the cell is nil it means no cell was available for reuse and that we should
* create a new one.
*/
if (cell == nil) {
/*
* Actually create a new cell (with an identifier so that it can be dequeued).
*/
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"MyIdentifier"] autorelease];
}
/*
* Now that we have a cell we can configure it to display the data corresponding to
* this row/section
*/
//Configure the cell here..
/* Now that the cell is configured we return it to the table view so that it can display it */
return cell;
}
Let me know if you were still unclear.
They are different things:
cellForItemAtIndexPath gets an already-populated cell from the collection view.
dequeueReusableCellWithReuseIdentifier will possibly return a cell that can be re-used, when populating a new cell. It's designed to help reduce the number of cell objects in existence. If it fails (and it often will) then a new cell must be created explicitly.
I have a UITextView populated with data from a UITableView (tvServices); when the user taps on a row in the UITableView, I move the contents of that cell to the UITextView, after which the contents of the UITextView are stored in a CoreData store.
When the user selects a record for updating, I move the contents of the stored UITextView back into the UITextView. When the user taps on the UITextView, I display the entire UITableView as a UIPopover, with the contents of the UITextView marked with a checkmark (AccessoryCheck) in the UITableView.
Unfortunately, this is not working the way I designed it... nothing gets checked. Here is my code ( this is a proof of concept - where I check every row in the UITableView to make sure it can be done). globalServicesArray is the data source for the UITableView cells:
for (int i = 0; i < sharedServicesArray.globalServicesArray.count; i++) {
NSIndexPath *path = [NSIndexPath indexPathForRow:i inSection:0];
UITableViewCell *cell = [tvServices cellForRowAtIndexPath:path];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
I hope this makes sense; if there is a better way of doing this, I'm open for suggestions... otherwise what am I doing wrong?
Assuming only one cell can be edited, can you try this. Store the index path in a class level iVar/Property, say. selectedIndex and reload the cell
//To hold the index paths
NSMutableArray *reloadArray = [NSMutableArray array];
if (self.selectedIndex)
{
//if already selected deselect
[reloadArray addObject:self.selectedIndex];
}
for (int i = 0; i < sharedServicesArray.globalServicesArray.count; i++) {
NSIndexPath *path = [NSIndexPath indexPathForRow:i inSection:0];
[reloadArray addObject:path];
}
[tvServices reloadRowsAtIndexPaths:reloadArray withRowAnimation:UITableViewRowAnimationNone];
In cell for row at index path check for the index path and set accessory checked if matches the index
if (self.selectedIndex &&
self.selectedIndex.row == indexPath.row &&
self.selectedIndex.section == indexPath.section)
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else
{
cell.accessoryType = UITableViewCellAccessoryNone;
}
I have a UICollectionView that is backed by a web service. I initially load 100 items from the service into the collection view. When the user reaches the bottom of the view, I dyanmically load the next "page" of content.
This is working, but my collectionView flashes and re-draws the entire view from the top. I am trying to get it to just begin drawing the new content at the bottom of the view.
Code for adding new content to the collectionview:
- (void)insertIntoCollectionView: (int) itemCnt {
// only do the batchupdate for NON-initial load
if(curPage != 0) {
// Add items to main photos array, and update collectionview using batchUpdate
[self.collectionView performBatchUpdates:^{
// Get starting size before update
int resultsSize = itemCnt;
// Create a new array to hold the indexpath for the inserted data
NSMutableArray *arrayWithIndexPaths = [NSMutableArray array];
// Add indexpath objects to the new array to be added to the collection view
for (int i = resultsSize; i < resultsSize + itemCnt; i++) {
[arrayWithIndexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
// Update the collectionview
[self.collectionView insertItemsAtIndexPaths:arrayWithIndexPaths];
}
completion:nil];
} else {
[self.collectionView reloadData];
}
curPage++;
Anything obvious? It functionally works, but looks pretty bad with all of the flashing and re-drawing.
OK, managed to fix it myself. I was using the incorrect values for the resultSize variable. The for loop should start at the end of the current array count, and loop through the number of item that we added, thus adding them at the end. I was starting at the beginning of the array index 1 when building the arrayWithIndexPaths, causing it to re-draw the whole view.