Multiline UILabel with padding in autolayout, in UITableView - ios

I have a UILabel in a UITableView that should be a maximum of 2 lines and have a bit of padding around it (7 left and right and 2 top and bottom). I'm using autolayout and targeting only iOS6 and above. All views are being created and added programatically.
I've subclassed my UILabel, here's the init method:
- (id)init
{
self = [super init];
self.translatesAutoresizingMaskIntoConstraints = NO;
self.numberOfLines = 2;
self.backgroundColor = UIColorFromARGB(0x99000000);
self.textColor = [UIColor whiteColor];
self.font = [UIFont boldSystemFontOfSize:14.0f];
return self;
}
If I add this in, I get the right padding, but it makes it just one line:
- (void)drawTextInRect:(CGRect)rect {
UIEdgeInsets insets = {2, 7, 2, 7};
return [super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
}
I've seen this answer a few times, but it doesn't work for me (no effect):
- (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines
{
UIEdgeInsets insets = {2, 7, 2, 7};
return [super textRectForBounds:UIEdgeInsetsInsetRect(bounds,insets) limitedToNumberOfLines:numberOfLines];
}
Does it make a difference that its in a table view? Any help would be appreciated.

As you saw in my above comment, you didn't really say what it's doing that you don't expect. I've just done this myself, and this works with autolayout:
#implementation InsetLabel
- (void) setInsets:(UIEdgeInsets)insets
{
_insets = insets ;
[self invalidateIntrinsicContentSize] ;
}
- (void)drawTextInRect:(CGRect)rect
{
return [super drawTextInRect:UIEdgeInsetsInsetRect(rect, self.insets)];
}
- (void)resizeHeightToFitText
{
CGRect frame = [self bounds];
CGFloat textWidth = frame.size.width - (self.insets.left + self.insets.right);
CGSize newSize = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(textWidth, 1000000) lineBreakMode:self.lineBreakMode];
frame.size.height = newSize.height + self.insets.top + self.insets.bottom;
self.frame = frame;
}
- (CGSize) intrinsicContentSize
{
CGSize superSize = [super intrinsicContentSize] ;
superSize.height += self.insets.top + self.insets.bottom ;
superSize.width += self.insets.left + self.insets.right ;
return superSize ;
}
#end

#implementation InsetLabel
- (void) setInsets:(UIEdgeInsets)insets
{
_insets = insets ;
[self invalidateIntrinsicContentSize] ;
}
- (void)drawTextInRect:(CGRect)rect
{
return [super drawTextInRect:UIEdgeInsetsInsetRect(rect, self.insets)];
}
- (void)resizeHeightToFitText
{
CGRect frame = [self frame];
CGFloat textWidth = frame.size.width - (self.insets.left + self.insets.right);
CGSize newSize = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(textWidth, 1000000) lineBreakMode:self.lineBreakMode];
frame.size.height = newSize.height + self.insets.top + self.insets.bottom;
self.frame = frame;
}
- (CGSize) intrinsicContentSize
{
CGSize superSize = [super intrinsicContentSize] ;
superSize.height += self.insets.top + self.insets.bottom ;
superSize.width += self.insets.left + self.insets.right ;
return superSize ;
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self resizeHeightToFitText];
}
This works even better imho.

Related

How to avoid this infinite loop caused by invalidateIntrinsicContentSize

This code causes an infinite loop when the view is put insida a tableviewcell. How do i use autolayout correctly without causing an infinite loop here?
#implementation SimpleStackView
#synthesize rowSpacing=_rowSpacing;
- (void)layoutSubviews
{
[super layoutSubviews];
[self sizeToFit];
[self invalidateIntrinsicContentSize];
CGFloat nextRowTop = 0;
for (UIView *view in self.subviews)
{
CGSize size = [view sizeThatFits:CGSizeMake(self.bounds.size.width, view.bounds.size.height)];
view.frame = CGRectMake(0, nextRowTop, self.bounds.size.width, size.height);
nextRowTop += view.frame.size.height + self.rowSpacing;
}
}
- (CGSize)sizeThatFits:(CGSize)size
{
CGFloat sumOfHeights = 0;
for (UIView *view in self.subviews) {
sumOfHeights += [view sizeThatFits:CGSizeMake(size.width, view.bounds.size.height)].height;
}
CGFloat sumOfRowSpacings = MAX(0, (int)self.subviews.count - 1) * self.rowSpacing;
return CGSizeMake(size.width, sumOfHeights + sumOfRowSpacings);
}
- (CGSize)intrinsicContentSize
{
CGFloat intrinsicHeight = [self sizeThatFits:self.bounds.size].height;
return CGSizeMake(UIViewNoIntrinsicMetric, intrinsicHeight);
}
#end

