UITableViewController scrolls too far with large table footer - iphone

I have a UITableViewController subclass used for entering settings for my app. I add custom buttons to the table footer by adding them to a view that I return in he call to tableView:viewForFooterInSection:.
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
CGRect viewRect = self.view.bounds;
float height = _settings.isNew ? 50.0 : 110.0;
float margin = (viewRect.size.width > 480.0) ? 44.0 : 10.0;
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, viewRect.size.width, height)];
GradientButton* button = [[GradientButton alloc] initWithFrame:CGRectMake(margin, 5, viewRect.size.width - margin * 2, 44)];
[view addSubview:button];
[button addTarget:self action:#selector(testConnectionClicked:) forControlEvents:UIControlEventTouchUpInside];
[button release];
if (!_settings.isNew)
{
// I add another button
}
return [view autorelease];
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
return _settings.isNew ? 50 : 110;
}
The whole point of subclassing from UITableViewController is to avoid problems with getting the cells scrolled into view when the keyboard appears.
This works mostly as it should, however when the edit moves to the last cell it seems to try to scroll the table footer into view. This means that it actually scrolls the editing textfield out of view when on iPhone in landscape view.

I solved this by adding an extra UIView below the tableview in my tableviewcontroller instead of setting some text in the footer of my last tableview section.
if you want that footer in multiple sections, this does not fix your problem of course.

Taking a hint from #ben-packard's answer, I discovered that the UITableViewController auto-scrolling isn't at fault; it's actually UITableView's -scrollToRowAtIndexPath:atScrollPosition:animated: that's causing the view to scroll too far. I have to imagine this behavior is intentional even though it's not documented, but in any case we can fix it by subclassing UITableView and overriding that method:
- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated {
if (scrollPosition == UITableViewScrollPositionNone)
[self scrollRectToVisible:[self rectForRowAtIndexPath:indexPath] animated:animated];
else
[super scrollToRowAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated];
}

Did you implement tableView:heightForFooterInSection? It sounds like that could be the problem. If you implement tableView:viewForFooterInSection you must also implement tableView:heightForFooterInSection.

I suspect that the problem lies in your implementation of the scrolling on keyboard, but you didn't post any code from it.
Instead of subclassing a UITableView, why don't you do something like scrollToRowAtIndexPath:atScrollPosition:animated: when you do something that displays the keyboard? I found a similar thread here.

I found that scrolling to the rect instead (even though it is derived from the same row) seems to work:
CGRect rect = [myTableView rectForRowAtIndexPath:myIndexPath];
[myTableView scrollRectToVisible:rect animated:YES];

Related

A subview of UITableCellView is shrunk when user does swipe-to-delete gesture

