Multiline UILabel without word wrap? - ios

Is it possible to have a UILabel that consists of multiple \n-delimited lines to have lines with their width > the label width to be truncated and not wrapped?
Suppose I have some text like the following:
This is a really long first line of text that is too long to fit horizontally
Short line
Another short line
I want this to appear in my UILabel like so:
1. This is a really long first line of text...
2. Short line
3. Another short line
However what is happening is I'm getting this:
1. This is a really long first line of text
that is too long to fit horizontally
2. Short line...
The third line is getting cut off. I've set the number of lines to 3, but it is still wrapping the first long line. It doesn't seem to matter what I set the line breaks property to on the label--it always wraps that first line. Is there any way to prevent wrapping completely on the label?

I don't think this is possible with any setting you can apply to your label. One way to do it, is to break the string into its individual lines, truncate any line that needs it so that it (with an added ellipsis) fits on one line, then put the string back together with line breaks. Something like this should work for any number of lines,
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UILabel *label;
#property (nonatomic) CGFloat ellipsisWidth;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSString *text = #"This is a really long first line of text that is too long to fit horizontally\nShort line\nAnother short line";
NSString *ellipsis = #"...";
self.ellipsisWidth = [ellipsis sizeWithAttributes:#{NSFontAttributeName:self.label.font}].width;
__block NSMutableString *truncatedString = [#"" mutableCopy];
[text enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) {
[truncatedString appendFormat:#"%#\n", [self oneLineOfString:line withFont:self.label.font]];
}];
NSString *finalString = [truncatedString stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
self.label.numberOfLines = 0;
self.label.text = finalString;
}
-(NSString *)oneLineOfString:(NSString *) aLine withFont:(UIFont *) font {
__block NSString *singleLine = nil;
__block NSString *lastFragment;
[aLine enumerateSubstringsInRange:NSMakeRange(0, aLine.length) options:NSStringEnumerationByWords usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
NSString *textFragment = [aLine substringToIndex:(substringRange.location + substringRange.length)];
CGRect textRect = [textFragment boundingRectWithSize:CGSizeMake(CGFLOAT_MAX ,CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName:font} context:nil];
if (textRect.size.width >= self.label.bounds.size.width - self.ellipsisWidth) {
singleLine = [lastFragment stringByAppendingString:#"..."];
*stop = YES;
}
lastFragment = textFragment;
}];
if (!singleLine) singleLine = aLine; // it doesn't need to be truncated, so return the passed in line
return singleLine;
}
If you want to truncate by character instead of by word, you can pass NSStringEnumerationByComposedCharacterSequences instead of NSStringEnumerationByWords to the options parameter of
enumerateSubstringsInRange:options:usingBlock:.
Of course, you could do it the easy way; stack 3 labels on top of each other, and give each one a line of your text :)

Related

Does characterAtIndex only have to be used for an NSString? And what is the main purpose of characterAtIndex?

I am writing an app on xcode and I am working on the code for the guess button so if the user enters a letter which is correct then the letter will appear in the location, length of the word in the label. I am using a characterAtIndex but an error continues to occure and I don't truly understand what the point of characterAtIndex is. This is my code so far.
#property (weak, nonatomic) IBOutlet UITextField *input; //user input
#property (weak, nonatomic) IBOutlet UILabel *Guessesinc; //number of incorrect guesses
#property (weak, nonatomic) IBOutlet UILabel *word; //the state of word as the user is guessing
#property (weak, nonatomic) NSString *rndw; //the correct and unknown word
- (IBAction)Guess;
- (IBAction)Guess
{
bool match = NO;
NSRange inputRange;
char charinput = [input characterAtIndex: 0];
for(int i = 0; i < self.rndw.length; i++)
{
char tempString = [self.rndw characterAtIndex: i];
if (charinput ==tempString)
{
match = YES;
inputRange = NSMakeRange(i, 1); //location, length
self.word.text = [self.word.text stringByReplacingCharactersInRange: inputRange withString: input];
}
The input characterAtIndex does not work but I do not understand why. Is there a way to fix this issue. If this code did run this portion of the code should produce something like this --d-. So for example if the user entered a d and the correct word is code the word label should look like --d-. Would this code work for what it had to do??
This answer makes a few guesses:
The input text field contains the letter the user entered for the current guess.
The word label shows the current state of the exposed word as the user make their guesses.
rndw is the current word the user is trying to guess.
Given those assumptions about your code, your Guess method is close. It seems your biggest issue is the attempt to get the first letter from the input text field. You are trying to call characterAtIndex: on a UITextField instead of on the field's text property (which is an NSString). You are also attempting to replace a substring with a UILabel.
Here's your Guess method with a few little changes and cleanup. Also note that it is standard convention that variable and method names start with lowercase letters and class names start with uppercase letters.
- (IBAction)guess
{
bool match = NO;
unichar charinput = [self.input.text characterAtIndex:0];
for (NSInteger i = 0; i < self.rndw.length; i++)
{
char tempChar = [self.rndw characterAtIndex:i];
if (charinput == tempChar)
{
match = YES;
NSRange inputRange = NSMakeRange(i, 1); //location, length
self.word.text = [self.word.text stringByReplacingCharactersInRange:inputRange withString:self.rndw.substringWithRange:inputRange];
}
This line:
char charinput = [input characterAtIndex: 0];
...is wrong.
Input is a UILabel. A UILabel is not an NSString. You probably want something like this:
NSString *inputString = self.input.text;
if (inputString.length == 0) {
return; //Don't try to fetch a character from an empty string.
}
char charinput = [input characterAtIndex: 0];
However, even that doesn't make much sense because input is a UILabel, which is a display-only field, not an input field. If you're trying to collect text from the user you want a UITextField or UITextView. For gather a single line of text from the usr a UITextField is probably what you want.
It also isn't clear why you first try to fetch the character from index 0 in your input UILabel, and then later try to fetch characters from a string self.rndw that you never seem to initialize. I guess rdnw is the word the user is supposed to be guessing?

Multiple fonts inside a single UITextField

I have a TextField and three buttons which are 40pts above the TextField. These buttons provide the changing of font size of TextField's text when I clicked on any of them for eg first button set font size to 17 second changes it to 20 and third change it to 24. So I add IbAction to all buttons like
- (IBAction)setRegularText:(id)sender {
self.additionalInfo.font = [UIFont systemFontOfSize:20];
}
And according to button. But it will change the previous entered text too. I want the text font to be change only when user selet the option. Previously entered text's font size must not be changed.
You will need to use the attributed string NSAttributedString. With text field it is best to have a delegate and implement the method on changing the characters in range. This will handle all the cases even when the user pasts the text from somewhere else.
So the NSMutableAttributedString has a method to replace the string in range with a mutable attributed string which is perfect for this method. The new string received by the delegate must simply be converted to the attributed one with a currently set font.
Try something like this:
#interface AttributedTextField : NSObject<UITextFieldDelegate>
#property (nonatomic, strong) NSMutableAttributedString *attributedString;
#property (nonatomic, strong) UIFont *currentFont;
#end
#implementation AttributedTextField
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
// ensure having a font
UIFont *font = self.currentFont;
if(font == nil) {
font = [UIFont systemFontOfSize:12.0f];
}
// ensure having a base string
if(self.attributedString == nil) {
self.attributedString = [[NSMutableAttributedString alloc] initWithString:#""];
}
// append the new string
[self.attributedString replaceCharactersInRange:range withAttributedString:[[NSMutableAttributedString alloc] initWithString:string attributes:#{NSFontAttributeName: font}]];
textField.attributedText = self.attributedString; // assign the new text which is attributed
return NO; // return false as we are overriding the text
}
#end
set the tag of every button as the font size that button should change.
i-e
self.button1.tag = 17;
self.button2.tag = 20;
self.button3.tag = 24;
and use the tag as font size.
i-e
- (IBAction)setRegularText:(UIButton *)sender {
self.additionalInfo.font = [UIFont systemFontOfSize:sender.tag];
}
You can set different text size in textfield like this way:
- (void)setFontString:(NSString *)setString setFontSize: (double) fontSize {
self.txtAnswer.text = #"";
self.txtAnswer.text = setString;
self.txtAnswer.font = [UIFont systemFontOfSize:fontSize];
}
- (IBAction)btn1Tap:(id)sender {
[self setFontString:#"Good Morning" setFontSize:20.0f];
}
- (IBAction)btn2Tap:(id)sender {
[self setFontString:#"Good Afternoon" setFontSize:15.0f];
}
- (IBAction)btn3Tap:(id)sender {
[self setFontString:#"Good Evening" setFontSize:10.0f];
}

UILabel adjustFont with multiline

I have a UILabel which usually has to display one or two words.
Many times one of the words doesn't fit into one line, so I would like to reduce font size in order to fit each word at least in one line (not breaking by character).
Using the technique described in http://beckyhansmeyer.com/2015/04/09/autoshrinking-text-in-a-multiline-uilabel/
self.numberOfLines = 2;
self.lineBreakMode = NSLineBreakByTruncatingTail;
self.adjustsFontSizeToFitWidth = YES;
self.minimumScaleFactor = 0.65;
I've found that it plays well when the second word doesn't fit in just one line.
But it doesn't when there is just one word, or the first word is the one
that doesn't fit.
I managed to solve the case of just one word doing this:
-(void)setText:(NSString *)text
{
self.numberOfLines = [text componentsSeparatedByString:#" "].count > 1 ? 2 : 1;
[super setText:text];
}
But how could I solve those cases where the first word doesn't fit?? Any ideas?
How about this ?
self.numberOfLines = [text componentsSeparatedByString:#" "].count;
[self setAdjustsFontSizeToFitWidth:YES];
But, this will rule out the case where your label text consists of two very small words, eg."how are". In such cases, the entire string will be visible in the first line itself. If it is your requirement to display each word in a separate line then i would recommend you adding a '\n' after every word. This means that you will have to edit the string before assigning it to the label. Thus, a universal solution could be like :
NSString *string = #"how are"; //Let this be the string
NSString *modifiedString = [string stringByReplacingOccurrencesOfString:#" " withString:#"\n"];
[self setText:modifiedString];
[self setTextAlignment:NSTextAlignmentCenter];
[self setAdjustsFontSizeToFitWidth:YES];
[self setNumberOfLines:0];
self.lineBreakMode = NSLineBreakByWordWrapping;

How can I get the length of text entered into UITextView as the user types?

I have aUITextView. I would like to have a way of updating aUILabel as the user times to indicate how many remaining characters are permitted?
In theViewController theUITextView is referenced as
IBOutlet UITextView *message;
I have tried looking at the show connections inspector and I don't see an edit changed option.
I looked at this example UITextField text change event
But it seemed to apply toUITextField notUITextView
This should help:
- (void)textViewDidChange:(UITextView *)textView{
NSString *stringCount = self.textView.text;
int count = (int) stringCount.length;
//assumes you have a label, see the attached gif
self.label.text = [NSString stringWithFormat:#"TextView count is: %d",count];
NSString *stringcount222 = self.textview222.text;
int count222 = (int) stringcount222.length;
self.label222.text = [NSString stringWithFormat:#"No. 2 count is: %d", count222];
}
You need to select the TextView that you are using and control+drag to the ViewController icon and select Delegate:
The you should be able to get a result like this:

enumerateLineFragmentsForGlyphRange:withBlock: returns word fragments

I'm using a UITextView to display some text. In laying out the text, I enumerate the lines of text using the enumerateLineFragmentsForGlyphRange:withBlock: method.
NSInteger shrunkNumberOfLines = 3;
__block NSMutableString *shortenedText = [NSMutableString new];
__block NSInteger currentLine = 0;
__block BOOL needsTruncation = NO;
[detailsTableViewCell.descriptionTextView.layoutManager enumerateLineFragmentsForGlyphRange:NSMakeRange(0, text.length) usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer *textContainer, NSRange glyphRange, BOOL *stop) {
if (currentLine < shrunkNumberOfLines) {
NSRange stringRange = ((glyphRange.length + glyphRange.location) <= text.length) ? glyphRange : NSMakeRange(glyphRange.location, (text.length - glyphRange.location));
NSString *appendString = [text substringWithRange:stringRange];
NSLog(#"%#", appendString);
[shortenedText appendString:appendString];
currentLine += 1;
} else {
needsTruncation = YES;
*stop = YES;
}
}];
However, I'm running into a weird bug: oftentimes, the text that gets displayed in the textview doesn't line up with the text that I see in that appendString.
For example, the text in the textfield might say something like:
President Obama offered a
blueprint for deeper American
engagement in the Middle East.
...but looking at my NSLog statements, those appendStrings are something like:
President Obama offered a blu
eprint for deeper American en
gagement in the Middle East.
I've tried a bunch of things - playing with hyphenationFactor, making sure that the textContainerInsets are correct, etc - but I can't figure this out. What's causing invalid line breaks in the enumerateLineFragmentsForGlyphRange:withBlock: method?
While I'm still not sure what caused the underlying issue above, I've at least found something that solves the symptom: https://stackoverflow.com/a/19603172/686902

Resources