Add padding around ASTextNode

I am working with AsyncDisplayKit (for the first time) and have an ASTextNode inside a ASCellNode. I want to adding padding or an inset around the text inside the ASTextNode. I attempted to wrap it with a ASDisplayNode but whenever I calculated it's size in calculateSizeThatFits: it always returned 0. Any suggestions would be appreciated. The code that is in the ASCellNode subclass is:
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
{
CGSize textSize = [self.commentNode measure:CGSizeMake(constrainedSize.width - kImageSize - kImageToCommentPadding - kCellPadding - kInnerPadding, constrainedSize.height)];
return CGSizeMake(constrainedSize.width, textSize.height);
}
- (void)layout
{
self.imageNode.frame = CGRectMake(kCellPadding, kCellPadding, kImageSize, kImageSize);
self.imageNode.layer.cornerRadius = kImageSize / 2.f;
self.imageNode.layer.masksToBounds = YES;
self.imageNode.layer.borderColor = [UIColor whiteColor].CGColor;
self.imageNode.layer.borderWidth = 2.f;
self.commentNode.backgroundColor = [UIColor whiteColor];
self.commentNode.layer.cornerRadius = 8.f;
self.commentNode.layer.masksToBounds = YES;
CGSize textSize = self.commentNode.calculatedSize;
self.commentNode.frame = CGRectMake(kCellPadding + kImageSize + kCellPadding, kCellPadding, textSize.width, textSize.height);
}
If you node is returning 0 height / size, you may be forgetting to invalidate its current size after changing its content.
Use: [node invalidateCalculatedSize];
For padding around the text node, you could add a node behind it with the size you want and then set a hitTestSlop on the text node.
This would increase its tappable area.
The better approach might be to embed it inside a custom node e.g.
Interface
#interface InsetTextNode: ASControlNode
#property (nonatomic) UIEdgeInsets textInsets;
#property (nonatomic) NSAttributedString * attributedString;
#property ASTextNode * textNode;
#end
Implementation
#implementation InsetTextNode
- (instancetype)init
{
self = [super init];
if (!self) return nil;
[self addSubnode:textNode];
self.textInsets = UIEdgeInsetsMake(8, 16, 8, 16);
return self;
}
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
{
CGFloat availableTextWidth = constrainedSize.width - self.textInsets.left - self.textInsets.right;
CGFloat availableTextHeight = constrainedSize.height - self.textInsets.top - self.textInsets.bottom;
CGSize constrainedTextSize = CGSizeMake(availableTextWidth, availableTextHeight);
CGSize textSize = [self.textNode measure:constrainedTextSize];
CGFloat finalWidth = self.textInsets.left + textSize.width + self.textInsets.right;
CGFloat finalHeight = self.textInsets.top + textSize.height + self.textInsets.bottom;
CGSize finalSize = CGSizeMake(finalWidth, finalHeight);
return finalSize;
}
- (void)layout
{
CGFloat textX = self.textInsets.left;
CGFloat textY = self.textInsets.top;
CGSize textSize = self.textNode.calculatedSize;
textNode.frame = CGRectMake(textX, textY, textSize.width, textSize.height);
}
- (NSAttributedString *) attributedString
{
return self.textNode.attributedString;
}
- (void)setAttributedString:(NSAttributedString *)attributedString
{
self.textNode.attributedString = attributedString;
[self invalidateCalculatedSize];
}
- (void)setTextInsets:(UIEdgeInsets)textInsets
{
_textInsets = textInsets;
[self invalidateCalculatedSize];
}
#end
2018: Now it is super easy to achieve the same effect, So just in case someone needs to add padding here is a Swift 4 solution: -
let messageLabel: ASTextNode = {
let messageNode = ASTextNode()
messageNode.textContainerInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
return messageNode
}()