I have a UITableView with custom subviews in its cells. When a user does swipe-to-delete gesture on a cell my view is shrunk for a moment while the "Delete" button is being appeared.
As I understand it happens due to some system animation which takes place when the "Delete" button is to be appeared and my subview needs to become shorter. I set my subview's contentMode to UIViewContentModeRedraw and everything looks fine after animation is finished.
I looked how this works in build-in Mail application and everything is fine there.
How can I avoid this shrinking? Is it possible to change this animation to "fade out" or something? Or can I handle a moment when this animation is started to make my subview shorter right away?
SOLUTION:
Finally I found a solution by subclassing UITableViewCell and overriding layoutSubviews method in the following way:
- (void)layoutSubviews
{
[super layoutSubviews];
UIView *view = [self.contentView viewWithTag:101];
CATransition *animation = [CATransition animation];
animation.duration = 0.2f;
animation.type = kCATransitionFade;
[view.layer removeAllAnimations];
[view.layer addAnimation: animation forKey:#"deletingFade"];
}
Thanks!
You can use this delegate method:
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = (UITableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
// do what ever you need to change in your cell
return YES;
}
(OR)
You can utilize the following methods in TableViewCell:
- (void)willTransitionToState:(UITableViewCellStateMask)state
- (void)didTransitionToState:(UITableViewCellStateMask)state
You can change according to state if you are using customCell.
Override layoutSubviews method in UITableViewCell derived class and update positions for all your subviews accordingly to contentView.bounds.size of the cell.

Transparent View at the Top of a UITableView

When you open Mail on an iPhone and tap Edit, select an email and tap Move, a UITableView appears with all the folders you can put the mail in. At the top is a transparent view that shows the email you selected. When you move your finger down, the view stays in place, when you move it up, the cells are visible through the transparent view.
How did apple configure this view? I thought of two ways, but they both don't work:
The view is returned as the header view of the UITableView. Doesn't work because the view stays at the top even if the table view is moved down.
The view is static at the top, the frame of the table view starts at the bottom of the transparent view. This doesn't work because when the table view is moved up, it is visible through the transparent view.
Any ideas on how to recreate this effect?
You need to create your transparent view and then add it as a subview to the view controller so it's a sibling of the UITableView. You would do this in the viewDidLoad() method.
- (void)viewDidLoad
{
int viewHeight = 50;
// Assume myTableView is a UITableView variable
myTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height - 44) style:UITableViewStylePlain];
myTableView.scrollIndicatorInsets = UIEdgeInsetsMake(viewHeight, 0, 0, 0);
myTableView.contentInset = UIEdgeInsetsMake(viewHeight, 0, 0, 0);
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(0,0,self.view.frame.size.width,viewHeight)];
// Configure your view here.
myView.backgroundColor = [UIColor colorWithRed:0.0 green:0.7 blue:0.8 alpha:0.75];
[self.view addSubview:myTableView];
[self.view addSubview:myView];
[myView release];
}
You could also setup your view using an XIB, but I'll leave that as an exercise for you.
Edit: Removed the requirement for the the UITableView delegate methods and custom header view by using the contentInset property instead.
Note: see additional comments below.
For the XIB method:
Create a new UIViewController (with XIB)
Add to it your UITableView and your UIView.
Make them both subviews of the default UIView (called View) and make sure that they are added in the correct order. UITableView should be the first in the list, your view should be second (so your view is on top of the UITableView).
In the .m file implement the minimum for UITableViewDataSource and UITableViewDelegate:
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
In Interface Builder select the UITableView list and go to 'Connection Inspector'. There drag dataSource and delegate to 'File Owner'.
Save and run. You should be done now.

iOS Add UIView below UITableView

I have a UITableView, under which (not in the header!) I would like a new UIView. It's done in WhatsApp, in the Chats' tab (I' new in here so I can't add a picture!).
Any help will be greatly appreciated.
Regards
Create a parent UIView to contain you UITableView and the other UIView. Add these views as subviews to the parent view.
Edited Answer:
You could do this. Initialize 2 buttons or 2 views in..
In the cellForRowIndexPath :
UIView * superView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 45)];
[superView addSubview:view1];
[superView addSubview:view2];
// You should make sure that CGRectMake of view1 + view 2 = superView //
Check if (indexPath.row == lastRow)
{
[cell addSubview: superView]
}
Original Answer:
You should use
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
I think the exact answer you are looking for is available here !!
UITableView has a backgroundView property that you can set.

iPhone UITableView with a header area

