I am trying to implement a nested numbered list in my text editor app like apple's notes app does. I was able to achieve some of it using NSTextList in UITextView. I want to do nested numbered list but there seem to be no way available. If I add indent to NSParagraphStyle which has NSTextList, it does not work, Attributed String does not reflect indent for some reason.
I came across NSTextListElement watching WWDC 22 videos but there is no documentation or sample code which explains how to implement it.
Can you tell me if it is possible to do nested lists using Text Kit 2 in UITextView (UIKit)? If Yes, What is the correct way to implement it?
class ViewController: UIViewController {
var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
textView = UITextView(usingTextLayoutManager: true)
textView.alwaysBounceVertical = true
textView.keyboardDismissMode = .interactive
textView.layer.borderColor = UIColor.black.cgColor
textView.layer.borderWidth = 2
view.addSubview(textView)
textView.translatesAutoresizingMaskIntoConstraints = false
[ textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
textView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
textView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
textView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
].forEach {
$0.isActive = true
}
self.loadText()
}
func loadText() {
let list = NSTextList(markerFormat: .decimal, options: 0)
list.startingItemNumber = 1
let paragraphStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
paragraphStyle.textLists = [list]
paragraphStyle.firstLineHeadIndent = 40
paragraphStyle.headIndent = 40
let attributes = [NSAttributedString.Key.paragraphStyle : paragraphStyle]
let nsmutableAttributedString = NSMutableAttributedString(string: "Hello World!", attributes: attributes)
textView.textStorage.setAttributedString(nsmutableAttributedString)
}
}
Nested lists are accomplished by including multiple NSTextLists
in your NSParagraphStyle
. Each textList
in the list of textLists
is a level of the nested list hierarchy, with the first list acting as the outermost and last list being the innermost. Sample code in SwiftUI using the RichTextKit library:
import Foundation
import SwiftUI
import RichTextEditor
struct ListTestView: View {
@State
var attrStr: NSAttributedString
var richTextContext = RichTextContext()
init() {
let listLevel1 = NSTextList(markerFormat: .decimal, options: 0)
let listLevel1Style = NSMutableParagraphStyle()
listLevel1Style.textLists = [
listLevel1
]
let listLevel2Style = NSMutableParagraphStyle()
listLevel2Style.textLists = [
listLevel1,
NSTextList(markerFormat: .lowercaseAlpha, options: 0)
]
let firstItems = NSMutableAttributedString(string: "item 1 at level 1\nitem 2 at level 1\n", attributes: [
.paragraphStyle: listLevel1Style
])
let subItem = NSMutableAttributedString(string: "item at level 2\n", attributes: [
.paragraphStyle: listLevel2Style
])
let lastItem = NSMutableAttributedString(string: "item at level 1 again", attributes: [
.paragraphStyle: listLevel1Style
])
firstItems.append(subItem)
firstItems.append(lastItem)
attrStr = firstItems
}
var body: some View {
return VStack(alignment: .leading) {
RichTextEditor(text: $attrStr, context: richTextContext)
}
}
}
struct Preview_ListTestView: PreviewProvider {
static var previews: some View {
ListTestView()
}
}
And the output:
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