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
Related
How the UILabel can be aligned from bottom. Let say, my label can hold three line of text.If the input text is single line, then this line should come bottom of the label.Please refer the below image for better understanding. The orange area is the full frame of label.Currently it has one line and it is aligned center. So what I want is, it should always aligned bottom regardless of how many lines.
Please suggest your ideas.
Thank you.
Swift 4.2 version using the contentMode property to set top and bottom:
class VerticalAlignedLabel: UILabel {
override func drawText(in rect: CGRect) {
var newRect = rect
switch contentMode {
case .top:
newRect.size.height = sizeThatFits(rect.size).height
case .bottom:
let height = sizeThatFits(rect.size).height
newRect.origin.y += rect.size.height - height
newRect.size.height = height
default:
()
}
super.drawText(in: newRect)
}
}
Then setup your label like that:
let label = VerticalAlignedLabel()
label.contentMode = .bottom
Here are two ways of doing that...
1. First set numberOfLines to 0 and then use sizeToFit property of UILabel so your UILabel display with its contentSize.
yourLabel.numberOfLines = 0;
[yourLabel sizeToFit];
See more information from this link: Vertically align text within a UILabel
2. Another option is to take UITextField instead of UILabel and set userInteractionEnabled to NO like below...
[yourTextField setUserInteractionEnabled:NO];
and then set the contentVerticalAlignment property to bottom like below...
[yourTextField setContentVerticalAlignment:UIControlContentVerticalAlignmentBottom];
UPDATE
Also, with UITextField, we can't achieve multiple lines. So instead we can use UITextView and set its userInteractionEnabled to NO. Then, use the code below to make it bottom aligned.
CGFloat topCorrect = ([label bounds].size.height - [label contentSize].height);
topCorrect = (topCorrect <0.0 ? 0.0 : topCorrect);
label.contentOffset = (CGPoint){.x = 0, .y = -topCorrect};
Subclass UILabel
#interface Label : UILabel
#end
Then override drawTextInRect like so
#implementation Label
- (void)drawTextInRect:(CGRect)rect
{
if(alignment == top) {
rect.size.height = [self sizeThatFits:rect.size].height;
}
if(alignment == bottom) {
CGFloat height = [self sizeThatFits:rect.size].height;
rect.origin.y += rect.size.height - height;
rect.size.height = height;
}
[super drawTextInRect:rect];
}
#end
i only set a bottom constraint to the super view in IB which works for me without using code and also number of Lines for a maximum constraint.
I recently ran into this problem and was able to solve it by putting my label in a stackview by itself. I got the idea from this post which had the same question but with multiple labels. The same technique can be used with a single label.
The stackview would have axis = horizontal and alignment = bottom (which is what does the trick).
My label is now perfectly aligned towards the bottom which is what I needed.
I had the same issue. Here is how I made to align the text to the bottom of my UILabel:
- (void)alignLabel:(UILabel *)l withText:(NSString *)text verticalAlignOption:(int)vertAlign{
CGSize stringSize = [text sizeWithFont:l.font constrainedToSize:l.frame.size lineBreakMode:l.lineBreakMode];
switch (vertAlign) {
case 0: // align = top
l.frame = CGRectMake(l.frame.origin.x,
l.frame.origin.y,
l.frame.size.width,
stringSize.height
);
break;
case 1: // align = bottom
l.frame = CGRectMake(l.frame.origin.x,
(l.frame.origin.y + l.frame.size.height) - stringSize.height,
l.frame.size.width,
stringSize.height
);
break;
case 2: // align = middle
// default
break;
}
l.text = text;
}
Ahd you simple call the method like this to align to the bottom:
[self alignLabel:self.mediaTitle withText:#"My text to align" verticalAlignOption:1];
Another option: use one label for your background color, I call this one originalLabel, and another for the text, called textLabel in my example. Then calculate the height and Y coordinate for textLabel:
[textLabel sizeToFit];
int height = textLabel.frame.size.height;
int yCoord = originalLabel.frame.origin.y +
originalLabel.frame.size.height - height;
textLabel.frame = CGRectMake( originalLabel.frame.origin.x, yCoord,
textLabel.frame.size.width, height);
In IB as #Tobe said Bottom constraint to superview should work,
Incase if you have multiple subview or horizontal stack view with one element to have bottom constraint then use Layout Margin to be Fixed with bottom less than other margins
Put your UILabel in a vertical UIStackView with a dummy view as a spacer.
Also, set the dummy view with a lower hugging and compression priority.
Top to Bottom:
Label
Dummy View
Bottom to Top:
Dummy View
Label
Screenshot
You can subclass UILabel and overriding the method :
- (void)drawTextInRect:(CGRect)rect
call super drawTextInRect with the rect where you want to use.
use autoLayout)
textLabel.numberOfLines = 0
textLabel.textAlignment = .center
textLabel.topAnchor.constraint(greaterThanOrEqualTo: sView.topAnchor).isActive = true
textLabel.leadingAnchor.constraint(equalTo: sView.leadingAnchor).isActive = true
textLabel.trailingAnchor.constraint(equalTo: sView.trailingAnchor).isActive = true
textLabel.bottomAnchor.constraint(equalTo: sView.bottomAnchor).isActive = true
I have a navigationBar with both Left and Right bar buttons on each side. I have a customTitlelabel which I set as the titleView of the UINavigationItem.
[self.navigationItem setTitleView:customTitleLabel];
All is fine now. The problem, the size of the rightbarButton is dynamic based on the input I get in one of the text fields.
Therefore the title is automatically centered based on the available space between the buttons.
how can i set the title to a fixed position?
Setting the titleView property of the nav bar works just fine - no need to subclass or alter any frames other than those of your custom view.
The trick to getting it centered relative to the overall width of UINavigationBar is to:
set the width of your view according to the size of the text
set the alignment to centered and
set the autoresizingmask so it gets resized to the available space
Here's some example code that creates a custom titleView with a label which remains centred in UINavigationBar irrespective of orientation, left or right barbutton width:
self.title = #"My Centered Nav Title";
// Init views with rects with height and y pos
CGFloat titleHeight = self.navigationController.navigationBar.frame.size.height;
UIView *titleView = [[UIView alloc] initWithFrame:CGRectZero];
UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
// Set font for sizing width
titleLabel.font = [UIFont boldSystemFontOfSize:20.f];
// Set the width of the views according to the text size
CGFloat desiredWidth = [self.title sizeWithFont:titleLabel.font
constrainedToSize:CGSizeMake([[UIScreen mainScreen] applicationFrame].size.width, titleLabel.frame.size.height)
lineBreakMode:UILineBreakModeCharacterWrap].width;
CGRect frame;
frame = titleLabel.frame;
frame.size.height = titleHeight;
frame.size.width = desiredWidth;
titleLabel.frame = frame;
frame = titleView.frame;
frame.size.height = titleHeight;
frame.size.width = desiredWidth;
titleView.frame = frame;
// Ensure text is on one line, centered and truncates if the bounds are restricted
titleLabel.numberOfLines = 1;
titleLabel.lineBreakMode = UILineBreakModeTailTruncation;
titleLabel.textAlignment = NSTextAlignmentCenter;
// Use autoresizing to restrict the bounds to the area that the titleview allows
titleView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
titleView.autoresizesSubviews = YES;
titleLabel.autoresizingMask = titleView.autoresizingMask;
// Set the text
titleLabel.text = self.title;
// Add as the nav bar's titleview
[titleView addSubview:titleLabel];
self.navigationItem.titleView = titleView;
You can't do what you want directly -- the position of your title view is out of your control (when managed by UINavigationBar).
However, there are at least two strategies to get the effect you want:
1) Add the title view not as the 'proper' title view of the nav bar, but as a subview of the UINavigationBar. (Note: this is not 'officially' sanctioned, but I've seen it done, and work. Obviously you have to watch out for your title label overwriting bits of the buttons, and handle different size nav bars for different orientations, etc. -- a bit fiddly.)
2) Make an intelligent UIView subclass that displays a given subview (which would be your UILabel) at a position calculated to effectively show the subview perfectly centered on the screen. In order to do this, your intelligent UIView subclass would respond to layout events (or frame property changes etc.) by changing the position (frame) of the label subview.
Personally, I like the idea of approach 2) the best.
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.topItem?.title = ""
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
navigationItem.title = "Make peace soon"
}
The right answer is to override sizeThatFits: of your custom titleView and return its content size. Navigation bar centers custom title view until it has no space left to do that.
For example if you have UIView container with UILabel inside:
#interface CustomTitleView : UIView
#property UILabel* textLabel;
#end
#implementation CustomTitleView
- (CGSize)sizeThatFits:(CGSize)size {
CGSize textSize = [self.textLabel sizeThatFits:size];
CGSize contentSize = size;
contentSize.width = MIN( size.width, textSize.width );
return contentSize;
}
#end
I tried aopsfan's answer but it didn't work. A breakpoint revealed that the bar's center was "(480.0, 22.0)" (The X coordinate way off) .
So I changed it into this:
- (void)layoutSubviews
{
[super layoutSubviews];
// Center Title View
UINavigationItem* item = [self topItem]; // (Current navigation item)
[item.titleView setCenter:CGPointMake(160.0, 22.0)];
// (...Hard-coded; Assuming portrait iPhone/iPod touch)
}
...and it works like a charm. The slide/fade effect when pushing view controllers is intact. (iOS 5.0)
I had similar problem.
My solution is do hide the original back button, add add your own implementation. Since the system will reserve space for the left items.
UIImage* cancelIcon = [UIImage imageNamed:#"ic_clear"];
UIBarButtonItem* cancelButton = [[UIBarButtonItem alloc] initWithImage:cancelIcon style:UIBarButtonItemStylePlain target:self action:#selector(back:)];
and the selector is simple
- (void)back:(UIButton *) sender
{
[self.navigationController popViewControllerAnimated:YES];
}
now it looks like this:
oh...and don't forget to use autolayout in your custom title view if you have dynamic length content like label in it. I add an additional layout in the customview to give it like "wrap_content" in Android by setting it centered to parent , and leading and trailing space ">=" 0
I had a similar situation where a titleView should be centered in UINavigationBar. I like occulus's approach of subclassing a UIView and overriding setFrame:. Then, I can center the frame inside the dimensions of UINavigationBar.
In the UIView subclass:
-(void)setFrame:(CGRect)frame{
super.frame = CGRectMake(320 / 2 - 50, 44 / 2 - 15, 100, 30);
}
The UIView subclass can then be assigned normally to titleView for each navigationItem. The developer does not have to programmatically add and remove special subviews from UINavigationBar.
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 am trying to create a table view which has a layout like what yobongo has:
and what I have now is really crappy, where the UIImage has different size, etc, etc...
How do I fix it to have something nice like that? I tried rearranging via IB but then mine looks like this:
I wanted to create a UIImage in the cell that has a fixed size (mine resizes here and there). How do I set that? I also want a rounded edge around the UIIMage... I have played around with the spring and struts via IB and I think I might have messed up something that I can't fix again..
I also want so that there exists a gap between rows and a border like in the picture below
I also wanted to implement a chat box like below where it expands if the text is more than it's limit. How can I do this?
Fixed image size
You have to set UIImageView frame for all image views. And then you have to play with UIImageView's contentMode property - where you can scale image to fit frame, fill frame, keep aspect ratio, etc. And you also have to set clipsToBounds to YES to clip "overlapping" image parts.
Round Corners
You can use CALayer for this, which is also available in UIImageView. It's matter of four lines ...
imageView.layer.cornerRadius = 3.0;
imageView.layer.masksToBounds = YES;
imageView.layer.borderColor = [UIColor blackColor].CGColor;
imageView.layer.borderWidth = 1.0;
Example:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier];
if ( self ) {
...
self.imageView.layer.cornerRadius = 3.0;
self.imageView.layer.masksToBounds = YES;
self.imageView.layer.borderColor = [UIColor blackColor].CGColor;
self.imageView.layer.borderWidth = 1.0;
...
}
return self;
}
Expandable Text Input
You have to prepare good background image for this. And then you can create stretchable image via UIImage class method: – stretchableImageWithLeftCapWidth:topCapHeight:
Each row will be subclassed UITableViewCell where you can handle all these things. Stretchable background, etc. Resizing via UITextView's delegate (textViewDidChange:), etc.
Google for some examples or search SO.
Gaps
UITableViewDelegate has method ...
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
... where you can specify row height. To create gap, add this to your custom cell ...
Header:
UIImageView *__backgroundImageView;
Initializer:
__backgroundImageView = [[UIImageView alloc] initWithImage:...stretchableImage...];
[self.contentView addSubview:__backgroundImageView];
[self.contentView sendSubviewToBack:__backgroundImageView];
Layouting:
- (void)layoutSubviews {
[super layoutSubviews];
// This draws background image over the whole cell and adds 5px gap top/bottom
CGRect rect = self.contentView.bounds;
rect.origin.y += 5; // Draw background image 5 pixels below cell top
rect.size.height -= 2 * 5; // Remove top/bottom gap from background image height
__backgroundImageView.frame = rect;
...
}
Memory Management:
- (void)dealloc {
[super dealloc];
[__backgroundImageView release]; __backgroundImage = nil;
...
}
If you want all images (of potentially different source sizes) to appear the same size in your UITableViewCell then you need to adjust the sizes of the images. The easiest way to do that is to use the ScaleToFill contentMode of your UIImageView, which will then do the work for you.
You can get rounded/bordered/colored corners on any view by doing this:
#import <QuartzCore/QuartzCore.h> // for layer.cornerRadius
...
self.comboTextView.layer.cornerRadius = 10.0;
self.comboTextView.layer.borderColor = [[UIColor grayColor] CGColor];
self.comboTextView.layer.borderWidth = 2;
Expanding TextView: I'd probably do this: implement the UITextViewDelegate function
- (void)textViewDidChange:(UITextView *)textView
then in that method get the textView.contentSize property, and set the textView.frame property to that size.
Create custom class that inherits from the UIView. Add ivar UIImageView* _img_v
Override setFrame:
-(void) setFrame:(CGRect) r {
[super setFrame: r];
if( _img_v.image && r.size.width*r.size.height ) {
CGSize isz = _img_v.image.size;
float sx = r.size.width/isz.width;
float sy = r.size.height/isz.height;
if( sx > sy ) {
sx = sy;
} else {
sy = sx;
}
CGRect img_frame = CGRectMake(0, 0, isz.width*sx, isz.height*sy);
img_frame.origin = CGPointMake((r.size.width - img_frame.size.width)/2.0, (r.size.height - img_frame.size.height)/2.0);
[_img_v setFrame: img_frame];
}
}
This won't crop the images, but you can also use scaleToFill for that.
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)
}
}