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];
}
Related
I have 3 or 2 sections (depending on datasource), in my grouped UITableView. I am trying to reload the last section via:
dispatch_async(dispatch_get_main_queue(), ^{
[UIView performWithoutAnimation:^{
[feedDetailTB reloadSections:[NSIndexSet indexSetWithIndex:feedDetailTB.numberOfSections-1] withRowAnimation:UITableViewRowAnimationNone];
}];
});
First of all, the footer never disappears. The data source basically keeps track of whether there are more comments or not (a simple load more functionality). In the viewForFooterInSection I simply return nil, when all the comments have been loaded.
But, as you see in the GIF, at first the loading button stays there. It is even accessible and works. When I scroll up, it vanishes and one can see it in the bottom, which is correct. But after all the comments have been reloaded, it should vanish, but sadly it stays there.
If I use reloadData it works fine. But I can't used it, since I have other sections, which I don't need to reload.
Second, there is a weird animation/flickering of the row items, even when I have used UITableViewRowAnimationNone. Not visible in the GIF
You should implement "isTheLastSection" according to your logic
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
if (isTheLastSection) {
return 40;
}
return 0;
}
In order to add new rows to a section, you must use the insertRowsAtIndexPaths rather than just adding new objects to data source and reloading a section.
Here's the code:
NSMutableArray *newCommentsIndexPath = [[NSMutableArray alloc] init];
for (NSInteger i = currentCount; i < (_postDetailDatasource.commentsFeedInfo.allCommentsArray.count + serverComments.count); i ++)
{
NSIndexPath *idxPath = [NSIndexPath indexPathForRow:i inSection:sectionNumber];
[newCommentsIndexPath addObject:idxPath];
}
[_postDetailDatasource.commentsFeedInfo.allCommentsArray addObjectsFromArray:serverComments];
[feedDetailTB beginUpdates];
[feedDetailTB insertRowsAtIndexPaths:newCommentsIndexPath withRowAnimation:UITableViewRowAnimationFade];
[feedDetailTB endUpdates];
So it's easy to select a row that is currently in the UITableView. For example, to select the first row:
[self.tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
animated:YES
scrollPosition:UITableViewScrollPositionNone];
Say that I have an array that is the data source for the table and the array count is greater than the number of cells displayed in the tableview. How can I get the UITableView to scroll to an index from the array that is beyond what is currently being displayed in the tableview?
All I am trying to do is to replicate programmatically what a user would do with their index finger as they scroll down the table.
My specific table displays 9 rows. My array has 20+ items. As the UIViewController loads, it retrieves the row number that should be selected (from an integer stored in NSUserDefaults). But I find that it will only scroll to the correct array position if that integer value is between 0 and 8. If it is 9 or greater, nothing happens, and I can't figure out how to make it respond to this. I've looked at all the UITableViewDelegate methods and none seems to address this.
What I've been doing to scroll and select a specific row is this (example arbitrarily selecting row 11):
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:11 inSection:0]
atScrollPosition:UITableViewScrollPositionTop
animated:YES];
[self.tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:11 inSection:0]
animated:YES
scrollPosition:UITableViewScrollPositionTop];
Can anyone help me? I assume it isn't difficult, but I'm stuck.
Thanks!
Your cells that are off the screen ain't selecting because you are using reusable cells. The cells from the visible screen will be used later, it isn't that all 100 cells are cached and each cell is responsible for each row. What it means is that they could or couldn't have something in it already. For example, lets say you have cell for row 1. When it comes off the screen, in the next few cells it will be reused as cell 15 or something, and if it had selected properties, it will still have it. It is like a new job and you get a desk from the developer before you - you could have desk with his trash, but it could also be clean.
I wouldn't select them as you select them by method, but in if statement in your cellForRowAtIndexPath. Something along the lines (added comments):
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
// When using method with forIndexPath you don't have to check for nil because you will always get cell
MyTableCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
MyObj *obj = [self.myArray objectAtIndex:indexPath.row];
cell.location.text = obj.location.location_description;
// other formatting, text display, image loading, etc.
if ([self.selectedObjects containsObject:obj]) {
// do some selecting stuff
} else {
// but don't forget to unselect because you can get already selected cell
}
return cell;
}
Edit: To select invisible cell, first scroll to it, then select:
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
[self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionTop];
Try using UITableViewScrollPositionBottom instead of UITableViewScrollPositionNone
That is use this code
[self.tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:10 inSection:0]
animated:YES
scrollPosition:UITableViewScrollPositionBottom];
I figured this out. My code was running in viewDidLoad which is too early. I needed to move it to viewDidAppear. At least I know that I am not losing my mind.
I am making a chat application. When the keyboard appears, my UITableView and toolbar (with textfield and button) move up using the setFrame method.
The problem occurs when I am receiving a message from another user while I'm typing a message. When that happens (i.e. after an insertRowsAtIndexPaths to the UITableView), my UITableView and toolbar "reset" to their original positions.
Is this normal behavior caused by the system? I can't find anything in my code that would do this. I want that the toolbar remains visible so I can still type my message.
This is the code to add a convoItem (conversation item) to my array and UITableView:
- (void)insertConvoItem:(ConvoItem *)item
{
[_convoItems addObject:item];
// Update the table view
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:([self.convoItems count] - 1) inSection:0];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
// Scroll to the bottom so we focus on the latest message
NSUInteger numberOfRows = [self.tableView numberOfRowsInSection:0];
if (numberOfRows) {
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:(numberOfRows - 1) inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
}
It's normal. Receiving new message resets your table view to it's original position.
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.
I am using [tableview reloadData]; to reload the data in my UItableView, however when I use this I loose my highlight on my UItableVewCell.
I would like to know the best way to reinstate this highlight.
I set a tempIndexPath when the user selects the cell they edit the information then I call reloadData, then inside cellForRowAtIndexPath I use this code to re-highlight the cell however its not working.
if ([tempIndexPath isEqual:indexPath]) {
cell.selected = YES;
}
This code keeps the highlighted selection, and is safe with resorting/inserts/repositioning since it keeps a reference to the underlying object from the model, instead of the index path. It also scrolls to the selection, which is helpful when the updated model causes the selection to be moved out of the current scroll position's frame.
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//Save the selected object at this row for maintaining the highlight later in reloadData
_selectedItem = [_items objectAtIndex:indexPath.row];
}
- (void) reloadData
{
[_itemsTable reloadData];
//Reselect and scroll to selection
int numItems = _items.count;
for (int i = 0; i < numItems; i++) {
NSDictionary *dict = [_numItems objectAtIndex:i];
if (dict == _selectedItem) {
//This is more reliable than calling the indexPathForSelectedRow on the UITableView,
// since the selection is cleared after calling reloadData
NSIndexPath *selectedIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
[_itemsTable scrollToRowAtIndexPath:selectedIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
[_itemsTable selectRowAtIndexPath:selectedIndexPath animated:FALSE scrollPosition:UITableViewScrollPositionNone];
break;
}
}
}
Save the selected row, reload your table view's data and select the row again.
NSIndexPath* selectedIndexPath = [tableView indexPathForSelectedRow];
[tableView reloadData];
[tableView selectRowAtIndexPath:selectedIndexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
You should know that this is dangerous, because if new items were added to the table view before the selected row, the selection will not be the correct cell. You should calculate how many rows were inserted before the row and adjust the selectedIndexPath accordingly.