4

So basically I want to add an unordered list to a UITextView. And to another UITextView I want to add an ordered list.

I tried using this code, but it only gave me a bullet point after the first time the user presses enter, (not any more than that,) and I can't even backspace it.

- (void)textViewDidChange:(UITextView *)textView
{
    if ([myTextField.text isEqualToString:@"\n"]) {
        NSString *bullet = @"\u2022";
        myTextField.text = [myTextField.text stringByAppendingString:bullet];
    }
}

If you only find a way performing it with Swift then feel free to post a Swift version of the code.

Lyndsey Scott
  • 37,080
  • 10
  • 92
  • 128
Horray
  • 693
  • 10
  • 25

3 Answers3

6

The problem is that you're using

if ([myTextField.text isEqualToString:@"\n"]) {

as your conditional, so the block executes if the entirety of your myTextField.text equals "\n". But the entirety of your myTextField.text only equals "\n" if you haven't entered anything but "\n". That's why right now, this code is only working "the first time the user presses enter"; and when you say "I can't even backspace it," the problem is actually that the bullet point's being re-added with the call to textViewDidChange: since the same conditional is still being met.

Instead of using textViewDidChange: I recommend using shouldChangeTextInRange: in this case so you can know what that replacement text is no matter it's position within the UITextView text string. By using this method, you can automatically insert the bullet point even when the newline is entered in the middle of the block of text... For example, if the user decides to enter a bunch of info, then jump back up a few lines to enter some more info, then tries to press newline in between, the following should still work. Here's what I recommend:

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

    // If the replacement text is "\n" and the
    // text view is the one you want bullet points
    // for
    if ([text isEqualToString:@"\n"]) {

        // If the replacement text is being added to the end of the
        // text view, i.e. the new index is the length of the old
        // text view's text...
        if (range.location == textView.text.length) {
            // Simply add the newline and bullet point to the end
            NSString *updatedText = [textView.text stringByAppendingString:@"\n\u2022 "];
            [textView setText:updatedText];
        }

        // Else if the replacement text is being added in the middle of
        // the text view's text...
        else {

            // Get the replacement range of the UITextView
            UITextPosition *beginning = textView.beginningOfDocument;
            UITextPosition *start = [textView positionFromPosition:beginning offset:range.location];
            UITextPosition *end = [textView positionFromPosition:start offset:range.length];
            UITextRange *textRange = [textView textRangeFromPosition:start toPosition:end];

            // Insert that newline character *and* a bullet point
            // at the point at which the user inputted just the
            // newline character
            [textView replaceRange:textRange withText:@"\n\u2022 "];

            // Update the cursor position accordingly
            NSRange cursor = NSMakeRange(range.location + @"\n\u2022 ".length, 0);
            textView.selectedRange = cursor;

        }
        // Then return "NO, don't change the characters in range" since
        // you've just done the work already
        return NO;
    }

    // Else return yes
    return YES;
}
Lyndsey Scott
  • 37,080
  • 10
  • 92
  • 128
  • (Still making a few updates to my answer to account for mid-text view entries.) – Lyndsey Scott Dec 04 '14 at 22:20
  • I just have one question: what is the point of putting 'text' at the if statement and not myText.text. I tried putting myText.text, and it didn't work, it only worked with 'text'. So my question is, what is the difference? – Horray Dec 04 '14 at 22:21
  • @Mike No, problem... But yeah, as I typed out at the same exact time as you posted, I want to make a few updates to handle mid-textview entries vs. end of textview entries. It's not a big deal or difference, but just would make the answer a bit more correct... – Lyndsey Scott Dec 04 '14 at 22:21
  • @Mike Haha we have a knack for posting at the same exact time, huh? ;) `myText.text` is the text in the text field whereas `text` is just the `replacementText`, i.e. "\n" in this case, as indicated by the delegate method parameters. – Lyndsey Scott Dec 04 '14 at 22:22
  • I don't get the diff, between mid and end textview – Horray Dec 04 '14 at 22:23
  • @Mike I'll explain in a sec after I make the quick edit to account for the difference. – Lyndsey Scott Dec 04 '14 at 22:25
  • @Mike OK, I've made the update. What I meant is that the user could potentially hit "enter" either at the end of the textview's text or somewhere in the middle. The answer now accounts for both possibilities and I've explained why in the code comments. – Lyndsey Scott Dec 04 '14 at 22:33
  • @Mike And if the answer works for you please hit the checkmark next to it to accept it as the answer. – Lyndsey Scott Dec 04 '14 at 22:34
  • Thank you so much. I'm definitely gonna put a check and if i would be able to, I would put 10 upper points, but it only allows one, so here you go. – Horray Dec 04 '14 at 22:53
  • It doesn't let me do that for another uitextview? I have 2 uitextviews, the myTextView - the first one it did work for, but the second one - secondTextView, it didn't work. Can you please show me how to apply the same code to a different textview? – Horray Dec 05 '14 at 07:48
  • @Mike Should work. Just check to make sure you've also set its delegate, i.e. `secondTextView.delegate = self;`. – Lyndsey Scott Dec 05 '14 at 13:31
  • I did that, but it affects both uiTextViews, I only want the bullet points for one uiTextView? – Horray Dec 05 '14 at 23:41
  • @Mike If you only want it to work for one text view then you have to specify that text view with a conditional in `shouldChangeTextInRange:`... But I'm confused. You said "Can you please show me how to apply the same code to a different textview?" but now you're saying " I only want the bullet points for one uiTextView?"... Which one is it? – Lyndsey Scott Dec 06 '14 at 00:08
  • Sorry, my bad. So I tested it out for one uitextview and it worked for that. then when I edited the code to put it on the other uitextview, it gave me problems. Can you show me a way by the first if statement to put mytextview.text instead of just text? I have to do that because if I just put text, it will apply if any textviews value is \n. Sorry for late reply. And thanks for your time. – Horray Dec 08 '14 at 02:23
  • I tried using this, but it didn't work either: if([[myTextview.text stringByAppendingString:text] isEqualToString:@"\n"]) – Horray Dec 08 '14 at 02:49
  • @Mike I'm still not sure I understand what you mean, but I've updated the code so the if statement specifies it's handling the bulletPointTextView – Lyndsey Scott Dec 08 '14 at 02:55
  • Thank you so much!! It worked!! I wish I can give you another check, but unfortunately you can only give 1. Thank you for being so patient. You really helped me tonz!! – Horray Dec 08 '14 at 04:37
  • Your work is pretty cool dude! How can I make it that after bullets get inserted and the textview changes text (textview setText:mutableText) the cursor goes back to where it was holding b4 this line of code executed? –  Dec 09 '14 at 08:24
