UILabel text truncates when rotated. How to fix? - ios

I am creating a watermark to overlay on top of an image view. The image can be panned, zoomed, etc.
I have created a watermark view class that, when sized, overlays a text label that is as big as will fit in the bounds. This works great and sizes appropriately as the image is sized, etc.
Now I want to rotate the label. As soon as I apply the transform, the text in the label truncates. I assume I am close, but am doing something silly because I am new at this. Any ideas?
Here is my class:
#interface WatermarkView ()
- (CGFloat)biggestBoldFontForString:(NSString*)text inRect:(CGRect)rect;
#end
#implementation WatermarkView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
// create a label that will display the watermark
mainLabel = [[UILabel alloc] initWithFrame:frame];
mainLabel.alpha = 0.35;
mainLabel.backgroundColor = [UIColor clearColor];
mainLabel.text = #"watermark";
mainLabel.font = [UIFont boldSystemFontOfSize:[self biggestBoldFontForString:mainLabel.text inRect:mainLabel.frame]];
mainLabel.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
// rotate label to a random slanted angle
mainLabel.transform = CGAffineTransformMakeRotation(RANDOM_FLOAT(-M_PI/5, -M_PI/6));
[self addSubview:mainLabel];
}
return self;
}
- (void)dealloc
{
[mainLabel release];
[super dealloc];
}
- (CGFloat)biggestBoldFontForString:(NSString*)text inRect:(CGRect)rect
{
CGFloat actualFontSize = 200;
[text sizeWithFont:[UIFont boldSystemFontOfSize:200] minFontSize:1 actualFontSize:&actualFontSize forWidth:rect.size.width lineBreakMode:0];
return actualFontSize;
}
- (void)layoutSubviews
{
[super layoutSubviews];
mainLabel.font = [UIFont boldSystemFontOfSize:[self biggestBoldFontForString:mainLabel.text inRect:self.frame]];
}
#end

A simple solution might be to simply create a second label, like your first, but adapted to the rotated view. Keep it hidden or alpha=0, then on rotation hid the initial textView and unhide the new Text.
Another issue may be that your frame is too small, once rotated, try keeping the font the size you want and text aligned center, but make the TextView frame much larger than you think you need.

The reason the label truncates after a rotation transform is the bounds shrink. The solution is to store the width of the label before rotation and reassign after didRotateFromInterfaceOrientation.
e.g.
#synthesize labelWidth
(void)viewDidLoad
{
self.labelWidth = _label.bounds.size.width;
_label.transform = CGAffineTransformMakeRotation(-1.3f);
}
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
CGRect bounds = _label.bounds;
bounds.size.width = self.labelWidth;
_label.bounds = bounds;
}

Related

UITableView cell width on iOS 8 stuck at 320pt

