In my custom table view cell subclass, the location of one of the textlabel depends on the content of an ivar (NSString). (i.e: if the NSString is the empty string, the location of the textlabel's frame is different).
The position if updated as follow:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
customOverlayCell *myCell = [self.tableView dequeueReusableCellWithIdentifier:#"CustomOverlayCell"];
if ([buildingFName isEqual:#""])
{
CGRect titleLabelFrame = myCell.titleLabel.frame;
titleLabelFrame.origin.y = 45;
[myCell.titleLabel setFrame:titleLabelFrame];
}
return myCell;
}
I have removed parts of code that weren't relevant.
The result is that the layout of the first cells that appear on the screen are properly updated, but the layout of the views that appear after scrolling down aren't updated.
Am I not using dequeueReusableCellWithIdentifier properly? Or is anything else wrong?
Edit:
Solution from EJV:
CGRect titleLabelFrame = myCell.titleLabel.frame;
if ([buildingFName isEqual:#""])
{
titleLabelFrame.origin.y = 45;
} else {
titleLabelFrame.origin.y = 37;
}
[myCell.titleLabel setFrame:titleLabelFrame];
If the frame of the title label is dynamic, then when you dequeue a cell from the table view, the frame could be in either of the two states (when buildingFName is empty and when it has characters). You need to make sure that you set the frame for when buildingFName is not empty. That way, the title label's frame will always be set correctly. So, you need code like this:
CGRect titleLabelFrame = myCell.titleLabel.frame;
if ([buildingFName isEqual:#""])
{
titleLabelFrame.origin.y = 45;
} else {
// Change titleLabelFrame
}
[myCell.titleLabel setFrame:titleLabelFrame];
I'm afraid, it would require subclassing the cell and implementing [UITableViewCell layoutSubviews] to properly lay out your subviews of the cell. This is how I did something similar for a switch table view cell:
- (void)layoutSubviews
{
CGFloat const ESCFieldPadding = 10.0f;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationBeginsFromCurrentState:YES];
// call super layout
[super layoutSubviews];
// obtain widths of elements
CGFloat contentWidth = self.contentView.frame.size.width;
CGFloat contentHeight = self.contentView.frame.size.height;
CGFloat switchWidth = self.switchView.frame.size.width;
CGFloat switchHeight = self.switchView.frame.size.height;
CGFloat labelWidth = contentWidth - (4 * ESCFieldPadding) - switchWidth;
// correctly position both views
self.textLabel.frame = CGRectMake(ESCFieldPadding, 0.0f,
labelWidth, contentHeight);
// it is needed to explicitly resize font as for some strange reason,
// uikit will upsize the font after relayout
self.textLabel.font = [UIFont boldSystemFontOfSize:[UIFont labelFontSize]];
CGRect switchFrame = self.switchView.frame;
switchFrame.origin = CGPointMake(contentWidth - ESCFieldPadding - switchWidth,
(contentHeight / 2) - (switchHeight / 2));
self.switchView.frame = CGRectIntegral(switchFrame);
[UIView commitAnimations];
}
try to disable autolayout from your cell
Related
I have been working on this for about 2 days, so i thought i share my learnings with you.
The question is: Is it possible to make the width of a cell in a grouped UITableView smaller?
The answer is: No.
But there are two ways you can get around this problem.
Solution #1: A thinner table
It is possible to change the frame of the tableView, so that the table will be smaller. This will result in UITableView rendering the cell inside with the reduced width.
A solution for this can look like this:
-(void)viewWillAppear:(BOOL)animated
{
CGFloat tableBorderLeft = 20;
CGFloat tableBorderRight = 20;
CGRect tableRect = self.view.frame;
tableRect.origin.x += tableBorderLeft; // make the table begin a few pixels right from its origin
tableRect.size.width -= tableBorderLeft + tableBorderRight; // reduce the width of the table
tableView.frame = tableRect;
}
Solution #2: Having cells rendered by images
This solution is described here: http://cocoawithlove.com/2009/04/easy-custom-uitableview-drawing.html
I hope this information is helpful to you. It took me about 2 days to try a lot of possibilities. This is what was left.
A better and cleaner way to achieve this is subclassing UITableViewCell and overriding its -setFrame: method like this:
- (void)setFrame:(CGRect)frame {
frame.origin.x += inset;
frame.size.width -= 2 * inset;
[super setFrame:frame];
}
Why is it better? Because the other two are worse.
Adjust table view width in -viewWillAppear:
First of all, this is unreliable, the superview or parent view controller may adjust table view frame further after -viewWillAppear: is called. Of course, you can subclass and override -setFrame: for your UITableView just like what I do here for UITableViewCells. However, subclassing UITableViewCells is a much common, light, and Apple way.
Secondly, if your UITableView have backgroundView, you don't want its backgroundView be narrowed down together. Keeping backgroundView width while narrow down UITableView width is not trivial work, not to mention that expanding subviews beyond its superview is not a very elegant thing to do in the first place.
Custom cell rendering to fake a narrower width
To do this, you have to prepare special background images with horizontal margins, and you have to layout subviews of cells yourself to accommodate the margins.
In comparison, if you simply adjust the width of the whole cell, autoresizing will do all the works for you.
To do this in Swift, which does not provide methods to set variables, you'll have to override the setter for frame. Originally posted (at least where I found it) here
override var frame: CGRect {
get {
return super.frame
}
set (newFrame) {
let inset: CGFloat = 15
var frame = newFrame
frame.origin.x += inset
frame.size.width -= 2 * inset
super.frame = frame
}
}
If nothing works you can try this
Make the background colour of the cell as clear color and then put an image of the cell with required size. If you want to display some text on that cell put a label above the image. Don't forget to set the background color of the label also to clear color.
I found the accepted solution didn't work upon rotation. To achieve UITableViewCells with fixed widths & flexible margins I just adapted the above solution to the following:
- (void)setFrame:(CGRect)frame {
if (self.superview) {
float cellWidth = 500.0;
frame.origin.x = (self.superview.frame.size.width - cellWidth) / 2;
frame.size.width = cellWidth;
}
[super setFrame:frame];
}
The method gets called whenever the device rotates, so the cells will always be centered.
There is a method that is called when the screen is rotated : viewWillTransitionToSize
This is where you should resize the frame. See example. Change the frame coords as you need to.
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[coordinator animateAlongsideTransition:nil completion:^(id<UIViewControllerTransitionCoordinatorContext> context)
{
int x = 0;
int y = 0;
self.tableView.frame = CGRectMake(x, y, 320, self.tableView.frame.size.height);
}];
}
i do it in
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
CGFloat tableBorderLeft = self.view.frame.origin.x + 10;
CGFloat tableBorderRight = self.view.frame.size.width - 20;
CGRect tableRect = self.view.frame;
tableRect.origin.x = tableBorderLeft;
tableRect.size.width = tableBorderRight;
tableView.frame = tableRect;
}
And this worked for me
In .h file add the delegate 'UITableViewDataSource'
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return size;
}
I have a UITableView where each cell contains one UIView in its contentView, let's call it V.
V also has subViews, one UIImageView and UILabel.UIImage is just a white rounded rectangle
I want my cell (along with UIImageView) to expand and shrink when selected. I added some code to didSelectRowAtIndexPath and heightForRowAtIndexPath methods.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSNumber * key = [NSNumber numberWithInt:indexPath.row];
UITableViewCell *cell = [cells objectAtIndex:indexPath.row];
UIView * v = [[[cell contentView] subviews] objectAtIndex:0];
UIImageView * image = [[v subviews] objectAtIndex:0];
_tappedIndex = [key intValue];
_expanded = [_tappedCells objectForKey:key] == nil;
NSLog(#"view's old params: %#", [GGStackPanel printFrameParams:v]); //prints out the frame params
NSLog(#"image's old params: %#", [GGStackPanel printFrameParams:image]);
if (_expanded)
{
[_tappedCells setObject:key forKey:key];
v.frame = CGRectMake(v.frame.origin.x, v.frame.origin.y, v.frame.size.width, v.frame.size.height*1.5);
image.frame = CGRectMake(image.frame.origin.x, image.frame.origin.y, image.frame.size.width, v.frame.size.height + 73);
}
else
{
[_tappedCells removeObjectForKey:key];
v.frame = CGRectMake(v.frame.origin.x, v.frame.origin.y, v.frame.size.width, v.frame.size.height/1.5 );
image.frame = CGRectMake(image.frame.origin.x, image.frame.origin.y, image.frame.size.width, v.frame.size.height - 113);
}
NSLog(#"view's new params: %#", [GGStackPanel printFrameParams:v]);
NSLog(#"image's new params: %#", [GGStackPanel printFrameParams:image]);
[tableView beginUpdates];
[tableView endUpdates];
}
- (CGFloat)tableView:(UITableView *)tv heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *v = [_cells objectAtIndex:indexPath.row];
CGFloat height = v.frame.size.height;
if (indexPath.row == _tappedIndex)
{
if (_expanded)
{
height = v.frame.size.height * 1.5;
}
else
{
height = v.frame.size.height/1.5;
}
}
return height;
}
The cell frame and V's are expanding, I logged their frame parameters. But the imageView always stays the same, even if you change its frame at each cell selection. Its frame changes while in didSelectRowAtIndexPath method, but when you tap on the same cell again, it says that its frame hasn't change from last time.
If I put another UIView instead of an imageView, it expands and shrinks with its parent View and cell. Thus, I came up with really odd solution: cut the rounded rectangle, leave narrow top and bottom parts as imageView. Then put a blank UIView in the middle, with same color as a rectangle. Now my "rectangle" is expanding, because the blank UIView expands and those two imageViews are shifting up and down. I don't like this solution, because UI gets messed up if you turn your phone to landscape mode, or try to expand a cell from landscape mode and go back to portrait.
Any comments, suggestions?
UIImage view has the same size as its image if you use initWithImage:. I assume that you're adding the image in IB, so I'm guessing that IB uses that method to fill the image view. If you can't add the image after the expansion (with setImage which will cause the image to fill the size of the image view), then I would just use a UIView with rounded corners. You don't need to do the slicing, just use a layer with rounded corners. This is what I've done to make a cell have a grouped look, by putting a rounded white rectangle inside a cell. This is the init method for the subview:
-(id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
self.backgroundColor = [UIColor whiteColor];
CALayer *bgLayer = self.layer;
[bgLayer setMasksToBounds:YES];
[bgLayer setCornerRadius:8];
}
return self;
}
Be sure to import the QuartzCore framework if you do this.
I wanted to lock search bar on top of table view when y boundary reaches 0 ( or beyond - value)
I tried to in scroll view delegate method, but nothing really changed. In fact, the search bar's frame changed, but it still behaved as default.
Any ideas?
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
UISearchBar *searchBar = self.searchDisplayController.searchBar;
CGRect rect = searchBar.frame;
rect.origin.y = MAX(0, scrollView.contentOffset.y);
self.searchDisplayController.searchBar.frame = CGRectMake(0,MAX(0,scrollView.contentOffset.y),320,44);
}
In your viewdidload method you need to offset the table view:
self.tableView.contentOffset = CGPointMake(0.0, 44.0);
Then In IB drag a searchbar above your UITableview in your UINavigation controller
Or prgrammically add it.
You need to call setNeedsLayout if you're running on iOS 6.0+.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
UISearchBar *searchBar = self.searchDisplayController.searchBar;
CGRect rect = searchBar.frame;
rect.origin.y = MAX(0, scrollView.contentOffset.y);
[scrollView setNeedsLayout]; // <-- Call setNeedsLayout here.
self.searchDisplayController.searchBar.frame = CGRectMake(0,MAX(0,scrollView.contentOffset.y),320,44);
}
Been searching for this for quite a while, and I still haven't found a way to do it.
I'd like to reproduce the section headers of the iPhone's Spotlight into a UITableView.
"Regular" table view headers stay visible at the top of a section when you scroll, as we all know. But there is one kind of header that I've never seen elsewhere than in the Spotlight page of Springboard: there, the headers span the whole height of the section and are not stuck on the top of the section, but on the left side.
How the heck is that achieved?
Good question. I made a little experiment. It almost looks like the view from spotlight. But it's lacking one import feature. The lower "image" doesn't push the upper image to the top if they collide.
I doubt that there is a built in solution without the use of private frameworks.
I achieved this:
As you can see the two header images at the top overlap. They don't push each other like normal headers to. And I had to deactivate the cell separators. If I would use them they would appear at the whole cell. So you have to draw them yourself. Not a big deal.
But I have no idea how I could fix the overlapping.
Here is the code that made this happen:
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 1;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIView *contentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.tableView.bounds.size.width, 44)];
contentView.backgroundColor = [UIColor lightGrayColor];
UIImageView *imageView = [[[UIImageView alloc] initWithFrame:CGRectMake(5, 5, 34, 34)] autorelease];
imageView.image = [UIImage imageNamed:[NSString stringWithFormat:#"%d", section]];;
[contentView addSubview:imageView];
return [contentView autorelease];
}
Okay I have done something similar to what the music app and spotlight search does.
I have not subclassed UITableView, I have just tracked the sections through it's scrollViewDidScroll method, and added the header views to the left of the tableView (so you will have to put the tableview to the right in your viewController's view, which means you can't use UITableViewController).
this method should be called in the scrollViewDidScroll, ViewDidLoad, and in didRotateFromInterfaceOrientation: (if you support rotation)
keep in mind that you will have to make space in the left equal to the size of the headers in any interface orientation that you support.
-(void)updateHeadersLocation
{
for (int sectionNumber = 0; sectionNumber != [self numberOfSectionsInTableView:self.tableView]; sectionNumber++)
{
// get the rect of the section from the tableview, and convert it to it's superview's coordinates
CGRect rect = [self.tableView convertRect:[self.tableView rectForSection:sectionNumber] toView:[self.tableView superview]];
// get the intersection between the section's rect and the view's rect, this will help in knowing what portion of the section is showing
CGRect intersection = CGRectIntersection(rect, self.tableView.frame);
CGRect viewFrame = CGRectZero; // we will start off with zero
viewFrame.size = [self headerSize]; // let's set the size
viewFrame.origin.x = [self headerXOrigin];
/*
three cases:
1. the section's origin is still showing -> header view will follow the origin
2. the section's origin isn't showing but some part of the section still shows -> header view will stick to the top
3. the part of the section that's showing is not sufficient for the view's height -> will move the header view up
*/
if (rect.origin.y >= self.tableView.frame.origin.y)
{
// case 1
viewFrame.origin.y = rect.origin.y;
}
else
{
if (intersection.size.height >= viewFrame.size.height)
{
// case 2
viewFrame.origin.y = self.tableView.frame.origin.y;
}
else
{
// case 3
viewFrame.origin.y = self.tableView.frame.origin.y + intersection.size.height - viewFrame.size.height;
}
}
UIView* view = [self.headerViewsDictionary objectForKey:[NSString stringWithFormat:#"%i", sectionNumber]];
// check if the header view is needed
if (intersection.size.height == 0)
{
// not needed, remove it
if (view)
{
[view removeFromSuperview];
[self.headerViewsDictionary removeObjectForKey:[NSString stringWithFormat:#"%i", sectionNumber]];
view = nil;
}
}
else if(!view)
{
// needed, but not available, create it and add it as a subview
view = [self headerViewForSection:sectionNumber];
if (!self.headerViewsDictionary && view)
self.headerViewsDictionary = [NSMutableDictionary dictionary];
if (view)
{
[self.headerViewsDictionary setValue:view forKey:[NSString stringWithFormat:#"%i", sectionNumber]];
[self.view addSubview:view];
}
}
[view setFrame:viewFrame];
}
}
also we need to declare a property that would keep the views that are visible:
#property (nonatomic, strong) NSMutableDictionary* headerViewsDictionary;
these methods return the size and X axis offset of the header views:
-(CGSize)headerSize
{
return CGSizeMake(44.0f, 44.0f);
}
-(CGFloat)headerXOrigin
{
return 10.0f;
}
I have Built the code so that any header view that's not needed gets removed, so we need a method that would return the view whenever needed:
-(UIView*)headerViewForSection:(NSInteger)index
{
UIImageView* view = [[UIImageView alloc] init];
if (index % 2)
{
[view setImage:[UIImage imageNamed:#"call"]];
}
else
{
[view setImage:[UIImage imageNamed:#"mail"]];
}
return view;
}
here's how it will look :
How it will look in lanscape, I have used contraints to give 44px in the left of the tableView
hope this helps :).
Good news: See answer of How to achieve an advanced table view header
result http://i.minus.com/jyea3I5qbUdoQ.png
I am overloading the delegate method -tableView:heightForRowAtIndexPath: and using -sizeWithFont:constrainedToSize: to programmatically set the height of the cell, based on the text in that cell:
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.section) {
case(kAboutViewOverviewSection): {
switch (indexPath.row) {
case(kAboutViewOverviewSectionRow): {
NSString *text = NSLocalizedString(#"kAboutViewOverviewSectionFieldText", #"");
CGSize s = [text sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake(280, 500)];
return s.height + 13;
}
default: {
break;
}
}
break;
}
default: {
break;
}
}
return 44.0;
}
This works when the table is first drawn. However, when I change the orientation of the device, from portrait to landscape, the cell height does not change.
I tried overriding the -shouldAutorotateToInterfaceOrientation: method in my table view controller:
- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
[self.tableView reloadData];
return YES;
}
This should reload the table data and (presumably) redraw the table cell views. But this does not have any effect.
Is there a way to force a cell to redraw with a new height, when the device is rotated?
EDIT: I have tried the following, to no effect:
- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
[self.tableView reloadData];
}
Here is what the application looks like in portrait orientation:
Here is what the application looks like in landscape orientation:
There is a gap between the top and bottom edges of the content.
EDIT 2:
Adjusting the size parameter via the table frame's width helped fix this display issue:
CGSize s = [text sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake([tableView frame].size.width,500)];
It's less than optimal, but the simplest solution is to call -reloadData on the table.
A better solution would be to call -beginUpdates, -deleteRowsAtIndexPaths:withRowAnimation:, -insertRowsAtIndexPaths:withRowAnimation:, and -endUpdates or simply -reloadSections:withRowAnimation: if targeting 3.0 only. This will add animation.
Edit: And you will also need a proper tableView:heightForRowAtIndexPath:
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGSize textSize = [[self textForRowAtIndexPath:indexPath] sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake([tableView frame].size.width - 20, 500)];
return textSize.height + 13.0f;
}
(where textForRowAtIndexPath: is a method that returns the cell's text)