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;
}
Related
I would like to get the size of a UITableView's content view when the table is populated. Any suggestions on how to do this?
// Allows you to perform layout before the drawing cycle happens.
//-layoutIfNeeded forces layout early. So it will correctly return the size.
// Like dreaming before doing.
[tableView layoutIfNeeded];
CGSize tableViewSize=tableView.contentSize;
Here's a utility method that does it the hard way. The negligible advantage is there's no need to call [tableView layoutIfNeeded].
#define CGSizesMaxWidth(sz1, sz2) MAX((sz1).width, (sz2).width)
#define CGSizesAddHeights(sz1, sz2) (sz1).height + (sz2).height
+ (CGSize)sizeForTableView:(UITableView *)tableView {
CGSize tableViewSize = CGSizeMake(0, 0);
NSInteger numberOfSections = [tableView numberOfSections];
for (NSInteger section = 0; section < numberOfSections; section++) {
// Factor in the size of the section header
CGRect rect = [tableView rectForHeaderInSection:section];
tableViewSize = CGSizeMake(CGSizesMaxWidth(tableViewSize, rect.size), CGSizesAddHeights(tableViewSize, rect.size));
// Factor in the size of the section
rect = [tableView rectForSection:section];
tableViewSize = CGSizeMake(CGSizesMaxWidth(tableViewSize, rect.size), CGSizesAddHeights(tableViewSize, rect.size));
// Factor in the size of the footer
rect = [tableView rectForFooterInSection:section];
tableViewSize = CGSizeMake(CGSizesMaxWidth(tableViewSize, rect.size), CGSizesAddHeights(tableViewSize, rect.size));
}
return tableViewSize;
}
For table views whose heights are dynamically sized by their cells' contents--
My tableView is contained in a UIView, whose updateConstraints() function looks like this:
override func updateConstraints() {
self.tableView.layoutIfNeeded()
self.tableViewHeight.constant = min(300, self.tableView.contentSize.height)
super.updateConstraints()
}
tableViewHeight is an IBOutlet to the XIB-assigned height of the table view. I get whichever is smaller--300 points, or the height of the table view's content size.
I don't know how much people will like this answer because I am not sure that I like it. But I managed to get something working with this.
This will work for dynamic height cells. The callback will get called a few times as the tableview figures out its contentView
class ContentSizeNotifyingTableView: UITableView {
var contentSizeDidChange: ((CGSize) -> ())?
override var contentSize: CGSize {
didSet {
self.contentSizeDidChange?(self.contentSize)
}
}
}
I have a class which subclasses the UITableViewCell. This cell will be used in two different
UITableViewController class, one with a size of 200x400 and one with a size of 200x600. The question is what do I need to change in my implementation? Can I override the constructor of the cell so that I can pass in the width and height of the cell that I want? If yes then how do I do this?
Put this in your UITableViewCell-subclass:
- (void)setFrame:(CGRrect)newFrame {
[super setFrame:newFrame];
// perform operations using the new frame.
// ex.:
// CGRect rect = self.label.frame;
// rect.size.width = newFrame.size.width - 20.f;
// self.label.frame = rect;
}
How do I set the size of a subview which is being placed programmatically inside a UITableViewCell?
I have a custom (subclassed) UITableViewCell in which part of the cell includes a number of rows (dynamic) which are implemented as a custom UIView. The idea is to create the custom view (which is a row of items) and then when the UITableViewCell hits layoutSubviews it will (a) set itself up re positioning and (b) loop through and call "layoutSubviews" on each of the rows of the custom UIViews it is using for the rows.
Question - I'm not sure how to correctly set the size/position of the custom UIView row? How do I, within the custom UIView layoutSubviews method, determine its x,y,width,height so I can position it?
Do I actually for example need to manually, within the UITableCellView's layoutSubviews method, loop through all the rows and call a custom method of them to pass them their position/width/height's that they will need to be at? Then when the custom UIView row's layoutSubview method is hit it would know to look up it's instance variable which stored these values to use them?
Hopefully this makes sense.
Structure is:
Dynamic Custom UITableCellView (subclassed)
determines rowHeight dynamically in heightForRowAtIndexPath
in layoutSubviews it includes looping through subviews (of custom UIView I have) and calls layoutSubview on each of these
In custom UIView (which represents a row of items, e.g. UILabel)
in layoutSubview - QUESTION: How do set the size of the frame of this subview?
I assume the x,y for the frame should be 0,0?
For the width/height how do you get the full size of the superview area to which it should set in?
Should the superview have passed this information down to it prior to the layoutSubviews?
You can add a category to UIView with resize methods.
UIView+ResizeMethods.h
- (void) setHeight: (CGFloat) height;
- (void) setWidth: (CGFloat) width;
- (void) setTop: (CGFloat) top;
- (void) setLeft: (CGFloat) left;
UIView+ResizeMethods.m
- (void) setHeight: (CGFloat) height {
CGRect frame = self.frame;
frame.size.height = height;
self.frame = frame;
}
- (void) setWidth: (CGFloat) width {
CGRect frame = self.frame;
frame.size.width = width;
self.frame = frame;
}
- (void) setTop: (CGFloat) top {
CGRect frame = self.frame;
frame.origin.y = top;
self.frame = frame;
}
- (void) setLeft: (CGFloat) left {
CGRect frame = self.frame;
frame.origin.x = left;
self.frame = frame;
}
any views added as part of the tableViewCell subclass are positioned relative to the cells frame, ie x:0,y:0 for the subview origin, would be the top left corner of the tableCell.
Something like this should be enough to get you started.
CGRect frame =[self frame];
frame.size.height=20.0f;
frame.origin.x=(self.frame.size.height/2)-frame.size.height;
frame.origin.y=0.0f;
[subview setFrame:frame];
I'm trying to do a horizontal scroll of UILabels with different widths.
I've already put all labels next to each other inside the UIScrollView, but since the page scrolling, bouncing and snapping is done in scrollview.frame.width "steps", i cannot set it to work as I'd wish.
Can this be done? Thank you so much :)
What happens if you set the size of your width property to be the width of the next label? Something like (in your scroll view delegate) :
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
// Get the label that's going to slide into view
UIView *label = [self getCurrentLabel];
// Get it's width
float width = [label frame].size.width;
// Set our size
CGRect frame = [scrollView frame];
frame.size.width = width;
[scrollView setFrame:frame];
}
PS I've not tried this so it might just fail horribly!
I have created a UITableViewCell derived class which resizes itself to display at an indentation level. I have tried setting indentationLevel and indentationWidth using the following in the ViewControllers cellForRowAtIndexPath:
cell.indentationLevel = [[item objectForKey:#"indent"] intValue];
cell.indentationWidth = CELL_INDENT_SIZE;
This in itself does not indent the cell, so I am using the following layoutSubviews in the UITableViewCell:
-(void)layoutSubviews {
CGFloat indentSize = (self.indentationLevel - 1) * self.indentationWidth;
CGRect r = containerView.frame;
containerView.frame = CGRectMake (r.origin.x + indentSize, r.origin.y, r.size.width - indentSize, r.size.height);
DLog(#"frameRect : %f, %f, %f, %f, %f",indentSize, r.origin.x, r.origin.y, r.size.width, r.size.height);
[super layoutSubviews];
}
This works fine for the initial layout, but when the cell is selected, it recalculates itself from the new dimensions and effectively shrinks the cell every time it is selected.
How can I resize this cell so that it doesn't keep changing the layout.
layoutSubviews is called every time the cell has to redraw itself. I imagine its being called every time the cell has to draw itself highlighted.
In any case, you wouldn't want to resize the cell itself there. drawRect: is the place to that.
Even better yet, just use the built-in indentation system. It handles all this for you unless you're doing something really unusual.