I am currently trying to create a simple UITableView with custom cells without using storyboard.
I'm getting an issue on the iPhone 6 simulator where the table view has a width of 375 (as it should), but the cells inside are getting a width of 320.
The number 320 is nowhere to be found in the project as I am not hard coding it. When I am setting the background colour of the cell, it extends the full width of 375, but I need to align an image to the right, which only aligns 320 across as shown in the photo below.
I'm not sure if it's because I'm missing constraints or if there's a bug. Any help is appreciated, thanks!
Code to set up table:
- (TBMessageViewCell *)getMessageCellforTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"MessageCell";
TBMessageViewCell *cell = (TBMessageViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[TBMessageViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
[cell createSubviews];
}
// Set the new message and refresh
[cell setMessage:self.viewModel.messages[indexPath.row]];
[cell populateSubviews];
cell.backgroundColor = [UIColor blueColor];
NSLog(#"cell Width: %f", cell.contentView.frame.size.width);
return cell;
}
Complete TBMessageViewCell:
#implementation TBMessageViewCell
const CGFloat MARGIN = 10.0f;
const CGFloat AVATAR_SIZE = 40.0f;
-(id)initWithStyle:(UITableViewCellStyle *)style reuseIdentifier:(NSString *)reuseIdentifier
{
if(self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier]){
}
// Sets background and selected background color
self.backgroundColor = [UIColor clearColor];
UIView *selectionColor = [[UIView alloc] init];
selectionColor.backgroundColor = [UIColor clearColor];
self.selectedBackgroundView = selectionColor;
return self;
}
- (void)populateSubviews
{
// Set the message body
[self.messageBodyLabel setText:self.message.body];
[self.messageBodyLabel setTextAlignment:NSTextAlignmentRight];
CGRect bodyFrame = CGRectMake(MARGIN, MARGIN, self.frame.size.width - (AVATAR_SIZE + (MARGIN * 3)), self.frame.size.height);
// Calculates the expected frame size based on the font and dimensions of the label
// FLT_MAX simply means no constraint in height
CGSize maximumLabelSize = CGSizeMake(bodyFrame.size.width, FLT_MAX);
CGRect textRect = [self.message.body boundingRectWithSize:maximumLabelSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:self.messageBodyLabel.font}
context:nil];
bodyFrame.size.height = textRect.size.height;
// Setup the new avatar frame (Right aligned)
CGRect avatarFrame = CGRectMake(bodyFrame.size.width + (MARGIN * 2), MARGIN, AVATAR_SIZE, AVATAR_SIZE);
// Align to the LEFT side for current user's messages
if ([[TBConfig userID] isEqualToString:self.message.user.userID]) {
// Set avatar to left if it's me
avatarFrame.origin.x = MARGIN;
bodyFrame.origin.x = AVATAR_SIZE + (MARGIN * 2);
[self.messageBodyLabel setTextAlignment:NSTextAlignmentLeft];
}
self.avatar.frame = avatarFrame;
self.avatar.layer.cornerRadius = self.avatar.frame.size.width/2;
self.messageBodyLabel.frame = bodyFrame;
// Set the new cell height on the main Cell
CGFloat cellHeight = MAX(bodyFrame.size.height, self.frame.size.height) + MARGIN;
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, cellHeight);
// Set the new Profile avatar
if (![self.avatar.profileID isEqualToString:self.message.user.facebookID]) {
[self.avatar setProfileID:nil];
[self.avatar setProfileID:self.message.user.facebookID];
}
}
- (void)createSubviews
{
self.messageBodyLabel = [[UILabel alloc] init];
self.messageBodyLabel.textColor = [UIColor whiteColor];
self.messageBodyLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.messageBodyLabel.numberOfLines = 0;
[self addSubview:self.messageBodyLabel];
// Creates the avatar
self.avatar = [[FBProfilePictureView alloc] init];
[self.avatar setPictureCropping:FBProfilePictureCroppingSquare];
[self addSubview:self.avatar];
}
You're printing the size of the cell before it has been added to the display — before it has been sized. It doesn't yet know the size of tableview it will be added to.
The cells will be given an appropriate frame when added to the display.
EDIT: oh, and you probably don't want that cellIdentifier to be static. You probably wanted *const.
Don't know if you have found the answer. I faced the same problem when I was trying to subclass UITableViewCell and add custom subviews programmatically without using xib.
Finally the solution worked for me is to use [[UIScreen mainScreen] applicationFrame] instead of self.frame when calculating subviews' frames.
The proper way to solve this is to perform your layout in the layoutSubviews method.
In your case, simply call "populateSubviews" within "layoutSubviews" method, like this:
- (void)layoutSubviews {
[super layoutSubviews];
[self populateSubviews];
}
... but before doing this I would recommend that you do content population in a separate method (ie, calls to label.text = ...), and place all layout-affecting calls (ie, label.frame = ...) below [super layoutSubviews] in the method above.
That would result in something like:
- (void)layoutSubviews {
[super layoutSubviews];
CGRect bodyFrame = CGRectMake(MARGIN, MARGIN, self.frame.size.width - (AVATAR_SIZE + (MARGIN * 3)), self.frame.size.height);
// Calculates the expected frame size based on the font and dimensions of the label
// FLT_MAX simply means no constraint in height
CGSize maximumLabelSize = CGSizeMake(bodyFrame.size.width, FLT_MAX);
CGRect textRect = [self.message.body boundingRectWithSize:maximumLabelSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:self.messageBodyLabel.font}
context:nil];
bodyFrame.size.height = textRect.size.height;
// .. the rest of your layout code here ..
}
- (void)populateSubviews {
[self.messageBodyLabel setText:self.message.body];
// .. the rest of your code here ..
}
After you set your avatar frame in:
self.avatar.frame = avatarFrame;
self.avatar.layer.cornerRadius = self.avatar.frame.size.width/2;
self.messageBodyLabel.frame = bodyFrame;
write
self.avatar.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin;
It should hook your avatar image to the right margin and leave the left margin as flexible.
Select your table view cell's connection inspector and check if you haven't connected editingAccessoryView by mistake
While this answer may not be as straightforward as you'd expect UIKit to deliver, due to a bug in UITableView - when doing things programmatically - you gotta get your hands dirty. Xib lovers - be warned - this answer isn't for you. It's probably just working.
In later IOS versions, let's hope this problem get resolved. The problem is uitableview is hardcoding the dimensions of the cell to 320. In one project I had - I was forcing the frame size to to fix this. N.B. this has problems with splitview controller on iPad.
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"cell frame width:%f",cell.frame.size.width); // you should see <- 320! WTH
// you can crudely correct this here
cell.frame = CGRectMake(0,0,[[UIScreen mainScreen] applicationFrame].size.width,CELL_HEIGHT);
}
Another option - that's working better for me is a local width variable to reference. I know this code is not ideal - but it works consistently.
#interface AbtractCustomCell : UITableViewCell {
float width;
}
#end
#implementation AbtractCustomCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
//optimise speed
// [self setOpaque:YES];
if ([SDiPhoneVersion deviceSize] == iPhone47inch) {
width = 375;
}
else if ([SDiPhoneVersion deviceSize] == iPhone47inch) {
width = 414;
}
else {
width = 320; // hardcode iphone 4/5 /ipad2
}
}
return self;
}
#end
then you have the option to make TBMessageViewCell a subclass of this AbstractCustomCell which will have this variable there for you. Instead of using self.contentView.bounds.size.width / self.bounds.size.width just use width.
I'm programmatically layout without Storyboard and facing same issue, and I need to return the cell upon cellForRowAt indexPath which in turn it still didnt manage to get the container size yet.
And I manage to solve it by move the layout codes into willDisplay cell delegate.

