I have a UICollectionViewController that is part of a Navigation Controller and Tab Bar Controller. At the beginning of the Navigation Controller, I have Interface Builder set to display the Toolbar at the bottom of the View. For the first View Controller of the Navigation Controller, I am using [self.navigationController setToolbarHidden:YES animated:YES]; in order to hide the Toolbar, then in my UICollectionViewController, I use
-(void)viewWillAppear:(BOOL)animated
{
[self.navigationController setToolbarHidden:NO animated:YES];
}
in order to have the Toolbar be displayed. This part works perfectly, my problem comes from the UICollectionViewCell that I have in the UICollectionView. I want it to be the full size of the UICollectionView, and I'm using AutoLayout, so I'm using:
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return self.collectionView.frame.size;
}
However, the first time the view is loaded, it returns the wrong value. The first time the view and CollectionView are loaded, that returns the size of the collectionView INCLUDING the height of the Toolbar, which then leads to the warning:
the item height must be less that the height of the UICollectionView
minus the section insets top and bottom values.
And the cell doesn't load. However, when I force a [self.collectionView reloadData]; after the warning, the correct value is returned for self.collectionView.frame.size and the cell loads.
I'm really frustrated at this and would greatly appreciate any help that anyone can offer.
Edit:
After reading Mundi's comment, I ended up changing some of my code to:
-(void)viewWillAppear:(BOOL)animated
{
[self.navigationController setToolbarHidden:NO animated:YES];
[self.collectionView reloadData];
}
And the issue was solved. However, now I have a new but related problem. When I add [self.collectionView scrollToItemAtIndexPath:selectedIndexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO]; (selectedIndexPath is pushed from the first view controller) to viewWillAppear, I get the same warning about the size of the UICollectionView item. If I have my code like this:
-(void)viewWillAppear:(BOOL)animated
{
[self.navigationController setToolbarHidden:NO animated:YES];
[self.collectionView scrollToItemAtIndexPath:selectedIndexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
[self.collectionView reloadData];
}
I don't get that size warning, but my collectionView always starts at index 0 rather than selectedIndexPath. If I change around the ordering to this:
-(void)viewWillAppear:(BOOL)animated
{
[self.navigationController setToolbarHidden:NO animated:YES];
[self.collectionView reloadData];
[self.collectionView scrollToItemAtIndexPath:selectedIndexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
}
I get that size warning, and the cell doesn't load until I invoke a method to call reloadData. However, it does start at the correct indexPath.
This problem is really about dealing with the view lifecycle. In viewWillAppear all the subviews are already laid out, so maybe you are hiding the toolbar too late.
Your solution to call reloadData after showing the toolbar is not so bad as all this happens before the view appears. It is logical - the geometry has changed and you have to recalculate the layout.
Try experimenting with the view controller's hidesBottomBarWhenPushed property. Maybe that takes care of this problem without having to code anything.
I have an issue with a View Controller which is loaded from a NIB. In this NIB I have 2 views, a landscape and portrait. When willAnimateForRotation occurs I i set
// willAnimateForRotation..
if (isLandscape) {
self.view = self.viewLandscape;
}
else {
self.view = self.portraitView;
}
in the beginning I set both tables to editing mode:
// viewDidLoad
[self.tableViewPortrait setEditing:YES animated:NO];
[self.tableViewLandscape setEditing:YES animated:NO]; // *
// *
I am pretty sure this line is causing cellForRow to be called on tableViewLandscape at this point, instead of when i set self.tableView = self.tableViewLandscape
It only happens the first time, if I perform 2 orientation changes it will load the items into the tableView correctly.
i dont want to do a [self.tableView reloadData] in orientation change.. that would be redundant.
can anyone see a more graceful way for me to fix this?
Ok, best I could come up with is:
//willAnimateToRotation..
if (!self.tableViewLandscape.editing)
{
[self.tableViewLandscape setEditing:YES animated:NO];
}
its a cheap hack but can't think of a more correct solution.
How can I give a tableView cell background color the fade effect as soon as it appears. I know how to get a cell selected when the table view appears but the color persists for ever. I would like it to stay there for a while (2 seconds or whatever) and then fade away.
Someone said that I should use [tableView performSelector] so I wrote this:
-(void) viewdidLoad {
[tableView performSelector:#(highlight) withObject:nil afterDelay:2];
}
-(void) highlight
{
//I have a row selected as soon as my view appears
-[tableView deselectRowAtIndexPath:[tableView indexPathForSelectedRow] animated:YES];
}
But when I used it, my application crashed. I think the crashing has something to do with the "withObject" attribute.
Can anyone help me with this?
-(void)viewDidAppear:(BOOL)animated {
[self performSelector:#selector(highlight) withObject:nil afterDelay:5];
[super viewDidAppear:animated];
}
-(void)highlight{
[tableView deselectRowAtIndexPath:[tableView indexPathForSelectedRow] animated:YES];
}
That worked for me, i think you may have missed out #selector() in performSelector. Be careful with the delay as keeping table cells selected after navigating back to the view is not recommended in Apples UI guidelines (as far as i can remember).
The - highlight method belongs to your controller object, not to the table view. If you change tableView to self in the second line, it should stop the app crashing.
I'm facing what appears to be a UIKit bug, and it takes the combination of two less commonly used features to reproduce it, so please bear with me here.
I have quite the common view hierarchy:
UITabBarController -> UINavigationController -> UITableViewController
and the table view controller pushes another table view controller onto the navigation controller's stack when a row is selected. There's absolutely nothing special or fancy in the code here.
However, the second UITableViewController, the "detail view controller" if you will, does two things:
It sets hidesBottomBarWhenPushed to YES in its init method, so the tab bar is hidden when this controller is pushed:
- (id)initWithStyle:(UITableViewStyle)style {
if(self = [super initWithStyle:style]) {
self.hidesBottomBarWhenPushed = YES;
}
return self;
}
It calls setToolbarHidden:NO animated:YES and setToolbarHidden:YES animated:YES on self.navigationController in viewWillAppear: and viewWillDisappear: respectively, causing the UIToolbar provided by UINavigationController to be displayed and hidden with animations:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.navigationController setToolbarHidden:NO animated:YES];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.navigationController setToolbarHidden:YES animated:YES];
}
Now, if the second UITableViewController was pushed by selecting the row at the bottom of the screen (it doesn't have to be the last row) in the first controller, this row does not automatically get deselected when the user immediately or eventually returns to the first controller.
Further, the row cannot be deselected by calling deselectRowAtIndexPath:animated: on self.tableView in viewWillAppear: or viewDidAppear: in the first controller.
I'm guessing this is a bug in UITableViewController's drawing code which of course only draws visible rows, but unfortunately fails to determine correctly if the bottommost row will be visible in this case.
I failed to find anything on this on Google or OpenRadar, and was wondering if anyone else on SO had this problem or knew a solution/workaround.
I had this same exact problem, (though I am not using a toolbar). My solution was to deselect the row in didSelectRowAtIndexPath after pushing my second view controller.
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
RunViewController *runViewController = [[RunViewController alloc] initWithNibName:#"RunViewController" bundle:nil];
runViewController.managedObjectContext = self.managedObjectContext;
runViewController.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:runViewController animated:YES];
[runViewController release];
//deslect "stuck" row
[aTableView deselectRowAtIndexPath:indexPath animated:YES];
}
If I remember well I think I had same/similar problem once, but my solution was rather rough:
- (UITableViewCell*)tableView:(UITableView*)tableView
cellForRowAtIndexPath:(NSIndexPath*)indexPath {
// ...
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
I have an issue where when a textField is clicked on in a UITableViewCell, the method tableView:didSelectRowAtIndexPath: does not get invoked. The problem is, I need to scroll my tableView into proper position, otherwise the keyboard goes right over the first responder.
I have to then move code like this:
[[self tableView] scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
into both my tableView delegate method and in my UITextField delegate method, textFieldDidBeginEditing:.
Is the best way to just create a new method, pass to it the indexPath of the cell/textfield being clicked, and call the method from both the tableView delegate and the UITextField delegate? better way of going about it?
I found the following works well (It assumes you're in a table view controller)
- (void)textFieldDidBeginEditing:(UITextField *)textField{
CGPoint pnt = [self.tableView convertPoint:textField.bounds.origin fromView:textField];
NSIndexPath* path = [self.tableView indexPathForRowAtPoint:pnt];
[self.tableView scrollToRowAtIndexPath:path atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
There are a couple of ways to fix this issue. What happens is that the tableViewCell delegates the touch event to its subviews which causes the textfield to handle the touch in stead of your cell.
To fix this:
Set the UITextfield's userinteractionEnabled property to NO, then when you get the didSelectRowAtIndexPath message you re-enable userInteractionEnabled and call the TextField's becomeFirstResponder. On v2.2 you don't even need to set the userInteractionEnabled flag, I have not tested this with other versions however the documentation is quite clear that you should have this enabled. in the tableViewController you simply need to have the indexpath saved until you get the UIKeyboardDidShow message
Create a delegate for the UITextField that reports back to your tableViewController so that you can set the scrolling offset from there.
register for the keyboard events and then figure out the scrolloffset by checking what textfield is in editing mode
You can set your controller as the delegate of your UITextField, then adjust your table view in either textFieldDidBeginEditing: or textFieldShouldBeginEditing:
I did not find any solutions that work for me in the web. After days of Googling and experimenting, I finally have this issued well nailed. It is a complex bug in Apple iPhone as you will see in the end of this post.
If you ran into an issue like me as follows:
having tableviewcell larger than half of the iphone screen (Do not confused with Apple's UICatalog's examples have a short tableview cell of less than 50 points, not applicable here.),
having more than one uitexfields in the cell or combination of uitextfield and uitextview or uiwebview in the cell,
Tapping between uitextfields and uitextview or uiwebview results in unpredictable scroll position either the clicked uitextfield jumps out of view or covered by the keybaord. It only works the very first time when the keyboard appears in the tableviewcell and not working right subsequently.
I had the major break through after reading posts similar to this one: http://alanduncan.net/old/index.php?q=node/13 They did not get it completely right either. The pain is caused by a bug in UIKeyboard events. When the keyboard first appear, it issue an UIKeyboardWillShowNotification and UIKeybaordDidShowNotification. Theree is a bug in iPhone that somehow the first UIKeyboardWillShowNotification differs from the subsequent UIKeyboardWillShowNotification. The solution is to OBSERVE UIKeyboardDidShowNotification. So when your cell will appear, add the following code
NSNotificationCenter*nc=[NSNotificationCenter defaultCenter];
[nc addObserver:self selectorselector(keyboardDidShow name:UIKeyboardDidShowNotification object:self.window];
In the keyboardDidShow function, we need to scroll the TABLEVIEW, not the tableviewcell as suggested in above post. Or you may see various objects go separate way, not scroll together in one piece.
(void)keyboardDidShow:(NSNotification *)notif
{
//1. see which field is calling the keyboard
CGRect frame;
if([textField_no1 isFirstResponder])
frame=textField_no1.frame;
else if([textField_no2 isFirstResponder])
frame=textField_no2.frame;
else if([textField_no3 isFirstResponder])
frame=textField_no3.frame;
else if([textView isFirstResponder])
frame=textView.frame;
else return;
CGRect rect=self.superview.frame;
//2. figure out how many pixles to scroll up or down to the posistion set by theKeyBoardShowUpHorizon.
//remove the complexity when the tableview has an offset
[((UITableView*)[self.superview).setContentOffset:CGPointMake(0,0) animated:YES];
int pixelsToMove=rect.origin.y+ frame.origin.y-theKeyBoardShowUpHorizon;
//3. move the uitableview, not uitableviewcell
[self moveViewUpOrDownByPixels:pixelsToMove];
}
- (void)moveViewUpOrDownByPixels:(int)pixels
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.6];
//find the position of the UITableView, the superView of this tableview cell.
CGRect rect=self.superview.frame;
//moves tableview up (when pixels >0) or down (when pixels <0)
rect.origin.y -= pixels;
rect.size.height += pixels;
self.superview.frame = rect;
[UIView commitAnimations];
}
To restore the tableView back, you need to add observer on UIKeyboardDidHideNotification (not UIKeyboardWillHideNotification as suggested by other posts, to avoid flickering) where you tableviewcell appears every time and put back the tableview to where it was.
[nc addObserver:self selectorselector(keyboarDidHide) name:UIKeyboardDidHideNotification object:nil];
- (void)keyboardDidHideNSNotification*)notif
{
//we have moved the tableview by number of pixels reflected in (self.superview.frame.origin.y). We need to move it back
[self moveViewUpOrDownByPixels:self.superview.frame.origin.y];
}
Do not forget to remove both of the observesr when your cell disappear by [[NSNotificationCenter defaultCenter] removeObserver:...
That is all it takes. I hope Apple iPhone team one day will resolve this issue, maybe in 4.0 in a few months.
I discovered that it's actually pretty easy to do this.
The UITextField delegate method textFieldDidBeginEditing will give you the text field, which you can then map to an indexPath using:
self.currentIndexPath = [self.tableView indexPathForRowAtPoint:textField.frame.origin];
Then you can scroll the cell into view (i.e. in your UIKeyboardDidShowNotification keyboard notification handler):
[self.tableView scrollToRowAtIndexPath:self.currentIndexPath atScrollPosition:UITableViewScrollPositionNone animated:YES];
I've found a solution.
Open .xib file in interface builder.
Select the table view
From IB Menu select Tools->Size Inspector
On Scroll View Size Section, modify Inset -> Bottom value to 100, 150 ,250 depending how big is your table view.
Code
-(void) textFieldDidBeginEditing:(UITextField *)textField
{
UITableViewCell *cell = (UITableViewCell *) [[textField superview] superview];
[self.tableView scrollToRowAtIndexPath:[tableView indexPathForCell:cell]
atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
didSelectRowAtIndexPath won't be called for UITextField embedded cells; hence, scroll logic needs to be elsewhere.
- (void)textFieldDidBeginEditing:(UITextField *)textField {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:1];
UITableViewCell *cell = [_tableView cellForRowAtIndexPath:indexPath];
[_tableView setContentOffset:CGPointMake(0, cell.frame.size.height) animated:YES];
}
Make sure to wire textField delegate to self
Register for UIKeyboardWillShowNotification and UIKeyboardWillHideNotification, then adjust your view as necessary in the notification handlers. One of the example apps shows how to do this, but I forget which...SQLiteBooks, or maybe EditableDetailView.
I was struggling with this same issue, where I have UITextFields inside of UITableViewCells and couldn't get view to scroll to the field when it was being edited. The core of my solution is below.
The key to this code is the line where the UITextField is created. Instead of hard coding a x and y value in the CGRectMake() function, it uses the x and y from the cell in which its being placed (+/- any offset you want from the edges of the cell as shown below). Hard coding x and y values in the UITextField* gives every cell the same x,y frame position for every UITextField* (it apparently is overridden by the cells frame when its displayed) so when you invoke the 'scrollRectToVisible' code it doesn't seem to have the correct coordinates to which it should scroll.
1) create cell, and add UITextField* to the cell using cell's frame x and y values (I'm including offsets here which are optional
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell* cell;
cell = [tableView dequeueReusableCellWithIdentifier:#"UITableViewCell"];
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:#"UITableViewCell"] autorelease];
//this is the critical part: make sure your UITextField* frame is based on the frame of the cell in which it's being placed.
UITextField* txtField = [[UITextField alloc] initWithFrame:CGRectMake(cell.frame.origin.x+20, cell.frame.origin.y+9, 280, 31)];
txtField.delegate = self;
[cell addSubview:txtField];
return cell;
}
2) adjust scroll view in textFieldDidBeginEditing
-(void) textFieldDidBeginEditing:(UITextField *)textField {
CGRect textFieldRect = [textField frame];
[self.tableView scrollRectToVisible:textFieldRect animated:YES];
}
The problem is aggravated by the fact that there is no simple way to find out whether user tapped on text field or it was activated via becomeFirstResponder.
The most elegant solution I could come up with was to implement a hitTest:withEvent: on cell subclass and basically pretend that text field does not exist until cell is selected.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *view = [super hitTest:point withEvent:event];
if(view == self.textField && !self.selected) {
return self;
}
return view;
}
tableView:didSelectRowAtIndexPath: then should manually make text field a first responder.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
TextFieldCell* cell = [self.tableView cellForRowAtIndexPath:indexPath];
[cell.textField becomeFirstResponder]
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
Finally, we have to deselect the row when we finish editing. This can be done via UITextField delegate or via keyboard notification, whatever you prefer.
- (void)textFieldDidEndEditing:(UITextField *)textField {
[self.view endEditing:YES];
}
we have one controller called TPKeyboardAvoiding, it handled everything about dynamic auto scrolling for tableview and scrollview.
you can download sample code from below code.
https://github.com/NarayanaRao35/TPKeyboardAvoiding