diff --git a/Aztec/Classes/TextKit/LayoutManager.swift b/Aztec/Classes/TextKit/LayoutManager.swift index fcf67aefa..ac5ce1106 100644 --- a/Aztec/Classes/TextKit/LayoutManager.swift +++ b/Aztec/Classes/TextKit/LayoutManager.swift @@ -27,6 +27,9 @@ class LayoutManager: NSLayoutManager { /// var blockquoteBorderWidth: CGFloat = 2 + /// The list indent style + /// + var listIndentStyle: TextList.IndentStyle = .default /// Draws the background, associated to a given Text Range /// @@ -213,15 +216,16 @@ private extension LayoutManager { } let characterRange = self.characterRange(forGlyphRange: glyphsToShow, actualGlyphRange: nil) + var firstLevelWidth: CGFloat? textStorage.enumerateParagraphRanges(spanning: characterRange) { (range, enclosingRange) in - guard textStorage.string.isStartOfNewLine(atUTF16Offset: enclosingRange.location), let paragraphStyle = textStorage.attribute(.paragraphStyle, at: enclosingRange.location, effectiveRange: nil) as? ParagraphStyle, let list = paragraphStyle.lists.last else { return } + let attributes = textStorage.attributes(at: enclosingRange.location, effectiveRange: nil) let glyphRange = self.glyphRange(forCharacterRange: enclosingRange, actualCharacterRange: nil) let markerRect = rectForItem(range: glyphRange, origin: origin, paragraphStyle: paragraphStyle) @@ -233,8 +237,22 @@ private extension LayoutManager { start = textStorage.numberOfItems(in: list, at: enclosingRange.location) } } + + var indentLevel: Int? + // Determine indentation level, if needed. The indentation level is only determined for the standard list style + if listIndentStyle == .varied { + // only get the width of the first level once + if firstLevelWidth == nil { + firstLevelWidth = paragraphStyle.indentToFirst(TextList.self) + } + + // calculate current indent level + let indentWidth = paragraphStyle.indentToLast(TextList.self) + indentLevel = Int(indentWidth / firstLevelWidth!) + } + markerNumber += start - let markerString = list.style.markerText(forItemNumber: markerNumber) + let markerString = list.style.markerText(forItemNumber: markerNumber, indentLevel: indentLevel) drawItem(markerString, in: markerRect, styled: attributes, at: enclosingRange.location) } } diff --git a/Aztec/Classes/TextKit/ParagraphProperty/TextList.swift b/Aztec/Classes/TextKit/ParagraphProperty/TextList.swift index 84f9f4bac..1fbdd5a0b 100644 --- a/Aztec/Classes/TextKit/ParagraphProperty/TextList.swift +++ b/Aztec/Classes/TextKit/ParagraphProperty/TextList.swift @@ -1,6 +1,8 @@ import Foundation import UIKit +fileprivate let DefaultUnorderedListMarkerText = "\u{2022}" +fileprivate let romanMarker = NSTextList(markerFormat: .lowercaseRoman, options: 0) // MARK: - Text List // @@ -14,14 +16,51 @@ open class TextList: ParagraphProperty { case ordered case unordered - func markerText(forItemNumber number: Int) -> String { + func markerText(forItemNumber number: Int, indentLevel: Int? = nil) -> String { switch self { - case .ordered: return "\(number)." - case .unordered: return "\u{2022}" + case .ordered: + if indentLevel == nil { + return "\(number)." + } + + switch indentLevel { + case 1: + return "\(number)." + case 2: + let text = getLetter(for: number) + return "\(text)." + default: + // marker for all levels > 2 + let text = romanMarker.marker(forItemNumber: number) + return "\(text)." + } + case .unordered: + if indentLevel == nil { + return DefaultUnorderedListMarkerText + } + + switch indentLevel { + case 1: + return DefaultUnorderedListMarkerText + case 2: + return "\u{2E30}" + default: + // marker for all levels > 2 + return "\u{2B29}" + } } } } + /// List Indent Styles + /// + public enum IndentStyle: Int { + /// A default single bullet style for each indentation level + case `default` + /// Use a varied (distinct) bullet style for each indentation level (i.e., WYSIWYG style) + case varied + } + public let reversed: Bool public let start: Int? @@ -90,3 +129,22 @@ open class TextList: ParagraphProperty { return lhs.style == rhs.style && lhs.start == rhs.start && lhs.reversed == rhs.reversed } } + +/// Returns the letters to use as the ordered list marker text +fileprivate func getLetter(for number: Int) -> String { + let listChars = "abcdefghijklmnopqrstuvwxyz" + let charCount = listChars.count + + // for recursion + func convert(_ value: Int) -> String { + if value <= charCount { + return String(listChars[listChars.index(listChars.startIndex, offsetBy: value - 1)]) + } + + let quotient = (value - 1) / charCount + let remainder = (value - 1) % charCount + return convert(quotient) + String(listChars[listChars.index(listChars.startIndex, offsetBy: remainder)]) + } + + return convert(abs(number)) +} diff --git a/Aztec/Classes/TextKit/TextView.swift b/Aztec/Classes/TextKit/TextView.swift index 26ecc5acb..d4284a886 100644 --- a/Aztec/Classes/TextKit/TextView.swift +++ b/Aztec/Classes/TextKit/TextView.swift @@ -231,7 +231,18 @@ open class TextView: UITextView { // MARK: - Properties: Text Lists var maximumListIndentationLevels = 7 - + + /// The list indent style + /// Default is `default`, single style for each level. + public var listIndentStyle: TextList.IndentStyle { + get { + return layout.listIndentStyle + } + set { + layout.listIndentStyle = newValue + } + } + // MARK: - Properties: Blockquotes /// The max levels of quote indentation allowed diff --git a/CHANGELOG.md b/CHANGELOG.md index ac46b3934..9faf937e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,7 +38,7 @@ _None._ ### New Features -_None._ +* Added support for alternate bullet styles per level for ordered and unordered lists [#1409] ### Bug Fixes diff --git a/Example/Example/EditorDemoController.swift b/Example/Example/EditorDemoController.swift index 016a0cb5f..090801f2b 100644 --- a/Example/Example/EditorDemoController.swift +++ b/Example/Example/EditorDemoController.swift @@ -174,6 +174,7 @@ class EditorDemoController: UIViewController { view.addSubview(separatorView) editorView.richTextView.textContainer.lineFragmentPadding = 0 + editorView.richTextView.listIndentStyle = .varied // color setup if #available(iOS 13.0, *) { view.backgroundColor = UIColor.systemBackground @@ -257,7 +258,7 @@ class EditorDemoController: UIViewController { rightMargin -= view.safeAreaInsets.right scrollInsets.right = -rightMargin - editorView.scrollIndicatorInsets = scrollInsets + editorView.horizontalScrollIndicatorInsets = scrollInsets } func updateTitleHeight() {