Get UITableView's height - iphone

I have created a UITableview with custom UITableViewCell.The UITableViewCell contains UILabels and I have calculated the height of each cell based on the cells height.But how can I get the tableview height?Because based on the tableView's height I need to calculate
the height of scrollView
I have created a UITableView like this when a button is clicked:
-(IBAction)btnClicked:(id)sender
{
self.tableView=[[UITableView alloc] initWithFrame:CGRectMake(0,150,327,[arr5 count]*205) style:UITableViewStylePlain];
self.tableView.delegate=self;
self.tableView.dataSource=self;
self.tableView.scrollEnabled = NO;
[self.view addSubview:self.tableView];
float fscrview = 150 + self.tableView.frame.size.height + 20;
testscroll.contentSize=CGSizeMake(320, fscrview);
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *city1 = city.text;
UIFont *font = [UIFont fontWithName:#"Helvetica" size:14.0];
CGSize constraintSize = CGSizeMake(280.0f, MAXFLOAT);
CGSize bounds = [city1 sizeWithFont:font constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
//similarly calculated for all
return (CGFloat) cell.bounds.size.height + bounds.height+bounds1.height+bounds2.height+bounds3.height+bounds4.height+bounds5.height;
}
I am able to get tableViewCells height through this.How do I calculate/set the overall tableView's height after this?
And how to calculate ScrollView's height based on tableView's row/height?

Use contentSize.height property of UITableView.
I think you want to set the whole tableview with content size and then set the scrollview size related content of UITableView and for this use bellow code...
After add data or reloadData in UITableView just set bellow code..
yourTableView.frame = CGRectMake(yourTableView.frame.origin.x,yourTableView.frame.origin.y, yourTableView.frame.size.width, yourTableView.contentSize.height);
float ftbl = yourTableView.frame.origin.y + yourTableView.contentSize.height + 15;
yourScrollView.contentSize=CGSizeMake(320, ftbl);
UPDATE:
self.tableView=[[UITableView alloc] initWithFrame:CGRectMake(0,150,327,[arr5 count]*205)style:UITableViewStylePlain];
self.tableView.delegate=self;
self.tableView.dataSource=self;
[testscroll addSubview:self.tableView]; /// add this tableview in scrollview not in self.view
[self.tableView reloadData];
self.tableView.frame = CGRectMake(self.tableView.frame.origin.x, self.tableView.frame.origin.y, self.tableView.frame.size.width, self.tableView.contentSize.height);
float ftbl = self.tableView.frame.origin.y + self.tableView.contentSize.height + 15;
testscroll.contentSize=CGSizeMake(320, ftbl);

Very simplest way to get your UITableView height
- (CGFloat)tableViewHeight
{
[tblData layoutIfNeeded];
return [YOUR_TABLE_NAME contentSize].height;
}

on iOS 8+ this worked to get the table view Height
CGFloat tableViewHeight = tableView.bounds.size.height;

Try this: tableView.backgroundView.bounds.size.height Should work
Logic:
UITableView has a property, backgroundView which is "A table view’s background view is automatically resized to match the size of the table view."
backgroundView is a UIView which has the property bounds which is a CGRect that "defines the size and position of the view."
CGRect has a size property, size has a height property
QED

You also can use KVO to observer tableview's contentSize property and adjust what you need in other views.
Put it somewhere, e.g. in viewDidLoad:
[self.tableView addObserver:self forKeyPath:#"contentSize" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionInitial context:nil];
Then implement observer:
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (object == self.tableView) {
self.scrollViewHeightConstraint.constant = self.tableView.contentSize.height;
}
else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

Related

Is it possible to add fixed content to a UIScrollView?

I want to create a subclass of UITableView or UIScrollView that will have some shading at the top when the content offset is > 0 to indicate that the content is scrollable. (See image attached)
The way I'm implementing it right now is using the UIViewController that is the delegate of the tableView. I simply have a GradientView on top of the tableView, and I intercept scrollViewDidScroll: to animate the visibility of that top gradient.
My problem with this implementation is that it's not "clean". I want my UIViewControllers to take care of logic, and not to deal with applying gradients and stuff. I wish I could just drop a subclass of UITableView that will do that for me.
The challenge for me is that I can't figure out how the tableView could add to itself a fixed content on top of the scrollable content.
Another question is what method/s of UIScrollView should I override to intercept the scrolling event. Obviously I don't want the tableView to be the delegate of itself...
Any ideas?
Thanks!
Ok, so I found the solution on Apple's WWDC 2011 Session 104 video - Advanced Scroll View Techniques.
There is a whole section in this video about "Stationary Views" inside a scroll view.
According to Apple, the way to go here is to override layoutSubviews and put there all the code to position whatever you want - wherever you want.
I tried it and it's actually pretty easy and it's working as expected.
So for example if I would like a shadowed header on top of the table when the content is being scrolled, this is the code I should write:
-(void) layoutSubviews
{
[super layoutSubviews];
[self positionTopShadow];
}
-(void) positionTopShadow
{
CGFloat yOffset = self.contentOffset.y;
// I'm doing some limiting so that the maximum height of the shadow view will be 40 pixels
yOffset = MIN(yOffset, 40);
yOffset = MAX(0, yOffset);
CGRect frame = self.topShadowView.frame;
// The origin should be exactly like the content offset so it would look like
// the shadow is at the top of the table (when it's actually just part of the content)
frame.origin = CGPointMake(0, self.contentOffset.y);
frame.size.height = yOffset;
frame.size.width = self.frame.size.width;
self.topShadowView.frame = frame;
if (self.topShadowView.superview == nil)
{
[self addSubview:self.topShadowView];
}
[self bringSubviewToFront:self.topShadowView];
}
I've managed to figure out a much simpler way of doing this then what Avraham did.
I use the fact that the UIScrollView calls scrollViewDidScroll: ever pixel the scrolling changes to set the object at the location of the offset. Below is my full code to keep a gray bar at the top of the scrollview as you move around:
- (void)viewDidLoad {
UIScrollView* scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(5.0, 50.0, self.bounds.size.width - 15.0, self.bounds.size.height - 60.0)];
[scrollView setBackgroundColor:[UIColor colorWithRed:251.0/255.0 green:251.0/255.0 blue:251.0/255.0 alpha:1.0]];
[scrollView setContentSize:CGSizeMake(scrollView.frame.size.width + 500, 1000.0)];
[scrollView setDelegate:self];
[self addSubview:scrollView];
UIView* header = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, scrollView.contentSize.width, 40.0)];
[header setTag:100];
[header setBackgroundColor:[UIColor darkGrayColor]];
[scrollView addSubview:header];
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
UIView* header = [self viewWithTag:100];
[header setFrame:CGRectMake(0.0, scrollView.contentOffset.y, header.bounds.size.width, header.bounds.size.height)];
}
You could try using viewForHeaderInSection method of tableView for the shaded view(and also heightForHeaderInSection)... Make the shaded portion as a header.That way there is a fixed content on top of the scrollable content.
#define kImageOriginHight 300
- (void)scrollViewDidScroll:(UIScrollView *)scrollView1{
CGFloat yOffset = scrollView1.contentOffset.y;
// NSLog(#" y offset := %f", yOffset);
//zoom images and hide upper view while scrooling to down position
if (yOffset < 0) {//-kImageOriginHight
CGRect f = imgV.frame;
f.origin.y = yOffset;
f.size.height = -yOffset + kImageOriginHight;
imgV.frame = f;
//viewTableUpperView.alpha = 1.5 - (yOffset/-kImageOriginHight);
//viewTableUpperView.userInteractionEnabled = NO;
if(yOffset+0.5 == -kImageOriginHight){
[UIView animateWithDuration:0.1 animations:^{
//viewTableUpperView.alpha = 1.0;
}];
//viewTableUpperView.userInteractionEnabled = YES;
}
}
}

