Weirdness with NSTextLists and {UI,NS}TextView undo TextKit2
18:13 02 Aug 2026

NSTextList under TextKit2 is supposed to provide markers described in the Paragraph formats, but it has some problems - specifically on iOS numbering is broken in a few ways, while on the Mac, the same code is almost fully functional. Below I show how NSTextList is supposed to work (including some code), outline the issues and finally show my workaround (which is pretty large and clunky). I've included a little C# code and some pseudocode that should be readable by anyone. I'm hoping someone else out there has found a better workaround.

Background

I've an app that runs on Mac and iOS, and uses a subclass on {NS/UI}TextView to support editing. In TextKit1 days, you used to have to put markers into the text box (which was a major pain), but with TextKit2 they move out into the Paragraph TextLists object and are automatically managed. They now include Markers, HeadIndents, StartingItemNumbers etc.

NSTextList

It works (mostly) well on the Mac. But it definitely has problems on iOS, such as:

  • Sharing NSTextList objects, but sharing is required for numbering across levels, causes crashes

  • Numbering is not automatic. Specifically:

    • fixing numbering when things change above

    • restarting numbering from the right place when you outdent (from level 2 to level 1 say)

    • Merging two lists leaves them as two lists (affects Mac and iOS)

How Its Supposed To Work

Use of NSTextList is outlined here. The key is that a list object is created for each level, and is shared between paragraphs. Each paragraph gets a list object for each level of depth in the paragraph's TextLists array, and those list objects are shared. Here is the paragraphs TextList array and some text:

[list1] This is the first line
[list1, list2] Second line
[list1, list2] Another line
[list1] Last line

yields:

  1. This is the first line

    1. Second line

    2. Another line

  2. Last line

The shared list object means that the last line gets the correct number when we return to the first level of depth.

The Failures

You MUST share list objects between paragraphs for this to work, and on the Mac it works well. But I have found that if you do share list objects on iOS, you get the following error after a small amount of typing when moving to level 2:

2026-08-02 12:00:43.043127+1200 App.iOS[89166:30527684]     3   UIKitCore                           0x0000000185ec85fc __53-[_UITextKit2LayoutController attributedTextInRange:]_block_invoke + 320
2026-08-02 12:00:43.043198+1200 App.iOS[89166:30527684]     4   UIFoundation                        0x00000001851c9fe4 -[NSTextContentStorage enumerateTextElementsFromLocation:options:usingBlock:] + 3904
    5   UIKitCore                           0x0000000185ec845c -[_UITextKit2LayoutController attributedTextInRange:] + 276
2026-08-02 12:00:43.043334+1200 App.iOS[89166:30527684]     6   UIKitCore                           0x0000000185ec87ac -[_UITextKit2LayoutController annotatedSubstringForRange:] + 44
    7   UIKitCore                           0x0000000186884978 -[UITextInputController annotatedSubstringForRange:] + 48
    8   UIKitCore                           0x0000000186893b04 -[UITextView annotatedSubstringForRange:] + 56

This crash happens well after all delegate events have completed, and appears to be in subsequent TextKit2 layout code (my code does not appear in the stack at that point, other than main). I have checked the state of the Storage, and it is perfectly healthy at the end of the last event.

Sometimes sharing list objects on iOS does work briefly, but iOS does not always correctly assign numbers as you move to deeper and shallower lists. And iOS does not fix numbers in a numbered list if you make changes above (e.g. add or delete a line).

So you cannot share list objects between paragraphs, because you will get crashes. And you cannot rely on iOS numbering.

A Workaround:

So on iOS it seems you must copy the parent's list object

[list1]
[list1.copy, list2]

Here is the code to achieve that when creating a paragraph with a new depth (shallower or deeper).

            var newLists = new List();
            var parentLists = parentParagraphStyle.TextLists;
            var copyCount = Math.Min(parentLists.Length, targetDepth);
#if __MACOS__
            //  On macOS, share existing NSTextList instances so TextKit2 manages auto-numbering.
            for (var i = 0; i < copyCount; i++)
                newLists.Add(parentLists[i]);
#endif
            //  Create fresh instances for any remaining levels.
            //  On iOS: always fresh (sharing crashes TextKit2); on macOS: only new/deeper levels.
            for (var i = newLists.Count; i < targetDepth; i++)
            {
                if (IsQuote())
                    newLists.Add(new NSTextList("\u275D", 0));
                else if (IsBullet())
                    newLists.Add(new NSTextList(NSTextListMarkerFormats.Circle, 0));
                else // Numbers
                    newLists.Add(new NSTextList(NSTextListMarkerFormats.Decimal, 0));
            }

With this, on the Mac, numbering will work beautifully because of shared NSTextList objects. On iOS, numbering will work at one level, but will fail when you go up a level (continuation fails). And it won't fix numbers when changes happen.

For that you need a more complex piece of code like this in DidProcessEditing:

  • When any significant change happens (create/delete paragraph in a list), walk all list paragraphs around the change and:

    • correct the "StartingItemNumber" for every item where the depth changes

    • Reset StartingItemNumber for all other items (otherwise some items can get stuck at 1).

Lastly the merging issue - if you JOIN two lists up, the lower list and upper list will each have an internal counter, based on the StartingItemNumber that they both have. If both lists start at one and you join them up, and reset the upper StartingItemNumber... that doesn't fix up the internal counters. So there are multiple rows in the one joined list that start with 1.

Am I missing something here or is it truely broken on iOS. Does anyone have editable multi-level numbered lists working on iOS with automatic numbering and automatic fix-ups when the list changes?

c# ios swift macos textkit2