iOS UITextView putting character onto next line

In this picture with a UITextView saying "Coordinate Geometry", the "e" gets put onto the next line. There is no line space there. I have also tried this with a UILabel (I switched to UITextView to try and fix the problem but there was no difference).
Here is my code for my subclass of UITextView
- (instancetype)initWithFrame:(CGRect)frame text:(NSString *)text circular:(BOOL)isCircular {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor lightGrayColor];//[UIColor clearColor];
self.alpha = 0.5;
self.textColor = [Styles mainTextColour];
[self setFont:[Styles heading2Font]];
[self setTextAlignment:NSTextAlignmentCenter];
self.text = text;
self.editable = NO;
self.contentInset = UIEdgeInsetsMake(-8, 0, -8, 0);
[self resizeFontToFix];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
NSAssert(NO, #"Don't use BubbleText -initWithFrame:, use -initWithFrame: andText: instead");
return nil;
}
- (void)resizeFontToFix {
//Resize to fix
CGFloat fontSize = self.font.pointSize;
CGFloat minSize = 6.0 * [Styles sizeModifier];
while (fontSize > minSize && [self sizeThatFits:(CGSizeMake(self.frame.size.width, FLT_MAX))].height >= self.frame.size.height) {
fontSize -= 1.0;
self.font = [self.font fontWithSize:fontSize];
}
//this below resizes to fit and centres the resulting view
// [self resizeFrameAndCentre];
}
- (void)resizeFrameAndCentre {
CGRect originalFrame = self.frame;
[self sizeToFit];
CGPoint centre = self.center;
centre.x = originalFrame.origin.x + (originalFrame.size.width / 2);
centre.y = originalFrame.origin.y + (originalFrame.size.height / 2);
self.center = centre;
}

iOS UILabel cannot resize the text

I would like to implement the ULLabelExtended to autosize the text size of the UILabel to the device width. but when it comes to the execution; it shows the picture as the result instead of showing the full clause:
The full clause : Ze aangeplant cocos rond hun nieuwe home.
Are there any other alternatives to get the relationship or functions that text size of label is proportional to the device width ?
The below is my code
Application:
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
CGFloat screenHeight = screenRect.size.height;
[self.labelLong setWidthOfLabelWithMaxWidth:screenWidth];
[self.labelShort setWidthOfLabelWithMaxWidth:screenWidth];
Source:
#import "UILabelExtended.h"
#implementation UILabelExtended
#synthesize selector,customDelegate, objectInfo;
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if(self.selector)
if([self.customDelegate respondsToSelector:self.selector]) {
[self.customDelegate performSelector:self.selector withObject:self];
return;
}
}
- (void)dealloc {
self.customDelegate = nil;
self.selector = NULL;
self.objectInfo = nil;
}
#end
#implementation UILabel(UILabelCategory)
- (void)setHeightOfLabel {
UILabel* label = self;
//get the height of label content
CGFloat height = [label.text sizeWithFont:label.font constrainedToSize:CGSizeMake(label.bounds.size.width, 99999) lineBreakMode:NSLineBreakByWordWrapping].height;
//set the frame according to calculated height
CGRect frame = label.frame;
if([label.text length] > 0) {
frame.size.height = height;
}
else {
frame.size.height = 0;
}
label.frame = frame;
}
- (void)setWidthOfLabel {
UILabel* label = self;
//get the height of label content
CGFloat width = [label.text sizeWithFont:label.font constrainedToSize:CGSizeMake(99999, label.bounds.size.height) lineBreakMode:NSLineBreakByWordWrapping].width;
//set the frame according to calculated height
CGRect frame = label.frame;
if([label.text length] > 0) {
frame.size.width = width+5;
}
else {
frame.size.width = 0;
}
label.frame = frame;
}
- (void)setHeightOfLabelWithMaxHeight:(float)maxHeight {
UILabel* label = self;
//get the height of label content
CGFloat height = [label.text sizeWithFont:label.font constrainedToSize:CGSizeMake(label.bounds.size.width, maxHeight) lineBreakMode:NSLineBreakByWordWrapping].height;
//set the frame according to calculated height
CGRect frame = label.frame;
if([label.text length] > 0) {
if (height > maxHeight) {
frame.size.height = maxHeight;
}
else {
frame.size.height = height;
}
}
else {
frame.size.height = 0;
}
label.frame = frame;
}
- (void)setWidthOfLabelWithMaxWidth:(float)maxWidth {
UILabel* label = self;
//get the height of label content
CGFloat width = [label.text sizeWithFont:label.font constrainedToSize:CGSizeMake(99999, label.bounds.size.height) lineBreakMode:NSLineBreakByWordWrapping].width;
//set the frame according to calculated height
CGRect frame = label.frame;
if([label.text length] > 0) {
if (width > maxWidth) {
frame.size.width = maxWidth;
}
else {
frame.size.width = width;
}
}
else {
frame.size.width = 0;
}
label.frame = frame;
}
#end