Override automatic scrolling in UITextView?

I have a view in a note-taking application I'm creating that is composed of a UITableView with a UITextView as a custom UITableViewCell (among other things). I know that having a UIScrollView within another UIScrollView is generally not recommended but I don't see a better way to do it for my purposes.
At any rate, the issue I'm having right now is that the UITextView, despite having scrolling disabled, is still affecting the scrolling of the UITableView itself. In other words, when I enter text into the UITextView or select it, the UITableView is scrolled automatically by the UITextView. Sometimes it works as desired, scrolling appropriately to the selected text, but more often than not it does not, generally scrolling to the bottom of the UITextView cell (which is not at all what I want). So I've been trying to handle the scrolling manually or finding a way to make it work automatically, but so far I've had no luck.
I've been searching for solutions here and on Google for days but I haven't come up with anything that works. I've tried simply scrolling to the desired area in the textViewDidBeginEditing method or by intercepting the UIKeyboardWillShowNotification notification, but neither produces the desired effect - the former scrolls after the UITextView has already automatically scrolled, the latter before (and so the automatic scrolling still occurs either way).
I've also tried subclassing the UITextView being used in the cell and overriding all scroll methods (ie. scrollRectToVisible), but this does not appear to prevent the automatic scrolling either. I've tried adjusting the frame of the UITableView when the UITextView is in use, which only made things worse.
So the conclusion I've come to is that I have to somehow disable all automatic scrolling in the UITextView, or prevent the two from communicating somehow. I've been trying to find a way to do this though and I haven't had any luck so far. Does anyone know how to do this or have an alternative solution to my issue? I'd really appreciate any help possible here.
Below is the code I think pertains to this issue. I'd be happy to provide any more if it helps. Also, this is my first iOS application, so forgive me if there is some poor technique in my code or if there is something I've overlooked.
UITableViewController:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
switch (indexPath.row) {
case 0:
cell = [self cellForTitleCell:indexPath withTableView:tableView];
break;
case 1:
cell = [self cellForBodyCell:indexPath withTableView:tableView];
break;
default:
cell = [self cellForListCell:indexPath withTableView:tableView];
break;
}
return cell;
}
- (UITableViewCell *)cellForBodyCell:(NSIndexPath *)indexPath withTableView: (UITableView *) tableView
{
static NSString *CellIdentifier = #"NoteViewBodyCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
NSString *bodyText = note.Body;
float frameHeight = [self tableView:tableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
frameHeight += self.navigationController.navigationBar.frame.size.height;
frameHeight += self.navigationController.toolbar.frame.size.height;
if (bodyTextView == nil)
{
bodyTextView = [[BodyTextView alloc] initWithFrame:CGRectMake(0, 0, 320, 460 - frameHeight)];
bodyTextView.font = [UIFont systemFontOfSize:17.0];
bodyTextView.text = bodyText;
bodyTextView.textColor = [UIColor blackColor];
bodyTextView.editable = YES;
bodyTextView.delegate = self;
bodyTextView.tag = 6;
CGSize bodySize = [bodyTextView.text sizeWithFont:bodyTextView.font
constrainedToSize:CGSizeMake(self.tableView.bounds.size.width-20, 9999)
lineBreakMode:UILineBreakModeWordWrap];
CGRect bodyFrame = bodyTextView.frame;
if (bodySize.height < 460 - frameHeight)
bodySize.height = 460 - frameHeight;
bodyFrame.size.height = bodySize.height;
bodyTextView.frame = bodyFrame;
bodyTextView.scrollEnabled = NO;
}
[cell.contentView addSubview:bodyTextView];
cell.frame = bodyTextView.frame;
bodyTextView.inputAccessoryView = keyboardToolbar;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat rowHeight;
CGSize bodyTextSize;
switch (indexPath.row) {
case 0:
rowHeight = 40;
break;
case 1:
if ([self isStringEmpty:bodyTextView.text])
bodyTextSize = [note.Body sizeWithFont:[UIFont systemFontOfSize:17.0] constrainedToSize:CGSizeMake(self.tableView.bounds.size.width-20, 9999) lineBreakMode:UILineBreakModeWordWrap];
else
bodyTextSize = bodyTextView.contentSize;
if (bodyTextSize.height < 460 - 128)
rowHeight = 460 - 128;
else
rowHeight = bodyTextSize.height;
break;
default:
rowHeight = 40;
break;
}
return rowHeight;
}
- (void)textViewDidChange:(UITextView *)textView
{
[self.tableView beginUpdates];
[self.tableView endUpdates];
CGRect bodyFrame = bodyTextView.frame;
bodyFrame.size.height = bodyTextView.contentSize.height;
if (bodyFrame.size.height < 460 - 128)
bodyFrame.size.height = 460 - 128;
bodyTextView.frame = bodyFrame;
}
UITextView (subclass):
- (id)init
{
self = [super init];
if (self) {
// Initialization code here.
self.scrollEnabled = NO;
}
return self;
}
- (void) scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
{
// do nothing
}
- (UIEdgeInsets) contentInset
{
return UIEdgeInsetsZero;
}
- (void)scrollRangeToVisible:(NSRange)range
{
// do nothing
}
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
{
// do nothing
}
- (BOOL) isScrollEnabled
{
return NO;
}
Some methods I've tried (found on StackOverflow):
// tried calling this method in both textViewDidBeginEditing and keyboardWillShow
- (BOOL)scrollToCursor
{
// if there is a selection cursor…
if(bodyTextView.selectedRange.location != NSNotFound)
{
NSLog(#"selectedRange: %d %d", bodyTextView.selectedRange.location, bodyTextView.selectedRange.length);
// work out how big the text view would be if the text only went up to the cursor
NSRange range;
range.location = bodyTextView.selectedRange.location;
range.length = bodyTextView.text.length - range.location;
NSString *string = [bodyTextView.text stringByReplacingCharactersInRange:range withString:#""];
CGSize size = [string sizeWithFont:bodyTextView.font constrainedToSize:bodyTextView.bounds.size lineBreakMode:UILineBreakModeWordWrap];
// work out where that position would be relative to the textView's frame
CGRect viewRect = bodyTextView.frame;
int scrollHeight = viewRect.origin.y + size.height;
CGRect finalRect = CGRectMake(1, scrollHeight, 1, 1);
// scroll to it
[self.tableView scrollRectToVisible:finalRect animated:YES];
return YES;
}
else
{
NSLog(#"NO CURSOR");
return NO;
}
}
- (void) keyboardWillShow:(NSNotification *)notification
{
if ([bodyTextView isFirstResponder])
{
NSLog(#"starting to change scroll view");
// Get the keyboard size
CGRect keyboardBounds;
[[notification.userInfo valueForKey:UIKeyboardFrameBeginUserInfoKey] getValue: &keyboardBounds];
// Detect orientation
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
CGRect frame = self.tableView.frame;
// Start animation
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.3f];
// Reduce size of the Table view
if (orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown)
frame.size.height -= keyboardBounds.size.height;
else
frame.size.height -= keyboardBounds.size.width;
frame.size.height += keyboardToolbar.frame.size.height;
// Apply new size of table view
self.tableView.frame = frame;
// Scroll the table view to see the TextField just above the keyboard
if (self.bodyTextView)
{
CGRect textViewRect = [self.tableView convertRect:bodyTextView.bounds fromView:bodyTextView];
[self.tableView scrollRectToVisible:textViewRect animated:NO];
}
[UIView commitAnimations];
}
isKeyboardVisible = YES;
}
- (void) keyboardWillHide:(NSNotification *)notification
{
if ([bodyTextView isFirstResponder])
{
// Get the keyboard size
CGRect keyboardBounds;
[[notification.userInfo valueForKey:UIKeyboardFrameBeginUserInfoKey] getValue: &keyboardBounds];
// Detect orientation
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
CGRect frame = self.tableView.frame;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.3f];
// Reduce size of the Table view
if (orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown)
frame.size.height += keyboardBounds.size.height;
else
frame.size.height += keyboardBounds.size.width;
// Apply new size of table view
self.tableView.frame = frame;
[UIView commitAnimations];
}
isKeyboardVisible = NO;
}
I believe most of the UITableView's automatic scrolling code resides in the UITableViewController. For example, if you have a UITextField in one of the table rows that is towards the bottom of the screen, the table automatically scrolls upwards to make way for the keyboard when the user taps on the UITextField. As you want to minimize auto-scrolling, I would suggest not using the UITableViewController. Instead, just subclass UIViewController & make it implement UITableViewDelegate & UITableViewDataSource. That being said, you would also miss out on some of the good things that UITableViewController provides.
HTH,
Akshay
Is bodyTextView the text view that you are trying to scroll/not scroll? If so, are you setting it's delegate somewhere else in your code? Meaning, do you have something like bodyTextView.delegate = self; somewhere? If you haven't set its delegate, that may be while it's not responding to some of your code. To be able to do this, subscribe to the text view delegate by adding <UITextViewDelegate> to your header file, along with setting its delegate as I explained.

Labels aligning in UITableViewCell

I use UITableView with cells created using UITableViewCellStyleSubtitle. Every cell's height is dynamically adjusted using the
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
delegate method.
The problem you can see on the picture
(note: image updated)
http://img.skitch.com/20090715-g7srxgee2d7fhi5rab2wufrdgm.png
How to set up align of textLabel and detailTextLabel to the top of the cell? (I really don't wont to do it by subclassing UITableViewCell and overriding layoutSubviews)
Thanx
Well, this one cost me an afternoon, but I think I figured it out. As far as I can tell, this appears to be a bug in how UITableViewCell is laying out the textLabel and detailTextLabel. When you set the row height, it seems to allocate equal height to the two labels, which means that you get exactly the behavior you're seeing above, even though detailTextLabel needs more room. Here are the two things I did to fix the problem. I had to subclass UITableViewCell to fix it, but it's a minimal amount of code.
First, make sure you're calculating the height of each row properly. Put this method into your table view delegate. Replace the font methods with your own:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellDetailText = [[self itemForIndexPath: indexPath] detailDescription];
NSString *cellText = [[self itemForIndexPath: indexPath] description];
// The width subtracted from the tableView frame depends on:
// 40.0 for detail accessory
// Width of icon image
// Editing width
// I don't think you can count on the cell being properly laid out here, so you've
// got to hard code it based on the state of the table.
CGSize constraintSize = CGSizeMake(tableView.frame.size.width - 40.0 - 50.0, CGFLOAT_MAX);
CGSize labelSize = [cellText sizeWithFont: [self cellTextLabelFont] constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
CGSize detailSize = [cellDetailText sizeWithFont: [self cellDetailTextLabelFont] constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
CGFloat result = MAX(44.0, labelSize.height + detailSize.height + 12.0);
return result;
}
Then, subclass UITableViewCell and override layoutSubviews:
#import "UITableViewCellFixed.h"
#implementation UITableViewCellFixed
- (void) layoutSubviews {
[super layoutSubviews];
self.textLabel.frame = CGRectMake(self.textLabel.frame.origin.x,
4.0,
self.textLabel.frame.size.width,
self.textLabel.frame.size.height);
self.detailTextLabel.frame = CGRectMake(self.detailTextLabel.frame.origin.x,
8.0 + self.textLabel.frame.size.height,
self.detailTextLabel.frame.size.width,
self.detailTextLabel.frame.size.height);
}
#end
Here is another solution without subclassing cells, so it certainly works with different table styles. (I haven't checked other solutions.) The title and detail strings are declared in my UITableViewController header and already defined. They aren''t very long, certainly well within height 4000!
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CGRect frame = [UIScreen mainScreen].bounds;
CGFloat width = frame.size.width;
int section = indexPath.section;
int row = indexPath.row;
NSString *title_string = [title_strings_array objectAtIndex:row];
NSString *detail_string = [detail_strings_array objectAtIndex:row];
CGSize title_size = {0, 0};
CGSize detail_size = {0, 0};
if (title_string && [title_string isEqualToString:#""] == NO ) {
title_size = [title_string sizeWithFont:[UIFont systemFontOfSize:22.0]
constrainedToSize:CGSizeMake(width, 4000)
lineBreakMode:UILineBreakModeWordWrap];
}
if (detail_string && [title_string isEqualToString:#""] == NO ) {
detail_size = [detail_string sizeWithFont:[UIFont systemFontOfSize:18.0]
constrainedToSize:CGSizeMake(width, 4000)
lineBreakMode:UILineBreakModeWordWrap];
}
CGFloat title_height = title_size.height;
CGFloat detail_height = detail_size.height;
CGFloat content_size = title_height + detail_height;
CGFloat height;
switch ( section ) {
case 0:
height = content_size;
break;
//Just in case
default:
height = 44.0;
break;
}
return height;
}
However you're sizing your cells, you should do it with the various sizing methods of NSString. That way, you can determine exactly how tall to make the cell and avoid the whitespace.
If it turns out that the textLabel and detailTextLabel are laid out using autoresizing masks, maybe you can do this when you return the cell:
cell.textLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin;
cell.detailTextLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
If this works, it's a bit easier than subclassing the cell. I haven't tried it though.

iPhone: scroll table view cell to visible above custom keyboard-aligned toolbar?

I've been asking a question or two over the past few days of working on an application that keeps a custom toolbar aligned to the top of the iPhone keyboard. I'm using the method described by Josh in this question; basically, I have the view controller listen for the UIKeyboardWillShowNotification and add the toolbar as necessary.
The view controller itself manages a table view whose cells all contain a UITextField. The keyboard and toolbar being presented are editing these text fields. The only issue I'm still having is this: when the keyboard and toolbar are presented for a cell more than about halfway down the table, it scrolls to be visible above the keyboard, but not above the toolbar.
The cells and text fields are still editable, but about half the cell is hidden under the toolbar. If I disappear the toolbar (don't add it in the notification responder), the entire cell becomes visible, but obviously I lose the functionality the toolbar provides.
Is there a way to change the location the text field gets scrolled to? I've tried playing around with the UITableView method scrollToRowAtIndexPath:atScrollPosition:animated:, but it tends to give weird results when toggling through several cells.
What's the best method for scrolling a table view cell to a visible position above a custom keyboard toolbar?
I do exactly what you are describing in my app. This is the code I use, verbatim. It's very nicely animated, and seems to work wonderfully.
Two disclaimers:
I pass in parentView as part of a custom initialization.
I did not write this code, I'm not taking credit for it. I got it from Matt Gallagher's truly wonderful blog Cocoa With Love.
static const CGFloat KEYBOARD_ANIMATION_DURATION = 0.3;
static const CGFloat MINIMUM_SCROLL_FRACTION = 0.2;
static const CGFloat MAXIMUM_SCROLL_FRACTION = 0.8;
// adjust this following value to account for the height of your toolbar, too
static const CGFloat PORTRAIT_KEYBOARD_HEIGHT = 216;
- (void)textFieldDidEndEditing:(UITextField *)textField
{
CGRect viewFrame = self.parentView.frame;
viewFrame.origin.y += animatedDistance;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:KEYBOARD_ANIMATION_DURATION];
[self.parentView setFrame:viewFrame];
[UIView commitAnimations];
}
- (void) textFieldDidBeginEditing:(UITextField*) textField {
CGRect textFieldRect = [self.parentView.window convertRect:textField.bounds fromView:textField];
CGRect viewRect = [self.parentView.window convertRect:self.parentView.bounds fromView:self.parentView];
CGFloat midline = textFieldRect.origin.y + 0.5 * textFieldRect.size.height;
CGFloat numerator = midline - viewRect.origin.y - MINIMUM_SCROLL_FRACTION * viewRect.size.height;
CGFloat denominator = (MAXIMUM_SCROLL_FRACTION - MINIMUM_SCROLL_FRACTION) * viewRect.size.height;
CGFloat heightFraction = numerator / denominator;
if (heightFraction < 0.0) {
heightFraction = 0.0;
}else if (heightFraction > 1.0) {
heightFraction = 1.0;
}
animatedDistance = floor(PORTRAIT_KEYBOARD_HEIGHT * heightFraction);
CGRect viewFrame = self.parentView.frame;
viewFrame.origin.y -= animatedDistance;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:KEYBOARD_ANIMATION_DURATION];
[self.parentView setFrame:viewFrame];
[UIView commitAnimations];
}
Assuming that you know the NSIndexPath that you would show above the keyboard, you can use the following fast and clean method:
- (void)textFieldDidBeginEditing:(UITextField *)textField {
UITableViewCell *aCell = [myTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:6 inSection:0]];
CGSize cellSize = aCell.frame.size;
[myTableView setContentOffset:CGPointMake(0, cellSize.height*6) animated:YES];
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
[myTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
atScrollPosition:UITableViewScrollPositionTop
animated:YES];
}
Since the UITableView inherits from UIScrollView you can adjust the contentOffset property to reflect the row visibility in which you are interested.
In the DidEndEditing method, you have to restore the original scroll position. This is achieved with a standard scrollToRowAtIndexPath:atScrollPosition:animated call.
Maybe you could resize the UITableView to be the height between the navigation bar and the toolbar above the keyboard, and then scroll the row into view?
Since all UITableViews are also UIScrollViews, you can call all of the standard scroll methods on them.
Something like this will do:
- (void)scrollTableView:(UITableView *)tableView toIndexPath:(NSIndexPath *)indexPath withBottomPadding:(CGFloat)bottomPadding
{
CGRect rectForRow = [tableView rectForRowAtIndexPath:indexPath];
CGRect bounds = [tableView bounds];
CGPoint contentOffset = [tableView contentSize];
if (rectForRow.origin.y + rectForRow.size.height > contentOffset.y + bounds.size.height - bottomPadding)
[tableView setContentOffset:rectForRow.origin.y + rectForRow.size.height - bounds.size.height - bottomPadding animated:YES];
else
[tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionNone animated:YES];
}
Note: I'm not at a Mac with Xcode at the moment. I will test this code when I am

How to resize a tableHeaderView of a UITableView?

I'm having trouble resizing a tableHeaderView. It simple doesn't work.
1) Create a UITableView and UIView (100 x 320 px);
2) Set the UIView as tableHeaderView of the UITableView;
3) Build and Go. Everything is ok.
Now, I want to resizing the tableHeaderView, so I add this code in viewDidLoad:
self.tableView.autoresizesSubviews = YES;
self.tableView.tableHeaderView = myHeaderView;
self.tableView.tableFooterView = myFooterView;
CGRect newFrame = self.tableView.tableHeaderView.frame;
newFrame.size.height = newFrame.size.height + 100;
self.tableView.tableHeaderView.frame = newFrame;
The height of the tableHeaderView should appear with 200, but appears with 100.
If I write:
self.tableView.autoresizesSubviews = YES;
CGRect newFrame = myHeaderView.frame;
newFrame.size.height = newFrame.size.height + 100;
myHeaderView.frame = newFrame;
self.tableView.tableHeaderView = myHeaderView;
self.tableView.tableFooterView = myFooterView;
Then it starts with 200 of height, as I want. But I want to be able to modify it in runtime.
I've also tried this, without success:
self.tableView.autoresizesSubviews = YES;
self.tableView.tableHeaderView = myHeaderView;
self.tableView.tableFooterView = myFooterView;
CGRect newFrame = self.tableView.tableHeaderView.frame;
newFrame.size.height = newFrame.size.height + 100;
self.tableView.tableHeaderView.frame = newFrame;
[self.tableView.tableHeaderView setNeedsLayout];
[self.tableView.tableHeaderView setNeedsDisplay];
[self.tableView setNeedsLayout];
[self.tableView setNeedsDisplay];
The point here is: How do we resize a tableHeaderView in runtime ???
Have anyone able to do this?
Thanks
iMe
FYI: I've gotten this to work by modifying the tableHeaderView and re-setting it. In this case, i'm adjusting the size of the tableHeaderView when the UIWebView subview has finished loading.
[webView sizeToFit];
CGRect newFrame = headerView.frame;
newFrame.size.height = newFrame.size.height + webView.frame.size.height;
headerView.frame = newFrame;
[self.tableView setTableHeaderView:headerView];
This answer is old and apparently doesn't work on iOS 7 and above.
I ran into the same problem, and I also wanted the changes to animate, so I made a subclass of UIView for my header view and added these methods:
- (void)adjustTableHeaderHeight:(NSUInteger)newHeight{
NSUInteger oldHeight = self.frame.size.height;
NSInteger originChange = oldHeight - newHeight;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1.0f];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:context:)];
self.frame = CGRectMake(self.frame.origin.x,
self.frame.origin.y,
self.frame.size.width,
newHeight);
for (UIView *view in [(UITableView *)self.superview subviews]) {
if ([view isKindOfClass:[self class]]) {
continue;
}
view.frame = CGRectMake(view.frame.origin.x,
view.frame.origin.y - originChange,
view.frame.size.width,
view.frame.size.height);
}
[UIView commitAnimations];
}
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context{
[(UITableView *)self.superview setTableHeaderView:self];
}
This essentially animates all the subviews of the UITableView that aren't the same class type as the calling class. At the end of the animation, it calls setTableHeaderView on the superview (the UITableView) – without this the UITableView contents will jump back the next time the user scrolls. The only limitation I've found on this so far is if the user attempts to scroll the UITableView while the animation is taking place, the scrolling will animate as if the header view hasn't been resized (not a big deal if the animation is quick).
If you want to conditionally animate the changes you can do the following:
- (void) showHeader:(BOOL)show animated:(BOOL)animated{
CGRect closedFrame = CGRectMake(0, 0, self.view.frame.size.width, 0);
CGRect newFrame = show?self.initialFrame:closedFrame;
if(animated){
// The UIView animation block handles the animation of our header view
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.3];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
// beginUpdates and endUpdates trigger the animation of our cells
[self.tableView beginUpdates];
}
self.headerView.frame = newFrame;
[self.tableView setTableHeaderView:self.headerView];
if(animated){
[self.tableView endUpdates];
[UIView commitAnimations];
}
}
Please note that the animation is two-folded:
The animation of the cells below the tableHeaderView. This is done using beginUpdates and endUpdates
The animation of the actual header view. This is done using a UIView animation block.
In order to synchronize those two animations the animationCurve has to be set to UIViewAnimationCurveEaseInOut and the duration to 0.3, which seems to be what the UITableView uses for it's animation.
Update
I created an Xcode project on gihub, which does this.
Check out the project ResizeTableHeaderViewAnimated in besi/ios-quickies
I think it should work if you just set the height of myHeaderView like so:
CGRect newFrame = myHeaderView.frame;
newFrame.size.height = newFrame.size.height + 100;
myHeaderView.frame = newFrame;
self.tableView.tableHeaderView = myHeaderView;
Used #garrettmoon solution above until iOS 7.
Here's an updated solution based on #garrettmoon's:
- (void)adjustTableHeaderHeight:(NSUInteger)newHeight animated:(BOOL)animated {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:[CATransaction animationDuration]];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:context:)];
self.frame = CGRectMake(self.frame.origin.x,
self.frame.origin.y,
self.frame.size.width,
newHeight);
[(UITableView *)self.superview setTableHeaderView:self];
[UIView commitAnimations];
}
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context{
[(UITableView *)self.superview setTableHeaderView:self];
}
This worked for me on iOS 7 and 8. This code is running on the table view controller.
[UIView animateWithDuration:0.3 animations:^{
CGRect oldFrame = self.headerView.frame;
self.headerView.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, oldFrame.size.width, newHeight);
[self.tableView setTableHeaderView:self.headerView];
}];
Its because the setter of tableHeaderView.
You have to set the UIView height before set the tableHeaderView. (Would be much easier if Apple open sources this framework...)
On iOS 9 and below, tableHeaderView would not re-layout after resizing it.
This issue is resolved in iOS 10.
To solve this issue, just do it with the following code:
self.tableView.tableHeaderView = self.tableView.tableHeaderView;
On iOS 9.x, doing this on viewDidLoad works just fine:
var frame = headerView.frame
frame.size.height = 11 // New size
headerView.frame = frame
headerView is declared as #IBOutlet var headerView: UIView! and connected on the storyboard, where it is placed at the top of the tableView, to function as the tableHeaderView.
This is only for when you use auto-layout and set translatesAutoresizingMaskIntoConstraints = false to a custom header view.
The best and the simplest way is to override intrinsicContentSize. Internally UITableView uses intrinsicContentSize to decide its header/footer size. Once you have override intrinsicContentSize in your custom view, What you need to do is as below
configure the custom header/footer view's layout(subviews)
invoke invalidateIntrinsicContentSize()
invoke tableView.setNeedsLayout() and tableView.layoutIfNeeded()
Then the UITableView's header/footer will be updated as you want. No need to set the view nil or reset.
One thing really interesting for the UITableView.tableHeaderView or .tableFooterView is that UIStackView loose its ability to manage its arrangedSubviews. If you want to use UIStackView as a tableHeaderView or tableFooterView, you have to embed the stackView in a UIView and override UIView's intrinsicContentSize.
For swift 5 Tested code
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard let headerView = self.tblProfile.tableHeaderView else {
return
}
let size = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
if headerView.frame.size.height != size.height {
headerView.frame.size.height = size.height
self.tblProfile.tableHeaderView = headerView
self.tblProfile.layoutIfNeeded()
}
}
Note : You need to give all subview's constraints form top, bottom, leading, trailing. So it will get whole required size.
Reference taken from : https://useyourloaf.com/blog/variable-height-table-view-header/
Setting the height for header view property tableView.tableHeaderView in viewDidLoad seems not work, the header view height still not change as expected.
After fighting against this issue for many tries. I found that, you can change the height by invoking the header view create logic inside the
- (void)didMoveToParentViewController:(UIViewController *)parent method.
So the example code would look like this:
- (void)didMoveToParentViewController:(UIViewController *)parent {
[super didMoveToParentViewController:parent];
if ( _tableView.tableHeaderView == nil ) {
UIView *header = [[[UINib nibWithNibName:#"your header view" bundle:nil] instantiateWithOwner:self options:nil] firstObject];
header.frame = CGRectMake(0, 0, CGRectGetWidth([UIScreen mainScreen].bounds), HeaderViewHeight);
[_tableView setTableHeaderView:header];
}
}
If custom headerView is designed using autolayout and headerView needs to be updated after web-fetch or similar lazy task.
then in iOS-Swift I did this and got my headerView updated using bellow code:
//to reload your cell data
self.tableView.reloadData()
DispatchQueue.main.async {
// this is needed to update a specific tableview's headerview layout on main queue otherwise it's won't update perfectly cause reloaddata() is called
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
I found the initWithFrame initializer of a UIView doesn't properly honor the rect I pass in. Hence, I did the following which worked perfectly:
- (id)initWithFrame:(CGRect)aRect {
CGRect frame = [[UIScreen mainScreen] applicationFrame];
if ((self = [super initWithFrame:CGRectZero])) {
// Ugly initialization behavior - initWithFrame will not properly honor the frame we pass
self.frame = CGRectMake(0, 0, frame.size.width, 200);
// ...
}
}
The advantage of this is it is better encapsulated into your view code.
I have implemented animated height change of the table's header to expand to overall screen when tapped. However, the code can help in other cases:
// Swift
#IBAction func tapped(sender: UITapGestureRecognizer) {
self.tableView.beginUpdates() // Required to update cells.
// Collapse table header to original height
if isHeaderExpandedToFullScreen {
UIView.animateWithDuration(0.5, animations: { () -> Void in
self.scrollView.frame.size.height = 110 // original height in my case is 110
})
}
// Expand table header to overall screen
else {
let screenSize = self.view.frame // "screen" size
UIView.animateWithDuration(0.5, animations: { () -> Void in
self.scrollView.frame.size.height = screenSize.height
})
}
self.tableView.endUpdates() // Required to update cells.
isHeaderExpandedToFullScreen= !isHeaderExpandedToFullScreen // Toggle
}
UITableView resizing header - UISearchBar with Scope Bar
I wanted a UITableView with a UISearchBar as the header to the table so I have a hierarchy that looks like this
UITableView
|
|--> UIView
| |--> UISearchBar
|
|--> UITableViewCells
UISearchBarDelegate methods
As has been stated elsewhere, if you don't setTableViewHeader after changing it, nothing will happen.
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar
{
searchBar.showsScopeBar = YES;
[UIView animateWithDuration:0.2f animations:^{
[searchBar sizeToFit];
CGFloat height = CGRectGetHeight(searchBar.frame);
CGRect frame = self.tableView.tableHeaderView.frame;
frame.size.height = height;
self.tableHeaderView.frame = frame;
self.tableView.tableHeaderView = self.tableHeaderView;
}];
[searchBar setShowsCancelButton:YES animated:YES];
return YES;
}
- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar
{
searchBar.showsScopeBar = NO;
[UIView animateWithDuration:0.f animations:^{
[searchBar sizeToFit];
CGFloat height = CGRectGetHeight(searchBar.frame);
CGRect frame = self.tableView.tableHeaderView.frame;
frame.size.height = height;
self.tableHeaderView.frame = frame;
self.tableView.tableHeaderView = self.tableHeaderView;
}];
[searchBar setShowsCancelButton:NO animated:YES];
return YES;
}
Obviously, by now Apple should have implemented UITableViewAutomaticDimension for tableHeaderView & tableFooterView...
The following seems to work for me using layout contraint(s):
CGSize s = [ self systemLayoutSizeFittingSize : UILayoutFittingCompressedSize ];
CGRect f = [ self frame ];
f.size = s;
[ self setFrame : f ];
If your tableHeaderView is a content adjustable webView,you can try:
[self.webView.scrollView addObserver:self forKeyPath:#"contentSize" options:NSKeyValueObservingOptionNew context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
self.webView.height = self.webView.scrollView.contentSize.height;
self.tableView.tableHeaderView = self.webView;
}
I tested it on iOS9 and iOS11,worked well.
Did you try
[self.tableView reloadData] after changing the height?