Moving a frame half of the parent's size moves it all the way across (Coordinates doubled)?

I have no idea what is going on.
I am not doing anything special.
I have a UIViewController that I create programmatically, no storyboard.
Then I create a UIView with nothing special
#import "SettingsView.h"
#interface SettingsView()
#property UIImageView* bg;
#end
#implementation SettingsView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.bg = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"happy.png"]];
[self addSubview:self.bg];
}
return self;
}
-(void)layoutSubviews
{
self.bg.frame = self.frame;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
I instantiate it in the VC and when I want to show it I do:
-(void)showSettingsView
{
self.settings.frame = self.view.frame;
self.btnInfo.userInteractionEnabled = NO;
self.btnPause.userInteractionEnabled = NO;
self.btnSettings.userInteractionEnabled = NO;
self.view.userInteractionEnabled = NO;
[self.view addSubview:self.settings];
CGRect frame = self.settings.frame;
frame.origin.x = frame.size.width * 0.48;
frame.origin.y = 0;
self.settings.frame = frame;
}
Instead of seeing the image view roughly half way on the screen, it starts at exactly double (0.48 * 2 = 96%) of the screen.
I cannot understand why coordinates are doubled or something?
When I check the frame, I clearly see the origin at around 150 which is half the width of the iPhone.
What could be happening?
Thanks
Could it be that in...
-(void)layoutSubviews
{
self.bg.frame = self.frame;
}
you should be using bounds instead of frame of your self object...
-(void)layoutSubviews
{
self.bg.frame = self.bounds;
}

How to implement this margin for UITableViewCell via layers

I need to implement this left margin for the background of the cell.
Now I tried to do it this way - but the result is not correct - the content of the cell is moving with the margin. How can I solve an issue?
CGRect layerFrame = self.layer.frame;
layerFrame.size.width -= kRightLayerMargin;
layerFrame.origin.x += kLeftLayerMargin;
self.layer.frame = layerFrame;
self.layer.backgroundColor = [[SCUtils cellSelectionColor] CGColor];
self.layer.cornerRadius = 10.0f;
Implement your cell's layoutSubviews method as follows:
- (void)layoutSubviews {
//have the cell layout normally
[super layoutSubviews];
//get the bounding rectangle that defines the position and size of the textLabel
CGRect textLabelFrame = [[self textLabel] frame];
//anchor it to the top-left corner
textLabelFrame.origin = CGPointMake(kLeftLayerMargin, textLabelFrame.origin.y);
//set the textLabel frame (this will move+resize it)
[[self textLabel] setFrame:textLabelFrame];
//reposition the other controls accordingly
}

UICollectionView Cell Frame Has Decimal Origin Value

I have a UICollectionView that I created programatically. Each cell in the collection view displays a label, and all the labels have identical properties. Despite this, I noticed that the text contained in each label in the center column of the collection view looked blurry.
A recursive description of the collection view shows that the center cell in the table always has an x origin that is a decimal value:
<UICollectionViewCell: 0xd98c1e0; frame = (242.5 0; 220 45); layer = <CALayer: 0xd98c270>> ...
My questions are: 1) could this be causing the bluriness? and 2) What is the best way to ensure that none of the x and y origins end up having a decimal value? (Aside from manually calculating the layout)
For reference, here is my UICollectionView code:
//subclass UICollectionViewFlowLayout
#implementation LabelLayout
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
return YES;
}
- (id)init
{
self = [super init];
if (self) {
[self setup];
}
return self;
}
-(void)prepareLayout
{
[super prepareLayout];
_cellCount = [[self collectionView] numberOfItemsInSection:0];
}
- (void)setup
{
self.itemSize = CGSizeMake(220.0f, 45.0f);
self.sectionInset = UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f);
self.minimumLineSpacing = 10.0f;
self.minimumInteritemSpacing = 20.0f;
}
#end
This would definitely cause blurring as everything is being anti-aliased across two pixels (Note: On a retina screen, you won't see any blurring because each point is actually two pixels so half points technically exist as a single pixel). To fix it you might have to use CGRectIntegral to force the frame to integer bounds. The best way to do that would be:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
for (NSLayoutAttributes *attribute in attributes){
attribute.frame = CGRectIntegral(attribute.frame);
}
}
You should do the same thing for layoutAttributesForItemAtIndexPath:.

