Labels for UISlider values and centering text - ios

I have a slider and would like to display text labels for some of the values on the slider.
I would like numbers to be centered on top of values. Labels are of fixed width - predefined. So I think all I need to do is calculate space width in between labels and keep incrementing x position when positioning each label. The problem that I am seeing though is there more labels there are the more each label slides to the right. I know this because the value set is always in the center of the slider in my test cases. Text is centered within label. Please see pictures on the first picture labels are almost correctly centered. The way to see what I mean is looking at the last label in both pictures. I almost think that it is the border of the label perhaps but I do not set the border either. I am not sure what I could be doing wrong in terms of math, but must be missing something. Also, it seems like the fir label maybe more than 1/2 label width of to the left of slider start position.
EDIT This is because the frame X starting position DOES not match the x starting position of the slider. See pic. Now I just need to figure out how to calculate the offset. The red line is drawn at slider.frame.origin.x. You can see that slider is off by a few points. The ball should be in the center at 3.125 - 25 .. but it is slightly of.
The way space between labels is calculated:
float xPosition = sliderFrame.origin.x;
//Slider adjusted width
float sliderWidth = sliderFrame.size.width - xPosition;
float spacedApart = (sliderWidth - (LABEL_WIDTH * numberofticks)) / (float) (numberofticks);
Each label is displayed at:
CGRect mpkLabelFrame = CGRectMake(xPosition - LABEL_WIDTH/2, (yPosition - spaceBetweenLabelsAndSlider), LABEL_WIDTH, LABEL_HEIGHT)+3; //need to increase by 3 to make it work better
//display label do other things
xPosition += (LABEL_WIDTH + spacedApart);

You don't need to take into account the sliders x coordinate to figure out the spacing. In fact, since they are distributed equally you don't need the label size as well.
CGFloat labelSpacing = sliderFrame.size.width/(numberOfTicks-1);
xPosition = sliderFrame.origin.x;
Set the label's frames:
CGRect mpkLabelFrame = CGRectMake(xPosition - LABEL_WIDTH/2, (yPosition - spaceBetweenLabelsAndSlider), LABEL_WIDTH, LABEL_HEIGHT);
xPosition += labelSpacing;
EDIT: code for testing the approach in an viewController:
- (void)viewDidLoad{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(40, 100, 240, 10)];
[self.view addSubview:slider];
int numberOfTicks = 7;
CGFloat labelSpacing = slider.frame.size.width/(numberOfTicks-1);
CGFloat xPosition = slider.frame.origin.x;
CGFloat labelWidth = 10;
CGFloat yPosition = CGRectGetMinY(slider.frame)-20;
for (int i=0;i<numberOfTicks;i++){
CGRect labelFrame = CGRectMake(xPosition - labelWidth/2, (yPosition), labelWidth, 20);
UILabel *label = [[UILabel alloc] initWithFrame:labelFrame];
label.backgroundColor = [UIColor redColor];
label.adjustsFontSizeToFitWidth = YES;
label.text = [NSString stringWithFormat:#"%i",i];
[self.view addSubview:label];
xPosition += labelSpacing;
}
}

Related

How to programmatically sizeToFit width AND height on UILabel?

