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

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.

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.

UITableView force sectionIndex to update without calling reloadData

I'm modifying my UITableView's data source from a PHChange instance, and this works fine. What I don't understand is I can't get the sectionIndex on the right of the UITableView to update unless you reload the whole UITableView with: reloadData.
Is there any other way to force update the sectionIndex on the right of the UITableView? If I manually call: sectionIndexTitlesForTableView: I confirm that I'm getting back fresh data, however the old sectionIndex on the right doesn't change visually, which is what I need.
Code:
PHFetchResultChangeDetails *changeDetails = [changeInstance changeDetailsForFetchResult:oldFetch];
if (changeDetails)
{
PHFetchResult *fetchResultAfterChange = changeDetails.fetchResultAfterChanges;
if (fetchResultAfterChange.count < 1)
{
[subArray removeObjectAtIndex:subIdx];
dispatch_async(dispatch_get_main_queue(),^
{
NSIndexPath *path = [NSIndexPath indexPathForRow:subIdx inSection:idx];
[self.albumsTableView deleteRowsAtIndexPaths:#[path] withRowAnimation:UITableViewRowAnimationNone];
// Confirmed data is updating
NSLog(#"%#",[self sectionIndexTitlesForTableView:self.albumsTableView]);
// Does nothing
[self.albumsTableView setNeedsDisplay];
});
}
}
Try this function
- reloadSectionIndexTitles
This method gives you a way to update the section index after inserting or deleting sections without having to reload the whole table.

Endless UICollectionView Slideshow + scrollToItemAtIndexPath

We have a UICollectionView that fills the entire screen to mimic a full-screen photo slideshow. The collection view's data is backed by a NSFetchedResultsController. Only one cell of the collection view displays on the screen at any given time. There is also a repeatable NSTimer set at an interval of 5 seconds that fires off a method "gotoNextPage" which calls scrollToItemAtIndexPath to the next fetched object (the next UICollectionView cell, the next slideshow photo). This is a smooth transition forward. When we get to the end of the slideshow we would like to seamlessly advance forward to the first photo and start the slideshow over again. Of course when we scrollToItemAtIndexPath back to the first photo it scrolls backwards rapidly (depending on how many fetched objects we have). We want the forward animation to be consistent no matter if you are at the beginning, middle, or end of the slideshow/UICollectionView.
Does anyone have a creative way to solve this problem?
Thanks
You could just let the index path item number increase continuously, and base your cellForItemAtIndexPath and other table logic on the remainder after dividing by the photo count. So you could code:
NSUInteger photoCount = [[self.fetchedResultsController fetchedObjects] count];
NSIndexPath *fetchIndexPath = [NSIndexPath indexPathForItem:(indexPath.item % photoCount) inSection:0];
Photo *myPhoto = [self.fetchedResultsController objectAtIndexPath:fetchIndexPath];
A similar approach should work in the other tableView/NSFetchedResultsController datasource/delegate methods. EDIT As you note in your comment, you would have to set numberOfItemsInSection to a very large number (NSIntegerMax?).
Alternatively...
You could amend your cellForItemAtIndexPath so that there is an extra cell, after the final photo, which contains the same photo as for row 0.
Photo *myPhoto
NSUInteger photoCount = [[self.fetchedResultsController fetchedObjects] count];
if (indexPath.item = photoCount) {
myPhoto = [self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
} else {
myPhoto = [self.fetchedResultsController objectAtIndexPath:indexPath];
}
... configure cell...
You would have to amend numberOfItemsInSection to return photoCount+1.
After you scroll forward to this new final cell, scroll immediately to the first cell but without animation. Since the first and last cell have the same photo, the user will not see any transition. Then you can revert to scrolling forward with animation.
if (currentItem == photoCount -1) {
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:photoCount inSection:0] atScrollPosition:<your preference> animated:NO];
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0] atScrollPosition:<your preference> animated:YES];
} else {
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:(currentItem+1) inSection:0] atScrollPosition:<your preference> animated:YES];
}

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!