Add image to the left of a UILabel

I have this relatively simple question, I think.
Imagine I have a UILabel with some text in it. Then, I want on the left, or right side of
the text also an image to be displayed (added).
Something like this:
http://www.zazzle.com/blue_arrow_button_left_business_card_templates-240863912615266256
Is there a way to do it, using, say UILabel methods? I didn't find such.
Just in case someone else look this up in the future. I would subclass the UIlabel class and add an image property.
Then you can override the setter of the text and image property.
- (void)setImage:(UIImage *)image {
_image = image;
[self repositionTextAndImage];
}
- (void)setText:(NSString *)text {
[super setText:text];
[self repositionTextAndImage];
}
In repositionTextAndImage, you can do your positioning calculation. The code I pasted, just insert an image on the left.
- (void)repositionTextAndImage {
if (!self.imageView) {
self.imageView = [[UIImageView alloc] init];
[self addSubview:self.imageView];
}
self.imageView.image = self.image;
CGFloat y = (self.frame.size.height - self.image.size.height) / 2;
self.imageView.frame = CGRectMake(0, y, self.image.size.width, self.image.size.height);
}
Lastly, override drawTextInRect: and make sure you leave space on the left of your label so that it does not overlap with the image.
- (void)drawTextInRect:(CGRect)rect {
// Leave some space to draw the image.
UIEdgeInsets insets = {0, self.image.size.width + kImageTextSpacer, 0, 0};
[super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
}
I just implemented similar thing in my Live Project, hope it will be helpful.
-(void)setImageIcon:(UIImage*)image WithText:(NSString*)strText{
NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
attachment.image = image;
float offsetY = -4.5; //This can be dynamic with respect to size of image and UILabel
attachment.bounds = CGRectIntegral( CGRectMake(0, offsetY, attachment.image.size.width, attachment.image.size.height));
NSMutableAttributedString *attachmentString = [[NSMutableAttributedString alloc] initWithAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]];
NSMutableAttributedString *myString= [[NSMutableAttributedString alloc] initWithString:strText];
[attachmentString appendAttributedString:myString];
_lblMail.attributedText = attachmentString;
}
Create a custom UIView that contains a UIImageView and UILabel subview. You'll have to do some geometry logic within to size the label to fit the image on the left or right, but it shouldn't be too much.
Create a UIImageView with your image and add the UILabel on top like
[imageview addSubView:label];
Set the frame of the label accordingly to your required position.

Resources