I'm programmatically creating multi-line UILabels ([label setNumberOfLines:0];).
The built-in sizeToFit method of UILabel works great for 1 line UILabels, but for multi-line text, it sets the height properly, but the width is set too small, causing longer text lines to wrap.
I don't know the label width until after the user enters their text. I want to resize the labels to fit the width of the longest line of text. And per #DonMag's comment, I also want to restrict the label to not be wider than the screen.
I tried different lineBreakMode settings but there isn't a 'nowrap' option.
I've searched SO and there are many related solutions but none that solve the problem of sizeToFit for both width and height.
Is there a way to programmatically size a multi-line UILabel to fit BOTH the width AND the height of the text?
You can do this with boundingRectWithSize...
Add your label to the view and give it a starting width constraint (doesn't really matter what value, as it will be changed).
Keep a reference to that width constraint (IBOutlet works fine if you're using IB).
Don't give it a height constraint.
When you set the text of the label, you can use this to change its width:
// get the font of the label
UIFont *theFont = _theLabel.font;
// get the text of the label
NSString *theString = _theLabel.text;
// calculate the bounding rect, limiting the width to the width of the view
CGRect r = [theString boundingRectWithSize:CGSizeMake(self.view.frame.size.width, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:#{NSFontAttributeName: theFont}
context:nil];
// change the constant of the constraint to the calculated width
_theWidthConstraint.constant = ceil(r.size.width);
// NOTE: If you are *not* using auto-layout,
// this same calculation can be used to explicitly set
// the .frame of the label.
Edit:
As per the OP's requirement, a complete, runnable example -- using code only, no storyboards / IB -- can be found here: https://github.com/DonMag/MultilineLabelFitWidth
Edit 2:
GitHub project updated... now includes examples for both manual frame setting and auto layout / constraints.
With some more experimentation, I found something that does the trick that I have not seen in SO (yet...). In general it works like this:
Find the longest text line
Set numberOfLines to 1 (temporarily)
Set label text to longest text line
Call label.sizeToFit (sets label width for longest line)
Set numberOfLines to 0 (multi-line)
Set label text to full multi-line text
Call label.sizeToFit (sets label height for all lines)
Voila! Now your UILabel is sized to fit your multi-line text.
Here is an example (demo project on GitHub: UILabelSizeToFitDemo):
- (UILabel *)label = nil;
- (void)updateLabel:(NSString *)notes {
// close to the "sticky" notes color
UIColor *bananaColor = [ViewController colorWithHexString:#"#FFFC79"];
if (_label == nil) {
_label = [[UILabel alloc] init];
_label.numberOfLines = 0;
_label.textColor = UIColor.blackColor;
[_label setBackgroundColor:[bananaColor colorWithAlphaComponent:0.9f]];
_label.textAlignment = NSTextAlignmentLeft;
[self.view addSubview:_label];
}
// make font size based on screen size
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
CGFloat fontSize = MIN(screenWidth,screenHeight) / 12;
[_label setFont:[UIFont systemFontOfSize:fontSize]];
// split lines
NSArray *lines = [notes componentsSeparatedByString:#"\n"];
NSString *longestLine = lines[0]; // prime it with 1st line
// fill a temp UILabel with each line to find the longest line
for (int i = 0; i < lines.count; i++) {
NSString *line = (NSString *)lines[i];
if (longestLine == nil || line.length > longestLine.length) {
longestLine = line;
}
}
// force UILabel to fit the largest line
[_label setNumberOfLines:1];
[_label setText:longestLine];
[_label sizeToFit];
// make sure it doesn't go off the screen
if (_label.frame.size.width > screenWidth) {
CGRect frame = _label.frame;
frame.size.width = screenWidth - 20;
_label.frame = frame;
}
// now fill with the actual notes (this saves the previous width)
[_label setNumberOfLines:0];
[_label setText:notes];
[_label sizeToFit];
// center the label in my view
CGPoint center = CGPointMake(self.view.bounds.size.width / 2, self.view.bounds.size.height / 2);
[_label setCenter:center];
}
UPDATE: Here is an alternate complete solution, using the boundinRectWithSize from the code snippet by #DonMag:
-(void)updateLabel:(NSString *)notes {
// close to the "sticky" notes color
UIColor *bananaColor = [ViewController colorWithHexString:#"#FFFC79"];
if (_label == nil) {
_label = [[UILabel alloc] init];
_label.numberOfLines = 0;
_label.textColor = UIColor.blackColor;
_label.backgroundColor = [bananaColor colorWithAlphaComponent:0.9f];
_label.textAlignment = NSTextAlignmentLeft;
[self.view addSubview:_label];
}
// set new text
_label.text = notes;
// make font size based on screen size
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
CGFloat fontSize = MIN(screenWidth,screenHeight) / 12;
[_label setFont:[UIFont systemFontOfSize:fontSize]];
// calculate the bounding rect, limiting the width to the width of the view
CGRect frame = [notes boundingRectWithSize:CGSizeMake(self.view.frame.size.width, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:#{NSFontAttributeName: _label.font}
context:nil];
// set frame and then use sizeToFit
[_label setFrame:frame];
[_label sizeToFit];
// center the label in my view
CGPoint center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2);
[_label setCenter:center];
}

Find absolute position of sub-string in UILabel to place another UILabel on top

I have the following UI in storyboard.
The structure is as follows
Parent (UIView)
ThinBlock (UIView) (green one)
MusicStaffView (UIView) (musical notation, actually it's UILabel as loaded via through setting via custom class)
My goal is to place another UILabel which contains an individual musical note, or say a string with length 1 on top of specific location when the music plays.
The end result should be like this.
The setup as you see above is based on auto-layout. My understand is that if I need to manually place something individually on top, thus I don't need auto-layout enabled for that element.
So it seems that I need to find absolute position of individual character inside UILabel, create UILabel on the fly, then add it as a child of UIViewController's view. But I can't achieve that, it always placed on the top left no matter what.
Here is the code I use to get the target string, its absolute position, creation of UILabel and place it at that position on the fly.
// calculate prefix size
NSRange range = NSMakeRange(2, 1);
NSString *prefix = [self.leftPattern.scoreLabel.text substringToIndex:range.location];
NSDictionary *attributes = #{NSFontAttributeName: self.leftPattern.scoreLabel.font};
CGSize size = [prefix sizeWithAttributes:attributes];
CGPoint p = CGPointMake(size.width, 0);
NSLog(#"prefix width: %f",p.x);
NSLog(#"prefix height: %f",p.y);
// calculate size of target string
NSString *targetString = [self.leftPattern.scoreLabel.text substringWithRange:range];
CGSize targetSize = [targetString sizeWithAttributes:attributes];
NSLog(#"Old point %#", NSStringFromCGPoint(self.leftPattern.frame.origin));
// find absolute point to place our UILabel later
CGPoint localPoint = self.leftPattern.scoreLabel.frame.origin;
CGPoint absolutePoint = [self.leftPattern.scoreLabel convertPoint:localPoint toView:[self.view window]];
NSLog(#"Absolute point %#", NSStringFromCGPoint(absolutePoint));
// create UILabel to place at calculated absolute point
UILabel *newLabel = [[UILabel alloc]initWithFrame:CGRectMake(absolutePoint.x + p.x, absolutePoint.y - 10, targetSize.width, targetSize.height)];
NSLog(#"newLabel frame: %#", NSStringFromCGRect(newLabel.frame));
newLabel.text = targetString;
newLabel.font = self.leftPattern.scoreLabel.font;
newLabel.numberOfLines = 1;
newLabel.baselineAdjustment = UIBaselineAdjustmentAlignBaselines; // or UIBaselineAdjustmentAlignCenters, or UIBaselineAdjustmentNone
newLabel.adjustsFontSizeToFitWidth = NO;
newLabel.clipsToBounds = YES;
newLabel.backgroundColor = [UIColor clearColor];
newLabel.textColor = [UIColor redColor];
newLabel.textAlignment = NSTextAlignmentCenter;
[newLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:newLabel];
[self.view layoutIfNeeded];
What I got from NSLog() is as follows
prefix is |\
prefix width: 18.780000
prefix height: 0.000000
Old point {8, 0}
Absolute point {49.333333333333314, 101.33333333333331}
newLabel frame: {{68.113333333333316, 101.33333333333331}, {16, 43}}
Also, it gets worse and more difficult when I rotate the device into landscape, UILabel is aligned center thus introduce space in front in which I don't know how to calculate its space width too.
Solution or suggestion to solve this will be really appreciated!

UIButton Title Label Not Filling When Text Alignment Is Centered

I have a UIButton and am trying to center the text. The below works, but it cuts off my button's titleLabel way before the end of the button. I want it to grow until the edge of the subview, i.e. a CGRect frame of (8+9,button height ,button width - 8 - 9, button height)
self.setDestinationButton.titleLabel.textAlignment = NSTextAlignmentCenter;
self.setDestinationButton.layer.masksToBounds = NO;
self.setDestinationButton.titleLabel.adjustsFontSizeToFitWidth = YES;
self.setDestinationButton.titleLabel.minimumScaleFactor = .75;
self.setDestinationButton.titleLabel.lineBreakMode = NSLineBreakByTruncatingTail;
UIImageView *destinationIcon = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"grey_dot"]];
destinationIcon.frame = CGRectMake(8, 18 ,9, 9);//choose values that fit properly inside the frame of your baseButton
//or grab the width and height of yourBaseButton and change accordingly
destinationIcon.contentMode=UIViewContentModeScaleAspectFill;//or whichever mode works best for you
[self.setDestinationButton addSubview:destinationIcon];

Adding UILabels to UIView dynamically

I have a web service that brings some text data back. The idea is to extract the text and create UILabels for the text to show on screen. I however, do not know two things:
How many labels are needed
The length of the text
Due to having no prior knowledge of these things I need a way to create some labels that have the right length for the text it contains.
I've managed to store the data into some objects that are in an array and then iterate through them creating the labels.. something like this:
for (BBItemAttributes *attribute in self.item.productAttributes){
UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(100, 100, 50, 10)];
label.text = attribute.displayTemplate;
[self.scrollView addSubview:label];
}
Obviously the problem here is due to creating the UILabels in code and having each one a hard coded CGRect size they are all onto of each other and sometimes don't fit in their respective boxes due to text being too long.
I need a way to line the labels up all on the same X axis point and on different Y axis points, so that they sit next to each other a certain space apart.
Is there a better way to do this?
You need to do something like this;
CGFloat yOrigin = 100;
CGFloat fixedSpace = 10;
for (BBItemAttributes *attribute in self.item.productAttributes)
{
UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(100, yOrigin, 50, 10)];
label.text = attribute.displayTemplate;
label.numberOfLines = 0;
[label sizeToFit]; // Resizes label to fit the text.
[self.scrollView addSubview:label];
yOrigin += label.frame.size.height + fixedSpace;
}
Try this. it will help you.
float yAxis = 0;
for (int i = 0; i< 50; i++)//for (BBItemAttributes *attribute in self.item.productAttributes){
{
CGSize size;
UIFont *font = [UIFont systemFontOfSize:15.0]; // Set Your Font
//Your String = attribute.displayTemplate
if (ios7)//Condition to check if ios7
{
//iOS 7
CGRect frame = [#"Your String" boundingRectWithSize:CGSizeMake(50, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:font}
context:nil];
size = CGSizeMake(frame.size.width, frame.size.height+1);
}
else
{
//iOS 6.0
size = [#"Your String" sizeWithFont:font constrainedToSize:CGSizeMake(50, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping];
}
UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(100, yAxis, 50, size.height)];
label.text = #"Your String";//attribute.displayTemplate
label.numberOfLines = 0;
label.tag = i; // Set tag if you want to access in future. :)
[self.scrollView addSubview:label];
// Increase yAxis
yAxis = yAxis + size.height + 10;//10 is extra space if you want between two label
}
// Do Not forget to set Contentsize of your ScrollView.
self.scrollView.contentSize = CGSizeMake(320, yAxis + 20);
If your showing a data structure that is a like a list of labels, I would suggest that you use UITableViewController for showing the list.It has the built in facility and properties that make it easy to display a list of objects.
Now the answer to your first Question.You can get the number of labels by determining the number of objects that are in your array. Something like this :
NSInteger *noOfLabels = yourArray.count;
As far as the length of the text is concerned, I suggest not keeping it very lengthy because Labels are not good for lengthy texts. You can however, specify the number of a lines for a particular label in case if it has lengthy text.This will make the text appear in two lines. Though you will have to adjust width and height accordingly.
You can get the text length by doing something similar to this :
NSString *text;
NSInteger *i = (NSInteger*)[text length];
Hope this helps.

Dyamic frame calculation for UIImages in UITableViewCell

I am putting few icons on UITableViewCell. The number of icons can vary from 1 to 6. Icons should not have fixed X position and instead they should be dynamically placed considering the cell space. So if there are 3 icons they should look placed centric to the cell. Also, the icon spacing should also vary. For instance when it is 6 icons all of them will be placed with less margins in between and when there are 3 then margin will be more and so will be the X position.
Please suggest some quick way to calculate this frame. I am running the app both on iOS 6 and iOS 7.
This is what I have tried by far but this does not seems to work well with icon count variation. Also, in between space is not dynamic with this.
int maxIconTypes = 6;
CGFloat innerPadding = ([self isIOS7]) ? 15.0f : 9.0f;
CGRect adjustedBoundsIOS6 = [self bounds];
CGFloat adjustedWidth = ([self isIOS7]) ? self.bounds.size.width : adjustedBoundsIOS6.size.width;
CGFloat xOrigin = innerPadding;
CGFloat iconViewSize = 25.0f;
CGFloat ySpacer = (self.bounds.size.height - iconSize) / 2;
CGFloat xSpacer = ((adjustedWidth - (innerPadding * 2)) - (maxRequestTypes * iconViewSize)) / (maxIconTypes - 1);
for (NSString *icon in iconList) {
UIImageView *anIconView = [[UIImageView alloc] initWithImage:[UIImage icon]];
if (anIconView.image) {
anIconView.frame = CGRectMake(xOrigin + originPadding, ySpacer, anIconView.image.size.width, anIconView.image.size.height);
[self.contentView addSubview:anIconView];
xOrigin += anIconView.frame.size.width + xSpacer;
}
}
However many items you have, the number of spaces is the same if you want equal spacing with half spacing at the start and end:
half width full width half
half width full width full width half
So, you just need to know the full width available and the combined width of the items. A simple multiply (to get the combined width of the items) and subtract (from the full width available) gives you the remaining width for the 'spacers'. Divide by the number of items to get the xSpacer, set the initial xOrigin to xSpacer * 0.5
Here is an example of what Wain is explaining:
//Im doing this in a UIView rather than in a UITableViewCell but idea is the same
int cellWidth = CGRectGetWidth(self.view.bounds);
//num of icons
int iconCount = 6;
//size of icon
int iconSize = 25;
//The max padding we can have
float maxPadding = (cellWidth - (iconSize * iconCount)) / iconCount;
//Our offset
float xOrigin = maxPadding / 2;
//Loooop
for (int i = 0; i < iconCount; i++) {
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(xOrigin, self.view.center.y, iconSize, iconSize)];
xOrigin += (iconSize + maxPadding);
label.backgroundColor = [UIColor blueColor];
[self.view addSubview:label];
}

Resources