Collection view crash on reloadItemsAtIndexPaths - ios

I have an uitableview with a collection view nested inside. Both the Collectionview and the Tableview have just 1 section. In the collectionview cells there are uiimageviews. I'm downloading pictures from a server, and when the picture is downloaded, I'd like to display them in the image view. I'm calling reloadItemsAtIndexPaths to reload the content of the cell to display the picture. This is what i found out so far:
if i'm using
[NSIndexPath indexPathWithIndex:[secondArray indexOfObject:dictionaryToRefresh]]
as the index to refresh in reloadItemsAtIndexPaths, then the first picture loads just fine in the first cell of the collection view. The reloadItemsAtIndexPaths does a nice fade-in effect too. Then it crashes badly with the error:
Assertion failure in -[UICollectionView
_endItemAnimationsWithInvalidationContext:tentativelyForReordering:], /SourceCache/UIKit/UIKit-3347.44/UICollectionView.m:3835 2015-05-03
18:47:02.385 FBomb[3117:798045] *** Terminating app due to
uncaught exception 'NSInternalInconsistencyException', reason:
'attempt to delete section 1, but there are only 1 sections before the
update'
if I use
[NSIndexPath indexPathForItem:[secondArray indexOfObject:dictionaryToRefresh] inSection:0];
All the pictures load just fine! But there is no fade-in effect, which is what i'm trying to achieve. Any ideas as to I can overcome this issue? I'd like to display the pictures with the fade in effect (I really can't understand why it doesn't happens)
Thanks in advance and pardon typos!
EDIT1
Full code:
-(void) reloadItemWithDictionary:(NSDictionary*)dictionaryToRefresh{
BOOL dictionaryFound = FALSE;
NSIndexPath *= [[NSIndexPath alloc]init];
NSIndexPath *indexPathSecondArray = [[NSIndexPath alloc] initWithIndex:0];
for (NSDictionary *firstDictionary in megaArray)
{
NSArray *secondArray = [firstDictionary objectForKey:#"subArray"];
if ([secondArray containsObject:dictionaryToRefresh]){
indexPathFirstArray = [NSIndexPath indexPathForRow:[megaArray firstDictionary] inSection:0];
//THIS FADES THE FIRST ITEM ONLY, THEN CREASHES
//indexPathSecondArray = [NSIndexPath indexPathWithIndex:[secondoArray indexOfObject:dictionaryToRefresh]];
//THESE WORKS, BUT THEY DON'T FADE
indexPathSecondArray = [NSIndexPath indexPathForItem:[secondoArray indexOfObject:dictionaryToRefresh] inSection:0];
//indexPathSecondArray = [NSIndexPath indexPathForRow:[secondoArray indexOfObject:dictionaryToRefresh] inSection:0];
dictionaryFound = TRUE;
break;
}
}
if ( dictionaryFound == TRUE){
dispatch_sync(dispatch_get_main_queue(), ^{
UITableViewCell *tableCell = [tableWithCollectionView cellForRowAtIndexPath:indexPathFirstArray];
UICollectionView *collectionViewInTableCell = (UICollectionView *)[tableCell viewWithTag:1000];
[collectionViewInTableCell performBatchUpdates:^{
[collectionView reloadItemsAtIndexPaths:#[indexPathSecondArray]];
} completion:^(BOOL finished){
}];
});
}
}

What happens when you try this instead:
NSUInteger x[] = {[secondArray indexOfObject:dictionaryToRefresh] , 0};
//I don't know if the first element is section or item. try both.
//NSUInteger x[] = {0, [secondArray indexOfObject:dictionaryToRefresh]};
NSIndexPath *path = [[NSIndexPath alloc] initWithIndexes: x length: 2];
[collectionView reloadItemsAtIndexPaths:[NSArray arrayWithObject:path]];
indexPathWithIndex creates an instance by just using one NSInteger whereas UITableView and UICollectionView requires two values for item/row and section

Related

UICollectionView : Offscreen auto scroll for paging

I am trying to scroll UICollectionView which is offscreen in my app, by below code.
int pages = ceil(aCollectionView.contentSize.height / aCollectionView.frame.size.height);
for (int i = 0; i < pages; i ++)
{
NSArray *sortedVisibleItems = [[aCollectionView indexPathsForVisibleItems] sortedArrayUsingSelector:#selector(compare:)];
NSIndexPath *lastItem = [sortedVisibleItems lastObject];
// 2.next position
NSInteger nextItem = lastItem.item + 1;
NSInteger nextSection = lastItem.section;
NSIndexPath *nextIndexPath = [NSIndexPath indexPathForItem:nextItem inSection:nextSection];
[self takeImage];
dispatch_async(dispatch_get_main_queue(), ^
{
[aCollectionView scrollToItemAtIndexPath:nextIndexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:NO];
});
}
And taking screen shots of each page for printing purpose.
But its not scrolling and always prints the 1st page multiple times.
UICollectionView's property
Am I missing or doing in wrong direction ?
You are always getting last object by NSIndexPath *lastItem = [sortedVisibleItems lastObject]; to take a image. This will only capture same page always.
This is because you are not removing lastObject from your array.
Remove your lastObject by using
[sortedVisibleItems removeLastObject];
int pages = ceil(aCollectionView.contentSize.height / aCollectionView.frame.size.height);
NSArray *visibleItems = [aCollectionView indexPathsForVisibleItems];
NSInteger row = 0;
NSIndexPath *currentItem;
for (NSIndexPath *indexPath in visibleItems) {
if (row < indexPath.row){
row = indexPath.row;
currentItem = indexPath;
}
}
NSLog(#"current indexpath ; %ld",(long)currentItem.row);
if (currentItem.row == pages-1) {
return;
}
NSIndexPath *nextItem = [NSIndexPath indexPathForItem:currentItem.item + 1 inSection:currentItem.section];
[aCollectionView scrollToItemAtIndexPath:nextItem atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:YES];
Try this
A collection view only updates on the main thread. Your code is wrapped in a loop, which never allows the main thread to run and update.
There are lots of discussions about this out there. Many related directly to doing the same thing with UIScrollView, but it's the same issue.
You might want to look at this... not sure if it will fit your needs, but I've seen it referenced multiple times: https://github.com/sgr-ksmt/PDFGenerator
If that doesn't work for you, it probably has the technique you need in it, so a little investigating of that code should find your answer.

UICollectionView: removing cell issue

I am trying to remove a cell from my collectionView, however. When I remove the object from my datasource and attempt a batchupdate, it says the cell doesn't exist anymore.:
'NSInternalInconsistencyException', reason: 'attempt to delete item 0 from section 0 which only contains 0 items before the update'
Before this code I remove the content from my core data, and the line [[usermanager getSelectedUser]loadCards]; actually reloads the datasource containing the content for the cells by getting them from the Core Data.
- (void)cardRemoved:(NSNotification *)note {
NSDictionary *args = [note userInfo];
Card *card = [args objectForKey:#"card"];
[[usermanager getSelectedUser]loadCards];
[self.collectionView performBatchUpdates:^{
NSIndexPath *indexPath =[NSIndexPath indexPathForRow:card.position.intValue inSection:0];
[self.collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
} completion:^(BOOL finished){
[self.collectionView setDelegate:self];
[self.collectionView setDataSource:self];
[self.collectionView reloadData];
}];
}
If I print out the amount of Cells before I call the loadCards line, I get the correct amount of rows(As expected).
EDIT
This is what loadCards calls:
-(NSMutableArray *)getCards{
UserModel *selectedUser = [self getSelectedUserFromDB];
NSMutableArray *cards = [[NSMutableArray alloc] init];
for(CardModel *cardModel in selectedUser.cards){
[cards addObject:[self modelToCard:cardModel]];
}
return cards;
}
I noticed, even if I don't call the loadCards method, It says there are no items in the view.
Can anyone help me out? Thank you
Remove the cells from the UICollectionView, then remove them from the model. The same thing applies to UITableView. You also don't need to reload the collection view after removing items.
If you prefer you can just remove items from the model and then reload the collection view and the items that aren't in the model will disappear from the collection view, but without the same animation that comes from removing items from a collection view.

Select Saved Checkmarked Rows on viewdidload

I have a settings page with a long list with checkmark accesories. Users can select as many rows as they want and I have them saving in NSUserDefaults. I want their previous selections to be selected next time they open settings so I've tried this:
NSArray *array = [NSMutableArray arrayWithArray:[[NSUserDefaults standardUserDefaults] objectForKey:#"choices"]];
if (array.count !=0) {
NSLog(#"not empt");
for (id obj in array) {
NSIndexPath *path = [NSIndexPath indexPathWithIndex:obj];
[self.tableView selectRowAtIndexPath:path animated:NO scrollPosition:UITableViewScrollPositionTop];
}
}
But the app crashes with this error every time:
erminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid index path for use with UITableView. Index paths passed to table view must contain exactly two indices specifying the section and row. Please use the category on NSIndexPath in UITableView.h if possible.
What am I doing wrong? Thanks
The error is quite explanatory. NSIndexPaths in UIKit require two indexes one for the section and one for the row. It's explained here
Assuming you only have one section in your table view:
for (id obj in array) {
NSIndexPath *path = [NSIndexPath indexPathForRow:obj inSection:0];
[self.tableView selectRowAtIndexPath:path animated:NO scrollPosition:UITableViewScrollPositionTop];
}

insertRowsAtIndexPaths: with scrollToRowAtIndexPath: causes UITableView sections to be incorrectly hidden

I've created a UITableview with sections that are clickable. When you click on them,
they "expand" to reveal cells within them
the clicked section scrolls to the top of the view.
I calculate all of the indexpaths to insert/delete the necessary cells and then insert them with the following code:
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:pathsToOpen withRowAnimation:insertAnimation];
[self.tableView deleteRowsAtIndexPaths:pathsToClose withRowAnimation:deleteAnimation];
[self.tableView endUpdates];
[self.tableView scrollToRowAtIndexPath:[pathsToOpen objectAtIndex:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
There's only one problem- the sections below the selected section are hidden. The first screen-shot shows how the tableview should look. The second screen-shot shows how it actually looks.
If you scroll up (so the hidden sections are offscreen) and then scroll back down, the hidden sections are brought back (once again visible). My guess as to why this is happening is the following:
The insert/delete animations are happening at the same time as the scrollToRowAtIndexPath and it is confusing the TableView. If I hadn't done scrollToRowAtIndexPath sections 3 & 4 would have been offscreen - and so the tableView somehow still thinks they are offscreen. UITableview hides cells/sections that are offscreen as an optimization. If I call scrollToRowAtIndexPath with a dispatch_after with 2 seconds, then sections 3 & 4 are displayed correctly.
So I think I know why this is happening, but I don't know how to fix/override this UITableview optimization. Actually, if I implement scrollViewDidEndScrollingAnimation and then add a breakpoint in this function, the app displays sections 3 & 4 correctly (that's how I got the first screen-shot). But once continuing from this function, the cells disappear.
The full project can be downloaded here
Additional implementation details: Sections are legitimate UITableView sections. I've added a tapGestureRecognizer that triggers a delegate callback to the tableview. Included below is the entire method that opens the sections.
- (void)sectionHeaderView:(SectionHeaderView *)sectionHeaderView sectionOpened:(NSInteger)sectionOpened
{
// Open
sectionHeaderView.numRows = DefaultNumRows;
sectionHeaderView.selected = YES;
NSMutableArray *pathsToOpen = [[NSMutableArray alloc] init];
for (int i = 0; i < sectionHeaderView.numRows; i++)
{
NSIndexPath *pathToOpen = [NSIndexPath indexPathForRow:i inSection:sectionOpened];
[pathsToOpen addObject:pathToOpen];
}
// Close
NSMutableArray *pathsToClose = [[NSMutableArray alloc] init];
if (openSectionHeader)
{
for (int i = 0; i < openSectionHeader.numRows; i++)
{
NSIndexPath *pathToClose = [NSIndexPath indexPathForRow:i inSection:openSectionHeader.section];
[pathsToClose addObject:pathToClose];
}
}
// Set Correct Animation if section's already open
UITableViewRowAnimation insertAnimation = UITableViewRowAnimationBottom;
UITableViewRowAnimation deleteAnimation = UITableViewRowAnimationTop;
if (!openSectionHeader || sectionOpened < openSectionHeader.section)
{
insertAnimation = UITableViewRowAnimationTop;
deleteAnimation = UITableViewRowAnimationBottom;
}
openSectionHeader.numRows = 0;
openSectionHeader.selected = NO;
openSectionHeader = sectionHeaderView;
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:pathsToOpen withRowAnimation:insertAnimation];
[self.tableView deleteRowsAtIndexPaths:pathsToClose withRowAnimation:deleteAnimation];
[self.tableView endUpdates];
[self.tableView scrollToRowAtIndexPath:[pathsToOpen objectAtIndex:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
From what I can tell, the problem is occurring when returning a section view that's already been used. Instead of:
- (UIView *)tableView:(UITableView*)tableView viewForHeaderInSection:(NSInteger)section
{
return [self.sectionHeaderViews objectAtIndex:section];
}
I get no problem if I create a new view each time:
- (UIView *)tableView:(UITableView*)tableView viewForHeaderInSection:(NSInteger)section{
SectionHeaderView *sectionHeaderView = [self.tableView dequeueReusableCellWithIdentifier:SectionHeaderView_NibName];
sectionHeaderView.textLabel.text = [NSString stringWithFormat:#"Section %d", section];
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:sectionHeaderView action:#selector(handleTap:)];
[sectionHeaderView addGestureRecognizer:tapRecognizer];
sectionHeaderView.section = section;
sectionHeaderView.delegate = self;
return sectionHeaderView;
}
It's possible this is occurring because you're using [self.tableView dequeueReusableCellWithIdentifier:SectionHeaderView_NibName]; to create section headers and hold on to them in an array, which I don't think UITableViewCell was created for, but I'm not certain. You may want to consider foregoing UITableViewCell for section views and instead use something else (perhaps a UIImageView with a UILabel). Or you can just not store the Section Views in an array...the way you currently have your code set up, you don't need the array and creating a new view is trivial enough you don't need to worry about it.
#AaronHayman's answer works (and IMO the accept and bounty should go to him, as it stands - this just didn't fit in a comment!), but I would go further - you shouldn't be using a cell at all for section header, and you shouldn't be using the dequeue mechanism to essentially load a nib.
Section header view's aren't supposed to be cells, and you may get unforseen effects by using them in place of regular views, particularly if they are deqeueued - the table is keeping a list of these reusable cells when you do that, and recycles them when they go off screen, but your section headers aren't reusable, you have one per section.
In your sample project, I changed the superclass of SectionHeaderView to be a plain UIView, and changed your createSectionHeaderViews method to load directly from the nibs there:
NSMutableArray *sectionHeaderViews = [[NSMutableArray alloc] init];
UINib *headerNib = [UINib nibWithNibName:SectionHeaderView_NibName bundle:nil];
for (int i = 0; i < 5; i++)
{
SectionHeaderView *sectionHeaderView = [[headerNib instantiateWithOwner:nil options:nil] objectAtIndex:0];
sectionHeaderView.textLabel.text = [NSString stringWithFormat:#"Section %d", i];
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:sectionHeaderView action:#selector(handleTap:)];
[sectionHeaderView addGestureRecognizer:tapRecognizer];
sectionHeaderView.section = i;
sectionHeaderView.delegate = self;
[sectionHeaderViews addObject:sectionHeaderView];
}
self.sectionHeaderViews = sectionHeaderViews;
I also commented out the register for reuse line from your viewDidLoad. This prevents the section headers from disappearing.

how to populate a static UITableView

I tried several ways but it seems I'm missing something..
Here is the code I use:
- (void)configureView
{
// Update the user interface for the detail item.
if (self.detailItem) {
for (int idx=0; idx<16; idx++) {
NSIndexPath *indexPath = [[NSIndexPath alloc] initWithIndex:idx];
[self.tableView cellForRowAtIndexPath:indexPath].detailTextLabel.text = [param objectAtIndex:idx];
}
self.detailDescriptionLabel.text = [self.detailItem description];
}
}
I call this on viewDidLoad. My tableview has 16 static cells, and I'm using standard "Subtitle" type cell, so no customization needed on that front. textLabel.text is filled at design time. Also my tableview is in a tableViewController. I also tried with standard population of tableview but it seems static cells don't agree with that way.
What am I doing wrong?
#jrturton I did some changes to see what's going on:
I added these three lines under for line to see if there's anything there:
UITableView *tv = self.tableView;
UITableViewCell *cell = [tv cellForRowAtIndexPath:[NSIndexPath indexPathForRow:idx inSection:0]];
NSString *label = [[NSString alloc] initWithString:cell.textLabel.text];
First of all tv is assigned correctly, I can see that. But other than that cell and label comes empty. Even tv has no row information it seems..
Could it be because tableview has datasource and delegate assigned to the tableviewcontroller.. I think I heard it shouldn't be like that or something...
#jrturton Here it is:
Hope this helps.
Some logging would quicky show you that your index path is not valid and therefore no cell is being returned.
To create an NSIndexPath for use in a UITableView, use this method:
[NSIndexPath indexPathForRow:idx inSection:0];
The index path has to contain a row and section for the table to understand it.
You also need to call this from viewWillAppear: rather than viewDidLoad, the static table is not populated that early on. And you must call [super viewWillAppear:animated] first!

Resources