On a UITableViewCell with UITableViewCellStyleSubtitle styling, I'm setting the imageView.image, textLabel.text, and detailTextLabel.text. There's white padding all around the cell. How do I get rid of the padding so all my images touch each other like the Youtube app below?
Probably the easiest way to do this would be to subclass UITableViewCell and override the -layoutSubviews method to do something like this:
- (void)layoutSubviews {
//have the cell layout normally
[super layoutSubviews];
//get the bounding rectangle that defines the position and size of the image
CGRect imgFrame = [[self imageView] frame];
//anchor it to the top-left corner
imgFrame.origin = CGPointZero;
//change the height to be the height of the cell
imgFrame.size.height = [self frame].size.height;
//change the width to be the same as the height
imgFrame.size.width = imgFrame.size.height;
//set the imageView's frame (this will move+resize it)
[[self imageView] setFrame:imgFrame];
//reposition the other labels accordingly
}
Just remove table separator:
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
In iOS8 you can set
tableView.layoutMargins = UIEdgeInsets.zero
in code, or from Interface Builder.
Try reducing the UITableView's row height in interface builder or code so that there is no padding.
I had to increase the padding i did so in the interface builder for the tableview.
However Daves answer might give you more control over modifying the cell view.
Related
How would I draw a rectangle in a custom table cell class? The cell currently has a background image with a few text labels. I would like to draw a rectangle behind each of the labels so they are easier to read over the detailed background image.
I know I could just set the background colour of the label but I would like to have padding between the background colour and the text. If that is possible, I'd love to know how! :)
I'm subclassing a TTTableMessageItemCell in Three20, a method below gets called in which you can play with subviews of the cell,
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat padding = 16;
CGFloat boxWidth = self.contentView.width - 2*padding;
CGFloat textWidth = boxWidth - (padding*2);
CGFloat textHeight = 100;
CGFloat top = kTableCellSmallMargin;
// Position Heading Text
_titleLabel.frame = CGRectMake(padding, top, textWidth, _titleLabel.font.ttLineHeight);
top += _titleLabel.height;
// Position Detail Text
[self.detailTextLabel sizeToFit];
self.detailTextLabel.top = top+2*padding;
self.detailTextLabel.left = 2*padding;
self.detailTextLabel.width = textWidth;
self.detailTextLabel.height = 100;
}
I would like the rectangles to be placed behind the _titleLable and detailTextLabel labels.
edit
I have been able to add the right box using the following,
UIView *view = [[UIView alloc] init];
view.backgroundColor = [UIColor whiteColor];
view.frame = CGRectMake(padding, top, textWidth, textHeight+2*padding);
[self insertSubview:view belowSubview:self.detailTextLabel];
It is laying on top of the label and I cant seem to get it behind it...
edit
I was adding the view to the wrong subview, fixed it with,
[[self.subviews objectAtIndex:0] insertSubview:view atIndex:0];
You can add the labels to views and these to the cell.
You could use insertSubview:belowSubview: to add views behind your labels. With backgroundColor and the right frame they will do what you intend to.
You can also bring detailLabel to front
By default, a UITableViewCell instance positions the label pair textLabel/detailTextLabel in the center of its parent view. I prefer to have the pair aligned to the top of the cell. How do I do that programmatically?
Thanks,
Doug
The docs describe -[UIView layoutSubviews] as
"Overridden by subclasses to layout subviews when layoutIfNeeded is invoked. The default implementation of this method does nothing."
That description is not elaborate, but is accurate. In this case, the method's behavior is to layout your subviews. It will be invoked anytime the device orientation changes. It is also scheduled for later invocation whenever you call -setNeedsLayout.
Since, UIView's implementation does nothing, (and I presume the same for UIControl), you get total creative freedom to make your UIView subclass subviews be positioned wherever you want them.
In subclassing a UITableViewCell, you have a couple of options to try:
Override -layoutSubviews and
manipulate the position of the built-in textLabel and -detailLabel views.
Override -viewDidLoad,
create two of your own UILabels to provide the text and detailed text,
add them to self.contentView, and
override -layoutSubviews to manipulate the position of your custom UILabel views
In a related SO question, the recommendation is to avoid #1, manipulating the built-in textLabel and detailTextLabel.
A more reliable bet would be to do something like this:
#interface MyTableViewCell : UITableViewCell {
UILabel *myTextLabel;
UILabel *myDetailTextLabel;
}
// ... property declarations
#end
#implementation MyTableViewCell
#synthesize myTextLabel, myDetailTextLabel;
- (id) initWithFrame: (CGRect)frame {
self = [super initWithFrame: frame];
if (self) {
myTextLabel = [[[UILabel alloc] initWithFrame:CGRectZero] autorelease];
[self.contentView addSubview: myTextLabel];
myDetailTextLabel = [[[UILabel alloc] initWithFrame:CGRectZero] autorelease];
[self.contentView addSubview: myDetailTextLabel];
}
return self;
}
- (void) dealloc {
[myTextLabel release];
[myDetailTextLabel release];
[super dealloc];
}
- (void) layoutSubviews {
// Let the super class UITableViewCell do whatever layout it wants.
// Just don't use the built-in textLabel or detailTextLabel properties
[super layoutSubviews];
// Now do the layout of your custom views
// Let the labels size themselves to accommodate their text
[myTextLabel sizeToFit];
[myDetailTextLabel sizeToFit];
// Position the labels at the top of the table cell
CGRect newFrame = myTextLabel.frame;
newFrame.origin.x = CGRectGetMinX (self.contentView.bounds);
newFrame.origin.y = CGRectGetMinY (self.contentView.bounds);
[myTextLabel setFrame: newFrame];
// Put the detail text label immediately to the right
// w/10 pixel gap between them
newFrame = myDetailTextLabel.frame;
newFrame.origin.x = CGRectGetMaxX (myTextLabel.frame) + 10.;
newFrame.origin.y = CGRectGetMinY (self.contentView.bounds);
[myDetailTextLabel setFrame: newFrame];
}
#end
In MyTableViewCell, you would ignore the built-in text labels and use your own custom UILabels. You take complete control over positioning them within the content rect of the table view cell.
I'm leaving a lot of stuff out. In doing custom layout with text labels, you'd want to consider:
Figuring out your own layout algorithm.
I'm using a layout algorithm above that resizes the custom UILabels to fit their text content, then positions them side-by-side. You'll likely want something more specific to your app.
Keeping the custom labels within the content view.
In -layoutSubviews, you might want logic to keep the custom UILabels sized and positioned so that they don't fall outside the bounds of the content rect. With my naive layout logic, any long text dropped into either UILabel (or both) could cause the labels to be positioned right out of the content view bounds.
How to handle -viewDidLoad/-viewDidUnload.
As coded above, this subclass doesn't handle being loaded from a nib. You might want to use IB to layout your cell, and if you do, you'll need to think about -viewDidLoad/-viewDidUnload/-initWithCoder:
The following method in your UITableViewCell subclass should quickly and concisely align both textLabel and detailTextLabel to the top of the cell (nod to Bill's code), without adding any custom views.
- (void) layoutSubviews
{
[super layoutSubviews];
// Set top of textLabel to top of cell
CGRect newFrame = self.textLabel.frame;
newFrame.origin.y = CGRectGetMinY (self.contentView.bounds);
[self.textLabel setFrame:newFrame];
// Set top of detailTextLabel to bottom of textLabel
newFrame = self.detailTextLabel.frame;
newFrame.origin.y = CGRectGetMaxY (self.textLabel.frame);
[self.detailTextLabel setFrame:newFrame];
}
Layout of the UITableViewCell textLabel and detailTextLabel are not directly modifiable, except by picking one of the defined styles provided by the API.
typedef enum {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
} UITableViewCellStyle;
If you want to customize UITableViewCell layout, you'll need to subclass it and override the -layoutSubviews method.
- (void) layoutSubviews {
// [super layoutSubViews]; // don't invoke super
... do your own layout logic here
}
I'm having a scrollview as the detailedview of tableview cell. There are multiple views on the detailedview like labels, buttons etc. which I'm creating through interface builder. What I'm creating through interface builder is static. I'm putting everything on a view of height 480.
A label on my detailedview is having dynamic text which can extend to any length. The problem is that I need to set the scrollview's content size for which I need its height.
How shall I set scrollview's height provided the content is dynamic?
You could try to use the scrollview'ers ContentSize. It worked for me and I had the same problem with the control using dynamic content.
// Calculate scroll view size
float sizeOfContent = 0;
int i;
for (i = 0; i < [myScrollView.subviews count]; i++) {
UIView *view =[myScrollView.subviews objectAtIndex:i];
sizeOfContent += view.frame.size.height;
}
// Set content size for scroll view
myScrollView.contentSize = CGSizeMake(myScrollView.frame.size.width, sizeOfContent);
I do this in the method called viewWillAppear in the controller for the view that holds the scrollview. It is the last thing i do before calling the viewDidLoad on the super.
Hope it will solve your problem.
//hannes
Correct shorter example:
float hgt=0; for (UIView *view in scrollView1.subviews) hgt+=view.frame.size.height;
[scrollView1 setContentSize:CGSizeMake(scrollView1.frame.size.width,hgt)];
Note that this only sums heights, e.g. if there are two subviews side by side their heights with both be added, making the sum greater than it should be. Also, if there are vertical gaps between the subviews, the sum will be less than it should be. Wrong height confuses scrollRectToVisible, giving random scroll positions :)
This loop is working and tested:
float thisy,maxy=0;for (UIView *view in scrollView1.subviews) {
thisy=view.frame.origin.y+view.frame.size.height; maxy=(thisy>maxy) ? thisy : maxy;
}
A somewhat easier way to do this is to nest your layout within a view then put that view within the scrollview. Assuming you use tags, this works as follows:
UIScrollView *scrollview = (UIScrollView *)[self.view viewWithTag:1];
UIView *longView = (UIView *)[self.view viewWithTag:2];
scrollview.contentSize = CGSizeMake(scrollView.frame.size.width, longView.frame.size.height);
That way the longView knows how tall it is, and the scrollview's content is just set to match.
This depends on the type of content you are going to add dynamically. So let's say you have a big text data to show, then use the UITextView and as it is a subclass of the UIScrollView, you can get the setContentSize of TextView when you assign the text content. Based on that you can set the total size of the UIScrollView.
float yPoint = 0.0f;
UIScrollView *myScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0.0f, yPoint, 320.0f, 400.0f)];
UITextView *calculatorTextView = [[UITextView alloc] init]; calculatorTextView.text = #"My looong content text ..... this has a dynamic content"; `
[calculatorTextView sizeToFit];
yPoint = yPoint + calculatorTextView.contentSize.height; // Bingo, we have the new yPoint now to start the next component.
// Now you know the height of your text and where it will end. So you can create a Label or another TextView and display your text there. You can add those components as subview to the scrollview.
UITextView *myDisplayContent = [[UITextView alloc] initWithFrame:CGRectMake(0.0f, yPoint, 300.f, calculatorTextView.contentSize.height)];
myDisplayContent.text = #"My lengthy text ....";
[myScrollView addSubview:myDisplayContent];
// At the end, set the content size of the 'myScrollView' to the total length of the display area.
[myScrollView setContentSize:yPoint + heightOfLastComponent];
This works for me.
I guess there's no auto in case of scrollview, and the contentsize should be calculated for static views on the screen at least and for dynamic once it should be calculated on the go.
scrollView.contentSize = [scrollView sizeThatFits:scrollView.frame.size]
I believe would also work
I had the same situation, but then I wrote a new version in Swift 4 mirroring the better answer in Objective-C by Hannes Larsson:
import UIKit
extension UIScrollView {
func fitSizeOfContent() {
let sumHeight = self.subviews.map({$0.frame.size.height}).reduce(0, {x, y in x + y})
self.contentSize = CGSize(width: self.frame.width, height: sumHeight)
}
}
Setup: I have a UITextView inside a UITableViewCell's contentView. I want it to take up the full size of the cell. I create the text view like so:
UITextView *textView = [[[UITextView alloc] initWithFrame:CGRectMake(0,0,268,43)] autorelease];
textView.backgroundColor = [UIColor redColor];
textView.layer.cornerRadius = 10;
textView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
And I override heightForRowAtIndexPath to return 200 for that row.
The background color is just so I can tell where it is. The cell seems to be automatically sizing it correctly upon first view. However, I need it to continue to resize it correctly while autorotating the interface, which only seems to work sometimes, and only when I'm not editing the textView. Other times, it resizes the view to have a very small height (looks like -1), or makes it too wide, or just doesn't even resize it at all.
I've tried overriding layoutSubviews in the cell and just do nothing, but even that doesn't stop the view from resizing all over the place.
I've been hacking away at this for awhile now, but still have found no solution.
A UITableViewCell has a fixed height, the height provided by the UITableView's delegate. When you rotate your device, the height of the row will never change, unless you call -reloadData on your tableView. I'd get rid of the autoresizing and manage it yourself.
When you init your textField, you can easily set the frame to CGRectZero. Then implement -layoutSubviews (and call super in that method, before setting the frames of your subviews) and set the frame of the UITextField according to the contentRect property of the cell.
- (id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier {
if(self = [super ...]){ // or -initWithStyle:reuseIdentifier: whatever you want
_textView = [[UITextView alloc] initWithFrame:CGRectZero]; // Instance variable
// Probably not needed to set autoresizing mask
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
_textView.frame = CGRectMake(0.0f, 0.0f, self.contentRect.size.width, self.contentRect.size.height); // Adjust as needed
}
If you have a plain (not grouped) UITableView with a single row, the rest of the screen is filled with blank or empty cells. How do you change the appearance of these blank cells? Af first, I thought they would have the appearance of the cell used in the table view but it seems they don't.
The people from Cultured Code (Things) have done a nice job modifying these cells but I can't immediately think of a way to change their appearance.
Any tips?
Although it's not quite what you're looking for, you can also change it to just display a blank region (instead of blank cells) by setting a footer view on the table. A UIView of height 0 will do the trick.
Based on samvermette's answer, but modified to use a background image directly rather than compositing an image from a UITableViewCell subclass. Sam's answer is good, but it will be less performant than just creating a background image directly.
Create a UIView subclass -- in this example I called it CustomTiledView -- and add the following code to the class:
- (void)drawRect:(CGRect)rect {
UIImage *image = [UIImage imageNamed:#"tableview_empty_cell_image"];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextScaleCTM (context, 1, -1);
CGContextDrawTiledImage(context,
CGRectMake(0, 0, rect.size.width, image.size.height),
[image CGImage]);
}
Add the following code to your tableviewcontroller:
- (CGFloat)tableView:(UITableView *)tableView
heightForFooterInSection:(NSInteger)section {
// this can be any size, but it should be 1) multiple of the
// background image height so the last empty cell drawn
// is not cut off, 2) tall enough that footer cells
// cover the entire tableview height when the tableview
// is empty, 3) tall enough that pulling up on an empty
// tableview does not reveal the background.
return BACKGROUND_IMAGE_HEIGHT * 9; // create 9 empty cells
}
- (UIView *)tableView:(UITableView *)tableView
viewForFooterInSection:(NSInteger)section {
CustomTiledView *footerView = [[CustomTiledView alloc] init];
return [footerView autorelease];
}
Finally, you'll need to set the bottom content inset of your tableview to the negative of the value returned from -tableView:heightForFooterInsection: In this example it would be -1*BACKGROUND_IMAGE_HEIGHT*9. You can set the bottom content inset either from the Size Inspector of Interface Builder or by setting the self.tableView.contentInset property from the tableviewcontroller.
Cheers!
I set a ~300px-high UIView subclass to my tableView header and footer views, adjusting the tableView insets so they compensate for these views (set the top and bottom insets to -300px).
My UIView subclass implements the drawRect method, in which I use CGContextDrawTiledImage() to draw an empty UITableViewCell repetitively:
- (void)drawRect:(CGRect)rect {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(300, 46),NO,0.0);
emptyCell = [[SWTableViewCell alloc] initWithFrame:CGRectZero];
[emptyCell drawRect:CGRectMake(0, 0, 300, 46)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
[emptyCell release];
UIGraphicsEndImageContext();
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextScaleCTM (context, 1, -1); // this prevents the image from getting drawn upside-down
CGContextDrawTiledImage(context, CGRectMake(0, 0, 300, 46), [newImage CGImage]);
}
In my case my tableviewcells are 46px high, so if I want make UIView subclass to contain 8 of these empty cells, I need to make it 368px high.
From http://jomnius.blogspot.com/2011/03/hide-uitableview-empty-cell-separator.html
Easy way is to define that table has no cell separator lines:
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
Hard way, if you need separator lines, is to define that table has no separator lines and create cell separator lines as part of custom UITableViewCell. Bottom part of cell, obviously, and most likely using really thin graphics image. Remember to define image autosizing and mode properly.
Another way is to define an empty UITableView's tableFooterView:
UIView *footer =
[[UIView alloc] initWithFrame:CGRectZero];
self.myTable.tableFooterView = footer;
[footer release];
If you simply want to change the height of the "empty cells" when there are no cells at all in your TableView: you need to set the rowHeight property of your UITableView (the implementation of tableView:heightForRowAtIndexPath: has no effect on empty TableViews). If there are cells in your TableView and you have implemented tableView:heightForRowAtIndexPath: then the last row height will be used for empty cells.
You will probably have to make a custom subclass of UITableView that fills the background with the appearance of blank cells.
- (void)drawRect:(CGRect)rect {
[super drawRect: rect];
// draw your background here if the rect intersects with the background area
}
I believe the approach of using the UITableView tableView:viewForFooterInSection: method will not work for the case where the table is empty, ie., with no cells. In that case, you can use the technique Answerbot proposed can be repurposed to stuff the created UIView into the table's footer view explicitly, like the following:
CGRect cellRect = CGRectMake(0, 0, table.bounds.size.width, table.bounds.size.height);
CustomTiledView *footerView = [[[CustomTiledView alloc] initWithFrame:cellRect] autorelease];
table.tableFooterView = footerView;
For swift 4.0 :
self.tableView.tableFooterView = UIView(frame: CGRect.zero)