Each new line adds a numbered list of the UITextView

I am trying to add a number to the beginning of each line whenever the user enters a new line. I would like the numbers to be in order (like in an ordered list), but with my current code, if the user does not add a newline to the end, but instead adds a line in the middle UITextView

, it will continue counting from where he is stopped at the bottom - it means NSUInteger

I have made increments and do not take into account that the user has not made a new line at the end.

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
    if ([text isEqualToString:@"\n"])
    {
        numbered ++;
        NSString *string = [NSString stringWithFormat:@"\n%lu ", (unsigned long)numbered];
        [self insertXEveryNewLine:range :textView :string];

        return NO;
    }
    return YES;
}


- (BOOL)insertXEveryNewLine:(NSRange)place :(UITextView *)View :(NSString *)something
{
    NSRange cursor = NSMakeRange(place.location + 3, 0);

    NSMutableString *mutableT = [NSMutableString stringWithString:View.text];
    [mutableT insertString:something atIndex:place.location];
    [View setText:mutableT];
    return NO;
}

      

The code I just posted adds a numbered list that increments by 1 each new line. Now, if you try to add a new line in the middle and not at the end, it will increment 1 from the last line number, it won't increment from the previous line number. For example, if the user adds 6 lines to UITextView

, then the user goes to line # 3 and adds a new line, it displays # 7 after line # 3, because every time the user makes a new line, it numbered

increases by 1.

Edit

enter image description here

When the user adds a new row after row 1, I want all rows to be updated. Hope this is clear.

+3


source to share


1 answer


This scenario is actually more logically complex than I expected, because dynamically creating a numbered list as user input requires code that handles many different scenarios related to deletes, inserts, cursor position, etc. But at the heart of my answer, this code basically works by splitting a string of a text representation into "\ n" separated "linear" components, removing the current trailing numbers from each string, re-appending the matching numbers in order, and then recombining the string to return into text representation.



- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{

    // Add "1" when the user starts typing into the text field
    if (range.location == 0 && textView.text.length == 0) {

        // If the user simply presses enter, ignore the newline
        // entry, but add "1" to the start of the line.
        if ([text isEqualToString:@"\n"]) {
            [textView setText:@"1 "];
            NSRange cursor = NSMakeRange(range.location + 3, 0);
            textView.selectedRange = cursor;
            return NO;
        }

        // In all other scenarios, append the replacement text.
        else {
            [textView setText:[NSString stringWithFormat:@"1 %@", text]];
        }
    }

    // goBackOneLine is a Boolean to indicate whether the cursor
    // should go back 1 line; set to YES in the case that the
    // user has deleted the number at the start of the line
    bool goBackOneLine = NO;

    // Get a string representation of the current line number
    // in order to calculate cursor placement based on the
    // character count of the number
    NSString *stringPrecedingReplacement = [textView.text substringToIndex:range.location];
    NSString *currentLine = [NSString stringWithFormat:@"%lu", [stringPrecedingReplacement componentsSeparatedByString:@"\n"].count + 1];

    // If the replacement string either contains a new line
    // character or is a backspace, proceed with the following
    // block...
    if ([text rangeOfString:@"\n"].location != NSNotFound || range.length == 1) {

        // Combine the new text with the old
        NSString *combinedText = [textView.text stringByReplacingCharactersInRange:range withString:text];

        // Seperate the combinedText into lines
        NSMutableArray *lines = [[combinedText componentsSeparatedByString:@"\n"] mutableCopy];

        // To handle the backspace condition
        if (range.length == 1) {

            // If the user deletes the number at the beginning of the line,
            // also delete the newline character proceeding it
            // Check to see if the user deleting a number and
            // if so, keep moving backwards digit by digit to see if the
            // string preceeded by a newline too.
            if ([textView.text characterAtIndex:range.location] >= '0' && [textView.text characterAtIndex:range.location] <= '9') {

                NSUInteger index = 1;
                char c = [textView.text characterAtIndex:range.location];
                while (c >= '0' && c <= '9') {

                    c = [textView.text characterAtIndex:range.location - index];

                    // If a newline is found directly preceding
                    // the number, delete the number and move back
                    // to the preceding line.
                    if (c == '\n') {
                        combinedText = [textView.text stringByReplacingCharactersInRange:NSMakeRange(range.location - index, range.length + index) withString:text];

                        lines = [[combinedText componentsSeparatedByString:@"\n"] mutableCopy];

                        // Set this variable so the cursor knows to back
                        // up one line
                        goBackOneLine = YES;

                        break;
                    }
                    index ++;
                }
            }

            // If the user attempts to delete the number 1
            // on the first line...
            if (range.location == 1) {

                NSString *firstRow = [lines objectAtIndex:0];

                // If there text left in the current row, don't
                // remove the number 1
                if (firstRow.length > 3) {
                    return  NO;
                }

                // Else if there no text left in text view other than
                // the 1, don't let the user delete it
                else if (lines.count == 1) {
                    return NO;
                }

                // Else if there no text in the first row, but there text
                // in the next, move the next row up
                else if (lines.count > 1) {
                    [lines removeObjectAtIndex:0];
                }
            }
        }

        // Using a loop, remove the numbers at the start of the lines
        // and store the new strings in the linesWithoutLeadingNumbers array
        NSMutableArray *linesWithoutLeadingNumbers = [[NSMutableArray alloc] init];

        // Go through each line
        for (NSString *string in lines) {

            // Use the following string to make updates
            NSString *stringWithoutLeadingNumbers = [string copy];

            // Go through each character
            for (int i = 0; i < (int)string.length ; i++) {

                char c = [string characterAtIndex:i];

                // If the character a number, remove it
                if (c >= '0' && c <= '9') {
                    stringWithoutLeadingNumbers = [stringWithoutLeadingNumbers stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:@""];
                } else {
                    // And break from the for loop since the number
                    // and subsequent space have been removed
                    break;
                }
            }

            // Remove the white space before and after the string to
            // clean it up a bit
            stringWithoutLeadingNumbers = [stringWithoutLeadingNumbers stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];

            [linesWithoutLeadingNumbers addObject:stringWithoutLeadingNumbers];
        }

        // Using a loop, add the numbers to the start of the lines
        NSMutableArray *linesWithUpdatedNumbers = [[NSMutableArray alloc] init];

        for (int i = 0 ; i < linesWithoutLeadingNumbers.count ; i ++) {
            NSString *updatedString = [linesWithoutLeadingNumbers objectAtIndex:i];
            NSString *lineNumberString = [NSString stringWithFormat:@"%d ", i + 1];
            updatedString = [lineNumberString stringByAppendingString:updatedString];
            [linesWithUpdatedNumbers addObject:updatedString];
        }

        // Then combine the array back into a string by re-adding the
        // new lines
        NSString *combinedString = @"";

        for (int i = 0 ; i < linesWithUpdatedNumbers.count ; i ++) {
            combinedString = [combinedString stringByAppendingString:[linesWithUpdatedNumbers objectAtIndex:i]];
            if (i < linesWithUpdatedNumbers.count - 1) {
                combinedString = [combinedString stringByAppendingString:@"\n"];
            }
        }

        // Set the cursor appropriately.
        NSRange cursor;
        if ([text isEqualToString:@"\n"]) {
           cursor = NSMakeRange(range.location + currentLine.length + 2, 0);
        } else if (goBackOneLine) {
            cursor = NSMakeRange(range.location - 1, 0);
        } else {
            cursor = NSMakeRange(range.location, 0);
        }

        textView.selectedRange = cursor;

        // And update the text view
        [textView setText:combinedString];

        return NO;
    }

    return YES;
}

      

+5


source







All Articles