UICollectionView : Offscreen auto scroll for paging - ios

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.

Related

Getting nill UITableviewcell from indexpath

I am getting nil UITableviewcell,check my below code.
int random = arc4random() % arr.count;
NSInteger index = [[arr objectAtIndex:random-1] integerValue];
NSIndexPath *path = [NSIndexPath indexPathForRow:index inSection:0];
SongListCell *cell2 = (SongListCell *)[table_view cellForRowAtIndexPath:path];
Getting nill cell.
what is wrong? what i am doing wrong?
Method cellForRowAtIndexPath: returns nil if cell is not visible or index path is out of range
I'm not sure exactly what you're trying to achieve, but I think that maybe the second row of your code shouldn't be there. Is that possible? Shouldn't it be:
int random = arc4random() % arr.count;
NSIndexPath *path = [NSIndexPath indexPathForRow:random-1 inSection:0];
SongListCell *cell2 = (SongListCell *)[table_view cellForRowAtIndexPath:path];
Because otherwise, your depending on the values inside arr and not just on the amount of objects in arr, and these values may contain invalid indices (or maybe even non numeric ones).
If that's not the case, can you please provide an example of values from arr?
You should create the cell yourself if it is nil, alternatively in your viewDidLoad or where appropriate, you can call one of these methods to make the tableview create the cells for you.
[self.tableView registerClass:<#(__unsafe_unretained Class)#> forCellReuseIdentifier:<#(NSString *)#>]
[self.tableView registerNib:<#(UINib *)#> forCellReuseIdentifier:<#(NSString *)#>]

Collection view crash on reloadItemsAtIndexPaths

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

Using a button to scroll a uicollectionview - how do i check bounds?

I don't know how to figure out whether I get to the last IndexPath and when to "rewind" and scroll to the first IndexPath
This is some setup:
- (void)viewWillLayoutSubviews;
{
[super viewWillLayoutSubviews];
UICollectionViewFlowLayout *flowLayout = (id)self.collectionView.collectionViewLayout;
if (UIDeviceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) {
flowLayout.itemSize = CGSizeMake(1024.0f, 768.0f);
} else {
flowLayout.itemSize = CGSizeMake(1024.0f, 768.0f);
}
[flowLayout invalidateLayout]; //force the elements to get laid out again with the new size
visibleItems = [self.collectionView indexPathsForVisibleItems];
self.currentIndexPath = [visibleItems firstObject];
[self.collectionView.collectionViewLayout invalidateLayout];
}
This is my button code:
- (IBAction)addToUploadQueque:(id)sender {
NSLog(#"current: %#",self.currentIndexPath);
NSInteger section = [self numberOfSectionsInCollectionView:self.collectionView] - 1;
NSInteger item = [self collectionView:self.collectionView numberOfItemsInSection:section]-1;
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForItem:item inSection:section];
NSIndexPath *firstIndexpath =[NSIndexPath indexPathForItem:0 inSection:0];
NSLog(#"current: %#",lastIndexPath);
if (self.currentIndexPath <= lastIndexPath) {
NSInteger newLast = [self.currentIndexPath indexAtPosition:self.currentIndexPath.length-1]+1;
self.currentIndexPath = [[self.currentIndexPath indexPathByRemovingLastIndex] indexPathByAddingIndex:newLast];
[self.collectionView scrollToItemAtIndexPath:self.currentIndexPath
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally
animated:YES];
}else{
self.currentIndexPath = [visibleItems firstObject];
[self.collectionView scrollToItemAtIndexPath:self.currentIndexPath
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally
animated:YES];
}
}
I'm making a button that iterates through each cell in the collection view and when it gets to the end (instead of going out of bounds) scroll back to the first cell.
Can anyone tell me what i'm doing wrong?
Judging from your comments, it doesn't sound like you want to just enumerate the visible cells in one pass, but rather want to enumerate through them manually. And it sounds like you're having an issue getting the next NSIndexPath. The problem with your code snippet is that you're incrementing the row/item (depending upon whether you're dealing with UITableView or UICollectionView), but not considering whether you've reached the end of a section, much less the end of the data source, before you try to use your incremented data source.
You could do something like:
NSInteger item = self.currentIndexPath.item;
NSInteger section = self.currentIndexPath.section;
item++; // go to next item
if (item >= [self.collectionView numberOfItemsInSection:section]) { // if you reached end of section ...
item = 0; // ... go to the start of the next section
section++;
if (section >= [self.collectionView numberOfSections]) { // if you reached the end of the data source ...
// all done, so set section to zero to go back to beginning, e.g. // ... then you're done
section = 0;
}
}
self.currentIndexPath = [NSIndexPath indexPathForItem:item inSection:section]; // otherwise, this is your new NSIndexPath
BTW, if we're going to focus on your code snippet, the another issue is that you're using the <= operator to compare two index paths. You cannot do that. You have to use the compare method of NSIndexPath.
But that if statement strikes me as unnecessary, as there is no index path in the data source after the last index path. If you're incrementing logic (above) correctly detects the end of the data source, then this if statement is unnecessary.
There are many, many issues in this code. But I'm wondering whether, rather than going through all of those details, whether a simpler approach is possible. If you just want to perform uploads for all of the visible rows, perhaps you could do something radically simpler, such as:
for (NSIndexPath *indexPath in self.collectionView.indexPathsForVisibleItems) {
// if the cell has some visual indication to reflect upload has been initiated,
// do that here
// do your asynchronous upload here, where the completion block dispatches
// updates to the cell/collectionView (to reflect that the individual upload
// is done)
}
Note, your code is scrolling to the cell (presumably when the upload is done). I might try to dissuade you from that approach, but instead just update the cell (e.g. set some flag that your cellForItemAtIndexPath method references, and then call reloadItemsAtIndexPaths for each row as that row finishes. Because uploads can be slow, you might not want the UI scrolling around as these asynchronous uploads finish.

ios using moveRowAtIndexPath:toIndexPath: correctly

Okay so I'm making a to do list app. I'm just wondering how to use moveRowAtIndexPath:toIndexPath: properly since it keeps crashing if the toDoItemCompleted method is triggered. I'm trying to move a row down to the bottom of the list once the method is triggered.
-(void)toDoItemCompleted:(ToDoItem *)todoItem {
NSUInteger origIndex = [_toDoItems indexOfObject:todoItem];
NSIndexPath *origIndexPath = [[NSIndexPath alloc]initWithIndex:origIndex];
NSUInteger endIndex = _toDoItems.count-1;
NSIndexPath *endIndexPath = [[NSIndexPath alloc]initWithIndex:endIndex];
[self.tableView beginUpdates];
[self.tableView moveRowAtIndexPath:origIndexPath toIndexPath:endIndexPath];
[self.tableView endUpdates];
}
You don't say what the error is. You should post the full error and point out which line of code is actually causing the error.
But one issue with your code is that you forgot to update your data source. This needs to be done before updating the table view.
Another issue is with how you create the index paths.
Something like this:
- (void)toDoItemCompleted:(ToDoItem *)todoItem {
NSUInteger origIndex = [_toDoItems indexOfObject:todoItem];
NSIndexPath *origIndexPath = [NSIndexPath indexPathForRow:origIndex inSection:0];
NSUInteger endIndex = _toDoItems.count - 1;
NSIndexPath *endIndexPath = [NSIndexPath indexPathForRow:endIndex inSection:0];
// Update date source
[_toDoItems removeObject:todoItem]; // remove from current location
[_toDoItems addObject:todoItem]; // add it to the end of the list
[self.tableView beginUpdates];
[self.tableView moveRowAtIndexPath:origIndexPath toIndexPath:endIndexPath];
[self.tableView endUpdates];
}

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.

Resources