Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UILabel detect line breaks

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.

like image 857
morcutt Avatar asked Mar 05 '13 22:03

morcutt


2 Answers

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 Result of NSLog and Simulator

like image 183
βhargavḯ Avatar answered Nov 20 '22 21:11

βhargavḯ


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.

like image 32
Crt Gregoric Avatar answered Nov 20 '22 22:11

Crt Gregoric