Setting scroll position in UITableView

I have an application that works some what similar to how iPhone's Contact application works. When we add a new Contact user is directed to a view only screen with Contact information. If we select "All Contacts" from the navigation bar, user is navigated to list of all contacts where the recently added contact is in view.
We can move the view to a particular row using:
[itemsTableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionBottom];
... but it's not working. I'm calling this right after calling:
[tableView reloadData];
I think I'm not suppose to call selectRowAtIndexPath:animated:scrollPosition method here. But if not here, then where?
Is there any delegate method that gets called after the following method?
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
I think I've got a similar app: A list of item., tap '+' -> new screen, come back and see the updated list, scrolled to show the added item at the bottom.
In summary, I put reloadData in viewWillAppear:animated: and scrollToRowAtIndexPath:... in viewDidAppear:animated:.
// Note: Member variables dataHasChanged and scrollToLast have been
// set to YES somewhere else, e.g. when tapping 'Save' in the new-item view.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (dataHasChanged) {
self.dataHasChanged = NO;
[[self tableView] reloadData];
} else {
self.scrollToLast = NO; // No reload -> no need to scroll!
}
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (scrollToLast) {
NSIndexPath *scrollIndexPath = [NSIndexPath indexPathForRow:([dataController count] - 1) inSection:0];
[[self tableView] scrollToRowAtIndexPath:scrollIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
}
I hope this helps. If you add something in the middle of the list, you could easily scroll to that position instead.
You can try this , my application in some kind of similar to you when i click on a button scroll of uitableview is up.
[tableviewname setContentOffset:CGPointMake(0, ([arrayofcontact count]-10)*cellheight) animated:YES];
10 is for how many cell you want to scroll up
Hope this will help you:-)
Scrolling animation is inevitable when offset is set from viewDidAppear(_:). A better place for setting initial scroll offset is viewWillAppear(_:). You'll need to force the layout of a table view, because it's content size is not defined at that point.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.setNeedsLayout()
tableView.layoutIfNeeded()
if let selectedIndexPath = selectedIndexPath, tableView.numberOfRows(inSection: selectedIndexPath.section) > 0 {
tableView.scrollToRow(at: selectedIndexPath, at: .top, animated: false)
}
}
Do you have enough dummy contacts added to test the scrolling? It seems that only when your tableView is of a certain size the iPhone finds the inputus to scroll.
This is the code that works for me in my project. I use a previous button and therefore I scroll the tableview a little bit further down that it usually would go with UITABLEVIEWSCROLLPOSITION. (my previous button won't work if it can't "see" the previous cell.
Ignore some of the custom method calls.
- (void)textFieldDidBeginEditing:(UITextField *)textField {
//Show the navButtons
[self navButtonAnimation];
DetailsStandardCell *cell = (DetailsStandardCell *)textField.superview.superview.superview;
self.parentController.lastCell = cell;
//Code to scroll the tableview to the previous indexpath so the previous button will work. NOTE previous button only works if its target table cell is visible.
NSUInteger row = cell.cellPath.row;
NSUInteger section = cell.cellPath.section;
NSIndexPath *previousIndexPath = nil;
if (cell.cellPath.row == 0 && cell.cellPath.section != 0) //take selection back to last row of previous section
{
NSUInteger previousIndex[] = {section -1, ([[self.sections objectForKey:[NSNumber numberWithInt:section - 1]]count] -1)};
previousIndexPath = [[NSIndexPath alloc] initWithIndexes:previousIndex length:2];
}
else if (cell.cellPath.row != 0 && cell.cellPath.section != 0)
{
NSUInteger previousIndex[] = {section, row - 1};
previousIndexPath = [[NSIndexPath alloc] initWithIndexes:previousIndex length:2];
}
[self.theTableView scrollToRowAtIndexPath: cell.cellPath.section == 0 ? cell.cellPath : previousIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
}

Resources