I am currently using a UILabel
to display multiple lines of text. The line break most is set to NSLineBreakByWordWrapping
so the the label automatically inserts line breaks. How can I detect where those line breaks are? Basically, I want to return a string with each \n
break included.
Here is my work around for detecting line breaks inside UILabel
- (int)getLengthForString:(NSString *)str fromFrame:(CGRect)frame withFont:(UIFont *)font
{
int length = 1;
int lastSpace = 1;
NSString *cutText = [str substringToIndex:length];
CGSize textSize = [cutText sizeWithFont:font constrainedToSize:CGSizeMake(frame.size.width, frame.size.height + 500)];
while (textSize.height <= frame.size.height)
{
NSRange range = NSMakeRange (length, 1);
if ([[str substringWithRange:range] isEqualToString:@" "])
{
lastSpace = length;
}
length++;
cutText = [str substringToIndex:length];
textSize = [cutText sizeWithFont:font constrainedToSize:CGSizeMake(frame.size.width, frame.size.height + 500)];
}
return lastSpace;
}
-(NSString*) getLinebreaksWithString:(NSString*)labelText forWidth:(CGFloat)lblWidth forPoint:(CGPoint)point
{
//Create Label
UILabel *label = [[UILabel alloc] init];
label.text = labelText;
label.numberOfLines = 0;
label.lineBreakMode = NSLineBreakByWordWrapping;
//Set frame according to string
CGSize size = [label.text sizeWithFont:label.font
constrainedToSize:CGSizeMake(lblWidth, MAXFLOAT)
lineBreakMode:UILineBreakModeWordWrap];
[label setFrame:CGRectMake(point.x , point.y , size.width , size.height)];
//Add Label in current view
[self.view addSubview:label];
//Count the total number of lines for the Label which has NSLineBreakByWordWrapping line break mode
int numLines = (int)(label.frame.size.height/label.font.leading);
//Getting and dumping lines from text set on the Label
NSMutableArray *allLines = [[NSMutableArray alloc] init];
BOOL shouldLoop = YES;
while (shouldLoop)
{
//Getting length of the string from rect with font
int length = [self getLengthForString:labelText fromFrame:CGRectMake(point.x, point.y, size.width, size.height/numLines) withFont:label.font] + 1;
[allLines addObject:[labelText substringToIndex:length]];
labelText = [labelText substringFromIndex:length];
if(labelText.length<length)
shouldLoop = NO;
}
[allLines addObject:labelText];
//NSLog(@"\n\n%@\n",allLines);
return [allLines componentsJoinedByString:@"\n"];
}
How to use
NSString *labelText = @"We are what our thoughts have made us; so take care about what you think. Words are secondary. Thoughts live; they travel far.";
NSString *stringWithLineBreakChar = [self getLinebreaksWithString:labelText forWidth:200 forPoint:CGPointMake(15, 15)];
NSLog(@"\n\n%@",stringWithLineBreakChar);
You just need to set parameters required by getLinebreaksWithString and you will get a string with each "\n" break included.
OutPut
Here is my solution to this problem.
I iterate through all whitespace characters in the provided string, set substrings (from beginning to current whitespace) to my label and check the difference in the height. When the height changes I insert a new line character (simulates a line break).
func wordWrapFormattedStringFor(string: String) -> String {
// Save label state
let labelHidden = label.isHidden
let labelText = label.text
// Set text to current label and size it to fit the content
label.isHidden = true
label.text = string
label.sizeToFit()
label.layoutIfNeeded()
// Prepare array with indexes of all whitespace characters
let whitespaceIndexes: [String.Index] = {
let whitespacesSet = CharacterSet.whitespacesAndNewlines
var indexes: [String.Index] = []
string.unicodeScalars.enumerated().forEach { i, unicodeScalar in
if whitespacesSet.contains(unicodeScalar) {
let index = string.index(string.startIndex, offsetBy: i)
indexes.append(index)
}
}
// We want also the index after the last character so that when we make substrings later we include also the last word
// This index can only be used for substring to this index (not from or including, since it points to \0)
let index = string.index(string.startIndex, offsetBy: string.characters.count)
indexes.append(index)
return indexes
}()
var reformattedString = ""
let boundingSize = CGSize(width: label.bounds.width, height: CGFloat.greatestFiniteMagnitude)
let attributes = [NSFontAttributeName: font]
var runningHeight: CGFloat?
var previousIndex: String.Index?
whitespaceIndexes.forEach { index in
let string = string.substring(to: index)
let stringHeight = string.boundingRect(with: boundingSize, options: .usesLineFragmentOrigin, attributes: attributes, context: nil).height
var newString: String = {
if let previousIndex = previousIndex {
return string.substring(from: previousIndex)
} else {
return string
}
}()
if runningHeight == nil {
runningHeight = stringHeight
}
if runningHeight! < stringHeight {
// Check that we can create a new index with offset of 1 and that newString does not contain only a whitespace (for example if there are multiple consecutive whitespaces)
if newString.characters.count > 1 {
let splitIndex = newString.index(newString.startIndex, offsetBy: 1)
// Insert a new line between the whitespace and rest of the string
newString.insert("\n", at: splitIndex)
}
}
reformattedString += newString
runningHeight = stringHeight
previousIndex = index
}
// Restore label
label.text = labelText
label.isHidden = labelHidden
return reformattedString
}
Image - Comparison of output string with label text.
This solution worked really well in my case, hope it helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With