1

Just in case, anyone was looking for a Swift 2.x solution to the same problem, here's a solution:

func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
    // If the replacement text is "\n" and the
    // text view is the one you want bullet points
    // for
    if (text == "\n") {
        // If the replacement text is being added to the end of the
        // text view, i.e. the new index is the length of the old
        // text view's text...


        if range.location == textView.text.characters.count {
            // Simply add the newline and bullet point to the end
            var updatedText: String = textView.text!.stringByAppendingString("\n \u{2022} ")
            textView.text = updatedText
        }
        else {

            // Get the replacement range of the UITextView
            var beginning: UITextPosition = textView.beginningOfDocument
            var start: UITextPosition = textView.positionFromPosition(beginning, offset: range.location)!
            var end: UITextPosition = textView.positionFromPosition(start, offset: range.length)!
            var textRange: UITextRange = textView.textRangeFromPosition(start, toPosition: end)!
            // Insert that newline character *and* a bullet point
            // at the point at which the user inputted just the
            // newline character
            textView.replaceRange(textRange, withText: "\n \u{2022} ")
            // Update the cursor position accordingly
            var cursor: NSRange = NSMakeRange(range.location + "\n \u{2022} ".length, 0)
            textView.selectedRange = cursor
        }

        return false


    }
    // Else return yes
    return true
}
KuboAndTwoStrings
  • 864
  • 12
  • 20
  • This worked perfectly. Had to make some small changes for Swift 4 syntax. positionFromPosition has been renamed to position(from: to:) and textRangeFromPosition has been renamed to textRange(from: to:) – Mike Carpenter Jun 09 '18 at 21:23
1

Swift 5.x version.

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        //
        // If the replacement text is "\n" and the
        // text view is the one you want bullet points
        // for
        if (text == "\n") {
            // If the replacement text is being added to the end of the
            // text view, i.e. the new index is the length of the old
            // text view's text...


            if range.location == textView.text.count {
                // Simply add the newline and bullet point to the end
                let updatedText: String = textView.text! + "\n \u{2022} "
                textView.text = updatedText
            }
            else {

                // Get the replacement range of the UITextView
                let beginning: UITextPosition = textView.beginningOfDocument
                
                let start: UITextPosition = textView.position(from: beginning, offset: range.location)!
                let end: UITextPosition = textView.position(from: start, offset: range.length)!
                
                let textRange: UITextRange = textView.textRange(from: start, to: end)!
                // Insert that newline character *and* a bullet point
                // at the point at which the user inputted just the
                // newline character
                textView.replace(textRange, withText: "\n \u{2022} ")
                // Update the cursor position accordingly
                let cursor: NSRange = NSMakeRange(range.location + "\n \u{2022} ".count, 0)
                textView.selectedRange = cursor
            }

            return false


        }
        // Else return yes
        return true
    }
tounaobun
  • 14,570
  • 9
  • 53
  • 75