How to Vertically Align Text Using UILabel and NSAttributedString

I am using UILabel and NSAttributedString to set linespacing for label texts in IOS7. But when i use this the text doesnt seems aligned centrally on the Label. Here is my code to set text (attributed) to the label.
-(void)setText:(NSString *)text
{
[super setText:text];
if(text)
[self setLineSpace];
}
-(void)setLineSpace
{
if([[[UIDevice currentDevice] systemVersion] floatValue] >= 7)
{
NSMutableAttributedString *string=[[NSMutableAttributedString alloc]initWithString:self.text];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
paragraphStyle.alignment=NSTextAlignmentJustified;
[paragraphStyle setLineSpacing:4] ;
// paragraphStyle.minimumLineHeight =0;
// paragraphStyle.maximumLineHeight=7;
// CTTextAlignment alignment = kCTCenterTextAlignment;
[string addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, string.length)];
self.text=nil;
self.attributedText=string;
[self setBackgroundColor:[UIColor redColor]];
}
}
Here are some Screenshots ,BTW am subclassing UILabel to and overriding the setter to implement linespacing.
Actually I solved it ....:) :) .The problem was with the custom font i was using.
Took some R&D but now it seems working
Just created a subclass for label and override some default methods.
- (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines
{
CGRect textRect = [super textRectForBounds:bounds limitedToNumberOfLines:numberOfLines];
if(([self.font.fontName isEqualToString:#"Gotham"]||[self.font.fontName isEqualToString:#"Gotham-Bold"]||[self.font.fontName isEqualToString:#"Gotham-Medium"])&&((int)[[[UIDevice currentDevice] systemVersion] floatValue])==6&&self.verticalAlignment==AlignMiddle)
{
textRect.origin.y = bounds.origin.y + (bounds.size.height - textRect.size.height) / 2.0;
return [self addToRect:textRect x:0 y:2];
}
return [self addToRect:textRect x:0 y:0.5];
}
-(void)drawTextInRect:(CGRect)requestedRect
{
if(((int)[[[UIDevice currentDevice] systemVersion] floatValue])>=7)
[super drawTextInRect:[self addToRect:requestedRect x:0 y:0.5]];
else
{
CGRect actualRect = [self textRectForBounds:requestedRect limitedToNumberOfLines:self.numberOfLines];
[super drawTextInRect:actualRect];
}
}
-(CGRect)addToRect:(CGRect)rect x:(CGFloat)dx y:(CGFloat) dy
{
rect=CGRectMake(rect.origin.x+dx, rect.origin.y+dy, rect.size.width, rect.size.height);
return rect;
}
-(CGRect)makeRect:(CGRect)rect x:(CGFloat)dx y:(CGFloat) dy
{
rect=CGRectMake(dx,dy, rect.size.width+dx, rect.size.height+dy);
return rect;
}
-(CGRect)makeRects:(CGRect)rect x:(CGFloat)dx y:(CGFloat) dy
{
rect=CGRectMake(rect.origin.x,rect.origin.y, rect.size.width+dx, rect.size.height+dy);
return rect;
}
-(void)truncateToFit
{
if([[[UIDevice currentDevice] systemVersion] floatValue] < 7)
{
[self sizeToFit];
CGRect frame=self.frame;
if(frame.size.height<self.frame.size.height)
self.frame=[self makeRects:frame x:0 y:0.5];
}
else
{
self.frame=[self makeRects:self.frame x:0 y:0.5];
[self sizeToFit];
}
}
-(void)truncatesToFit
{
self.lineBreakMode=NSLineBreakByTruncatingTail;
self.numberOfLines=2;
[self sizeToFit];
self.frame=[self makeRects:self.frame x:0 y:0.5];
}
-(void)truncatesToFit:(NSInteger )linesCount
{
self.numberOfLines=linesCount;
[self sizeToFit];
self.frame=[self makeRects:self.frame x:0 y:0.5];
}
Probably you can't do it with UILabel in the simple way.
If possible, you can use UITextField to vertically align content.
Else using NSAttributedString and the text of label, you can adjust Line-Height (Not sure).
Can be done via the attributed string:
ObjC:
// shift the text down 10 pixels
[string addAttribute:NSBaselineOffsetAttributeName value:#(-10) range:NSMakeRange(0, string.length)];
Swift:
// shift the text up 10 pixels
string.addAttributes([NSAttributedStringKey.baselineOffset:10], range: range)
Converted the solution put up by Ivan M in https://discussions.apple.com/thread/1759957?tstart=0 to Swift 5
class VerticallyAlignedUILabel: UILabel {
enum VerticalAlignment {
case top
case bottom
case middle
}
public var verticalAlignment:VerticalAlignment {
didSet {
self.setNeedsDisplay()
}
}
init(frame: CGRect, verticalAlignment:VerticalAlignment = .middle) {
self.verticalAlignment = verticalAlignment
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
var textRect:CGRect = super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines)
switch self.verticalAlignment {
case .top:
textRect.origin.y = bounds.origin.y
case .bottom:
textRect.origin.y = bounds.origin.y + bounds.size.height - textRect.size.height
case .middle:
textRect.origin.y = bounds.origin.y + (bounds.size.height - textRect.size.height) / 2.0
}
return textRect
}
override func drawText(in rect: CGRect) {
let actualRect = self.textRect(forBounds: rect, limitedToNumberOfLines: self.numberOfLines)
super.drawText(in: actualRect)
}
}
You may use some other workaround - a custom UILabel
NWLabel.m
#import "NWLabel.h"
#implementation NWLabel
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (!self) return nil;
_verticalAlignment = VerticalAlignmentTop;
return self;
}
-(VerticalAlignment) verticalAlignment
{
return _verticalAlignment;
}
-(void) setVerticalAlignment:(VerticalAlignment)value
{
_verticalAlignment = value;
[self setNeedsDisplay];
}
// align text block according to vertical alignment settings
-(CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines
{
CGRect rect = [super textRectForBounds:bounds limitedToNumberOfLines:numberOfLines];
CGRect result;
switch (_verticalAlignment)
{
case VerticalAlignmentTop:
result = CGRectMake(bounds.origin.x, bounds.origin.y, rect.size.width, rect.size.height);
break;
case VerticalAlignmentMiddle:
result = CGRectMake(bounds.origin.x, bounds.origin.y + (bounds.size.height - rect.size.height) / 2, rect.size.width, rect.size.height);
break;
case VerticalAlignmentBottom:
result = CGRectMake(bounds.origin.x, bounds.origin.y + (bounds.size.height - rect.size.height), rect.size.width, rect.size.height);
break;
default:
result = bounds;
break;
}
return result;
}
-(void)drawTextInRect:(CGRect)rect
{
CGRect r = [self textRectForBounds:rect limitedToNumberOfLines:self.numberOfLines];
[super drawTextInRect:r];
}
NWLabel.h
#import <UIKit/UIKit.h>
typedef enum
{
VerticalAlignmentTop = 0, // default
VerticalAlignmentMiddle,
VerticalAlignmentBottom,
} VerticalAlignment;
#interface NWLabel : UILabel
{
#private VerticalAlignment _verticalAlignment;
}
#property (nonatomic, readwrite, assign) VerticalAlignment verticalAlignment;
#end
When there's only one line of text displayed in your custom label, don't set the line spacing.

Resources