I have a view that was created with all of the default UITableView stuff, but now I need to add a header area above where the UITableView is (so the UITableView will scroll normally, but the top 100px of the screen or so will have static header content). I don't see where I can resize the UITableView in IB, and am not sure how to do this.
Does anyone know?
You can use UITableViewDelegate methods to create a custom header view for a table and specify the height, namely tableView:viewForHeaderInSection: and tableView:heightForHeaderInSection:. You can add whatever you like to the view. Here's an example that adds a right aligned UILabel:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0,0,tableView.frame.size.width,30)];
UILabel *headerLabel = [[UILabel alloc] initWithFrame:CGRectMake(60, 0, headerView.frame.size.width-120.0, headerView.frame.size.height)];
headerLabel.textAlignment = NSTextAlignmentRight;
headerLabel.text = [titleArray objectAtIndex:section];
headerLabel.backgroundColor = [UIColor clearColor];
[headerView addSubview:headerLabel];
return headerView;
}
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 30.0;
}
Why don't you use the UITableView provided header?. As follow:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return #"My Title";
}
Additionally you may resize your table view in IB by dragging the borders.
When you add a UIView or one of its subclasses onto the UITableView using IB (just drag a UIView and drop it onto the UPPER part of UITableView of yours), it automatically adds that UIView component and makes it the "tableHeader" component.
Each UITableView has one tableHeader and one tableFooter component reserved...
This way the new view would be a part of the UITable, and scroll with it or appear/disappear or whatever you do to the table. You can change its hidden property if you need conditional behavior.
On the other hand, if you want the header view stay put, as the table scrolls, then it is better to make the table smaller and put the header above it as suggested in other answers...
I finally solved this problem the right way without changing the base class. The one answer to add the view to the parent nav controller is nice but the transitions look horrible.
The fix is actually easy. The trick is to create custom setter and getter for self.tableView property. Then, in loadView, you replace the view with a fresh UIView and add the tableView to it. Then you're free to add subviews around the tableView. Here's how it's done:
In header:
#interface CustomTableViewController : UITableViewController
{
UITableView *tableView;
}
In .m:
- (UITableView*)tableView
{
return tableView;
}
- (void)setTableView:(UITableView *)newTableView
{
if ( newTableView != tableView )
{
[tableView release];
tableView = [newTableView retain];
}
}
- (void)loadView {
[super loadView];
//save current tableview, then replace view with a regular uiview
self.tableView = (UITableView*)self.view;
self.view = [[UIView alloc] initWithFrame:self.tableView.frame];
[self.view addSubview:self.tableView];
//code below adds some custom stuff above the table
UIView *customHeader = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 20)];
customHeader.backgroundColor = [UIColor redColor];
[self.view addSubview:customHeader];
[customHeader release];
self.tableView.frame = CGRectMake(0, customHeader.frame.size.height, self.view.frame.size.width, self.view.frame.size.height - customHeader.frame.size.height);
}
- (void)viewDidUnload
{
self.tableView = nil;
[super viewDidUnload];
}
Enjoy!
You will have to embed the UITableView in a UIView alongwith another view (which you are referring to as header section).
So, the UIView will have 2 subviews. The header view followed by the table view.
UIView(parent)
UIView (header)
UITableView (table)
Hope this helps.
I like the answer from noodl_es (upvoted), because it provides the functionality and behavior you want, yet you don't have to worry about resizing the UITableView: that is handled for you automatically. However, the solution is best suitable only if the header information pertains specifically to the first section of the table (or if the table has only one section). If the table has more than one section, then the header of the second section will push away the header of the first section when scrolled up, and therefore the header view will not appear to pertain to the whole table.
Found a solution at iphonedevsdk
Instead of doing this:
[tableViewController.view addSubview:viewSubclass];
do this
[tableViewController.navigationController.view addSubview:viewSubclass];
Suppose to have your UITableViewController
#interface MXMTableViewController : UITableViewController <UITableViewDelegate,UIScrollViewDelegate> {
/// your table view interface here
}
and a xib with you simple UITableView defined yet in it, you can do as Mihir says overriding the loadView method like this:
- (void)loadView {
[super loadView];
UIView *mainView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
self.view = mainView;
[mainView release];
// Add Header View
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 36)];
headerView.backgroundColor = [UIColor redColor];
[self.view addSubview:headerView];
// now, move your table view down. Check you nib to choose
// the right Y-axis offset
CGRect f = tableView.frame;
f.origin.y += headerView.frame.size.height/2;
tableView.frame = f;
// Add the table view to the container view
[self.view addSubview:self.tableView];
// Add footer
UIView *footerView = [[UIView alloc] initWithFrame:CGRectMake(0, self.tableView.frame.size.height, 320, 125)];
footerView.backgroundColor = [UIColor redColor];
[self.view addSubview:footerView];
[footerView release];
[headerView release];
}
...and that's it. You have a UITableView with fixed header and footer.
PS. You may now use your xib custom views as the header and footer's views.

Clicking on UITextField in a UITableViewCell

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