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
}
Related
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.
I have made a custom UIView which is shown when the user hits a button in the navigationbar. I make my view's in code. In my loadview I set the autoresizing masks and the view loads correct on screen. However the UIView which is shown when the user taps the button does not resize even when I have set the autoresizing masks.
UIView *blackView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 416.0)];
blackView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
Do I need to use self.view.frame.size.width and self.view.frame.size.height instead? And if I do why? Does not resizing masks work outside of loadView?
Thank you for your time:)
the autoresizingMask affects how a view will behave when its superviews frame changes. if all you are doing is showing theblackViewwhen you tap a button, thenblackView` will have whatever frame you initially set for it.
If this isn't enough info, please post some more code around how you are configuring and displaying blackView and it's superview and explain more about what situations you are expecting blackView to resize in. Rotation is one of them, if that's what you're concerned with.
First things first, I hope you've done this:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
Let's say the view that needs resizing is: view2
The view that has view2 as a subview is: view1
While creating view1 you would declare it as:
view1 = [[UIView alloc] init];
[view1 setNeedsLayout];
Now in view1's .m file you need to overload the layoutSubviews method as shown:
- (void)layoutSubviews
{
CGRect frame = view2.frame;
// apply changes to frame
view2.frame = frame;
}
In case view1 is a view controller's view, you need to do that same thing as above in the willRotate method as shown
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
CGRect frame = view2.frame;
// apply changes to frame
view2.frame = frame;
}
This is a tried and tested method that I use to handle orientation changes.
I have a table cell being displayed that shows a users image, name and some text. The user's image is 50x50, but I want a border around it so I set the view to center the image and set the frame to 52x52 and then set the background color of that view to my border color. That shows a 1 pixel border around the image.
I also want to show a 30 pixel wide border on the right of the cell when the cell is selected. I've tried to do that by creating a UIView the size of the cell's frame, then adding a subview to that view with a UIView the width and background color I would like. I then set that view to the selectedBackgroundView of the cell.
The problem here is that the cell's selectedBackgroundView gets applied to the background of all views inside the cell. So when I select a cell, the images "border" gets set to the cell's selected background color, the other 30px "border" I'm adding gets changed to that background color also.
Code inside my cellForRowAtIndexPath:
cell = (UserCellView *) currentObject;
UIView *c = [[UIView alloc ] initWithFrame:CGRectMake(0, 0, 30, cell.frame.size.height)];
c.backgroundColor = [[UIColor alloc] initWithRed:64/255.0 green:64/255.0 blue:64/255.0 alpha:1.0];
UIView *v = [[UIView alloc ] initWithFrame:cell.frame];
v.backgroundColor = [[UIColor alloc] initWithRed:35/255.0 green:35/255.0 blue:35/255.0 alpha:1.0];
[v addSubview:c];
cell.selectedBackgroundView = v;
[c release];
[v release];
I'll assume that you haven't actually tested what's going on to form your analysis that it "gets applied to the background of all views inside the cell".
I did something like this:
#interface TestView : UIView {
}
#end
#implementation TestView
-(void)setBackgroundColor:(UIColor*)c {
// Breakpoint here.
NSLog("setBackgroundColor: %#",c);
[super setBackgroundColor:c];
}
#end
...
UIView * v = [[[UIView alloc] initWithFrame:(CGRect){{0,0},{20,20}}] autorelease];
v.backgroundColor = [UIColor magentaColor];
UIView * v2 = [[[TestView alloc] initWithFrame:(CGRect){{5,5},{10,10}}] autorelease];
v2.backgroundColor = [UIColor yellowColor];
[v addSubview:v2];
cell.selectedBackgroundView = v;
The end result is that -setBackgroundColor: is called from -[UITableViewCell _setOpaque:forSubview:] when the view is selected, with something like UIDeviceWhiteColorSpace 0 0 (i.e. [UIColor clearColor]).
Or, in other words, the background colour of some of the subviews are set to [UIColor clearColor] while the cell is selected, allowing selectedBackgroundView to show through. I think this happens because a common optimization is to give textLabel/detailTextLabel the table's background colour (e.g. white) so it draws faster, but this means the background colour has to be reset when the cell is selected.
The easiest fix is to use an image instead: a 1-by-1-pixel image of the correct colour in a UIImageView will work, if a bit messy. (I had this problem when drawing custom separator lines with 1-pixel-high UIViews, so I just included the separator into the background image.)
An alternative fix is to use a CALayer instead: Add a 52x52 sublayer to the UIImageView's layer, and set the sublayer's background colour. I'm pretty sure UITableViewCell simply walks the view hierarchy, so it should ignore custom layers. (The big disadvantage with layers is that they don't auto-size, which made them unsuitable for my purposes, and means the 30px right border won't auto-size.)
A workaround is to subclass the relevant views and ignore -setBackgroundColor: if it's equal to [UIColor clearColor].
A simple but obnoxious-to-maintain solution is to override setSelected:animated: and setHighlighted:animated: with implementations re-setting the various backgrounds you want. Something along the lines of:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
self.childView.backgroundColor = [UIColor blueColor]; // whichever you want
}
First add this to your file
#import <QuartzCore/QuartzCore.h>
Then turn your view into an image with...
UIView *rowView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 1.0, 60.0)];
rowView.backgroundColor = [UIColor colorWithRed:35/255.0 green:35/255.0 blue:35/255.0 alpha:1.0];
UIGraphicsBeginImageContext(rowView.bounds.size);
[rowView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *yourImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Then instead of adding a UIView to your cell, just add a UIImageView with "yourImage".
A simple solution if the affected view can be a custom subclass is to override -setBackgroundColor:
- (void)setBackgroundColor:(UIColor *)color
{
// Ignore requests and do nothing
}
Thus UITableViewCell's attempt to set the colour will go ignored. Code in the custom view which really does want to set the background colour needs to call super:
- (void)setColor:(UIColor *)color
{
[super setBackgroundColor:color];
}
(or could probably message the underlying CALayer directly)
you will need to customize the contentView of the cells and handle the delegate tableView:cellForRowAtIndexPath
See Posting Here
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
}
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)