On the iPad I show a UIPopover when the user selects cells in a UITableView. The cell stays selected until the popover is dismissed.
When the user rotates the device from portrait to landscape orientation and the selected cell was on the lower part of the screen, it will disappear after the rotation and the popover ends up pointing at another (indifferent) cell.
How can I make sure that the selected cell in a UITableView stays on screen when rotating from portrait to landscape orientation?
Update: Combining Caleb's and kviksilver's codes, the following is a working solution:
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
CGRect activeCellRect = [self.tableView rectForRowAtIndexPath:self.indexPath];
if ((activeCellRect.origin.y + activeCellRect.size.height) >
(self.view.frame.origin.y + self.view.frame.size.height))
{
// If a row ends up off screen after a rotation, bring it back
// on screen.
[self.tableView scrollToRowAtIndexPath:self.indexPath
atScrollPosition:UITableViewScrollPositionBottom
animated:YES];
}
}
Update 2, on repositioning the UIPopover: After the scroll command it is necessary to send a reloadData message to the table view. Then the rectForRowAtIndexPath: method will correctly report the new position of the cell (otherwise it will not, as it is not updated properly after the scroll-command)!
On orientation change try checking indexPathsForVisibleRows to see if your cell is visible and then using scrollToRowAtIndexPath if not.. something like:
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
if (![[self.tableView indexPathsForVisibleRows] containsObject:[self.tableView indexPathForSelectedRow]]) {
[self.tableView scrollToRowAtIndexPath:[self.tableView indexPathForSelectedRow] atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
}
}
You already know which row is selected, right? You also know when the device orientation changes, or at least you can know that, because there are UIViewController methods for that. You can get the rectangle for the selected row using UITableView's -rectForRowAtIndexPath: method, and it's easy to make sure that rectangle stays visible using UIScrollView's -scrollRectToVisible:animated: method, which UITableView inherits.
Related
I am creating a paginated UICollectionView that scrolls horizontally and whose cells are as big as the collection view so that only one cell is shown at a time. I also want to display the last item first when the collection view first appears, so that the other items are revealed from the left instead of the default where the next items come in from the right.
To do that, I call scrollToItemAtIndexPath: in my view controller's viewDidLayoutSubviews as suggested by this answer. If I put the call to scrollToItemAtIndexPath in viewWillAppear or viewDidLoad, the collection view does not at all scroll to the last item.
However, this call destroys the layout of my collection view cells. For example, if I don't call scrollToItemAtIndexPath:, the collection view (the white area below) looks like the left one--correctly laid out but showing the first item. If I call scrollToItemAtIndexPath:, the collection view does initially display the last item, but the layout is messed up like in the right (the date isn't even showing anymore).
What am I doing wrong?
More info:
I see this error both in iOS 7 and iOS 8.
I use size classes in Xcode 6.1.
The code for viewDidLayoutSubviews:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForItem:self.unit.readings.count - 1 inSection:0];
[self.readingCollectionView scrollToItemAtIndexPath:lastIndexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:NO];
}
Whenever I've had this issue and I have a constant size cell I've worked around it by just setting the contentOffset manually and ignoring the collectionView methods.
self.collectionView.contentOffset = (CGPoint){
.x = self.collectionView.contentSize.width - cell.width,
.y = 0,
};
I put the following in viewWillLayoutSubviews (also works in viewDidLayoutSubviews):
[self.readingCollectionView scrollToItemAtIndexPath:lastIndexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:NO];
[self.readingCollectionView reloadData];
A UI issue persists, however: when the view controller appears, it displays the first item, THEN immediately refreshes to display the last item instead.
To get around this seemingly unresolvable problem, I hacked the UI instead: display a "Loading" label before the view controller appears, and show the collection view in viewDidAppear.
It has been suggested that one scroll to the desired row in viewWillAppear, but this does not work with iOS 7. I have only been able to make this work in iOS 7 in the viewDidAppear callback. Unfortunately, you see the desired row scroll into view. I don't want to see any scrolling, I simply want the row to be visible when loaded. Can anyone suggest the proper way to do this in iOS 7?
It probably did not work in viewWillAppear, because that table had no data at this point.
Add [tableView reloadData];and it should work.
Let me get this straight: you want your table view to show a certain row at the top when the view apperas? Yes?
If so, you want:
- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated
with your cell indexPath, UITableViewScrollPositionTop as scrollPosition and animated NO like so
[tableView scrollToRowAtIndexPath:myExampleindexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
If you know the cell index then it's as simple as:
[tableView setContentOffset:CGPointMake(cellLocation.x,cellLocation.y) animated:NO];
Call that just after you load your tableView data and it will scroll to your cell being on top. There are other options as well:
[tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:currentRow inSection:currentSection] animated:NO scrollPosition:UITableViewScrollPositionTop];
Use this code with whatever scrollPosition you would like and Apple takes care of the bounding to the table (whereas setting the scrolling position is all user defined, it could be out of the table's view).
EDIT:
You could surround your selecting code with a call to UIView setting no animations allowed. That has worked for me in the past with different things, but I have never tried it in viewDidLoad.
[UIView setAnimationsEnabled:NO];
//Scroll the tableview
[UIView setAnimationsEnabled:YES];
I have an UICollectionView. When I touch one of the cells I present a popover view from its location with the arrow pointing to the cell with some extra information about the cell.
When I rotate the device, the UICollectionView automatically repositions its cells.
What I'd like to do, is to reposition my popover view automatically so it points to the same cell as before (which is now at a different location in the UICollectionView)
What I'm having trouble is tracking (finding out) what is my cell's new location, so I can manually represent the popover view from the cell's new location.
I tried attaching and storing an "idString" for the cell for comparison, but this for some reason returns the cell's old frame from before the screen rotation
NSArray* visibleCells = [UIAppDelegate.ocollectionView visibleCells];
for (UICollectionViewCell *cell in visibleCells) {
if ([cell.idString isEqualToString:self.idString] ) {
NSLog (#"we have a match!!! %#", cell);
CGRect rectInCollectionView = cell.frame;
rect = [UIAppDelegate.collectionView convertRect:rectInCollectionView toView:[UIAppDelegate.collectionView superview]];
}
}
any ideas appreciated. thank you.
Where is this code being placed? I guess it should be placed on a delegate method like this kind:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
So the UICollectionView has redrawn its new cell's position.
In my UIViewContoller's subclass, ViewWillAppear asks whether or not there's any data to present, and if there is, changes the UINavigationController's prompt accordingly. This triggers an animation as the prompt pops into view, causing the UINavigationBar to grow in size. When this happens it partially occludes the cells in the top row of the UICollectionView.
I have a vertical Auto Layout constraint of 0, seemingly pinning the UICollectionView to its nearest neighbor, which should be its superview, but the navbar still blocks the top halves of the cells. I've tried everything — telling the CollectionView to layout its subviews, reloading data, etc., but nothing seems to work. Any idea what's going wrong?
- (void)viewWillAppear:(BOOL)animated{
if(self.orderedURLSet.count == 0){
self.navigationItem.prompt = nil;
[self.collectionView setNeedsDisplay];
} else {
self.navigationItem.prompt = #"Tap photos to edit";
}
[self.collectionView reloadData];
[self.collectionView layoutSubviews];
}
Edit: What makes this even stranger is that when I rotate orientation the collectionViewCells aren't occluded, and the full cells remain visible when I rotate back to portrait orientation. Is there some way I can "trick" my app into thinking its layout has changed and it needs to reposition the cells? LayoutSubviews isn't doing the trick.
Edit: After digging a little more into the UIView documentation, it looks like setNeedsLayout and layoutIfNeeded are really the methods I should be using, and not layoutSubviews. I've tried calling both of them, on navigationController:didShowViewController:animated:, viewWillAppear, viewDidAppear, and viewDidLayoutSubviews to no avail.
Have you tried??
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
I have a UITableView of cells where one cell contains a UITableView. Selecting that row that contains the table pushes a new view onto the screen with a list of items. The user selects a list of items and then presses 'back' to pop that view. The parent view looks at the number of items selected and the height of the cell is supposed to adjust to show all items selected.
What happens is that when the view is reshown, the listed items extends into the cell below. If I scroll that cell off the screen, then back to it, the cell is then the right height showing all items correctly.
I've tried several things such as putting code into viewWillAppear of the main table like [self.view setNeedsDisplay] and [self.view setNeedsLayout], but it doesn't work.
I can't seem to find a way to make that cell redraw to it's right size without scrolling the table once the view appears.
Is there some other method to force the redraw before it actually appears?
try:
NSIndexPath *theIndexPath = [NSIndexPath indexPathForRow:cellRow inSection:cellSection];
NSArray *theIndexPaths = [NSArray arrayWithObject:theIndexPath];
[table reloadRowsAtIndexPaths:theIndexPaths withRowAnimation:NO];
What are you currently using to redraw the cell? Just waiting for it to redraw itself when it goes off screen and back on? Or are you using something like [table reloadData];?