Skip to content

Commit 8dea1ea

Browse files
authored
Support standard bullet styles (#1409)
Reopening: #1354 - Addressed each point in the above PR. - Added alternate ordered list style support per level --- Similar to web browsers, draw different bullets at different levels. To test: - Set the `listIndentStyle` to `.varied` - Create an ordered or unordered list - Add multiple levels to it - Notice the bullet style for level one is different than level two and three - Also, execute "Big Lists" demo, works as expected | Before | After | | ------ | ------ | | ![old](https://user-images.githubusercontent.com/10981/160511981-8ae4d3f2-537e-4d18-a705-5641d59f255d.png) | ![new](https://user-images.githubusercontent.com/10981/160512005-04669be2-ea56-44df-a3af-2fdbb643027c.png) | | <img width="383" alt="Screenshot 2025-02-22 at 4 53 29 PM" src="https://github.com/user-attachments/assets/c8ce0a76-5ff1-4523-a8b4-8547ba8905b6" /> | <img width="384" alt="Screenshot 2025-02-22 at 4 52 42 PM" src="https://github.com/user-attachments/assets/e20a54e3-682b-4f32-8425-22d8a3f0acde" /> | --- - [x] I have considered if this change warrants release notes and have added them to the appropriate section in the `CHANGELOG.md` if necessary.
2 parents 259342a + e556bc7 commit 8dea1ea

5 files changed

Lines changed: 96 additions & 8 deletions

File tree

Aztec/Classes/TextKit/LayoutManager.swift

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ class LayoutManager: NSLayoutManager {
2727
///
2828
var blockquoteBorderWidth: CGFloat = 2
2929

30+
/// The list indent style
31+
///
32+
var listIndentStyle: TextList.IndentStyle = .default
3033

3134
/// Draws the background, associated to a given Text Range
3235
///
@@ -213,15 +216,16 @@ private extension LayoutManager {
213216
}
214217

215218
let characterRange = self.characterRange(forGlyphRange: glyphsToShow, actualGlyphRange: nil)
219+
var firstLevelWidth: CGFloat?
216220

217221
textStorage.enumerateParagraphRanges(spanning: characterRange) { (range, enclosingRange) in
218-
219222
guard textStorage.string.isStartOfNewLine(atUTF16Offset: enclosingRange.location),
220223
let paragraphStyle = textStorage.attribute(.paragraphStyle, at: enclosingRange.location, effectiveRange: nil) as? ParagraphStyle,
221224
let list = paragraphStyle.lists.last
222225
else {
223226
return
224227
}
228+
225229
let attributes = textStorage.attributes(at: enclosingRange.location, effectiveRange: nil)
226230
let glyphRange = self.glyphRange(forCharacterRange: enclosingRange, actualCharacterRange: nil)
227231
let markerRect = rectForItem(range: glyphRange, origin: origin, paragraphStyle: paragraphStyle)
@@ -233,8 +237,22 @@ private extension LayoutManager {
233237
start = textStorage.numberOfItems(in: list, at: enclosingRange.location)
234238
}
235239
}
240+
241+
var indentLevel: Int?
242+
// Determine indentation level, if needed. The indentation level is only determined for the standard list style
243+
if listIndentStyle == .varied {
244+
// only get the width of the first level once
245+
if firstLevelWidth == nil {
246+
firstLevelWidth = paragraphStyle.indentToFirst(TextList.self)
247+
}
248+
249+
// calculate current indent level
250+
let indentWidth = paragraphStyle.indentToLast(TextList.self)
251+
indentLevel = Int(indentWidth / firstLevelWidth!)
252+
}
253+
236254
markerNumber += start
237-
let markerString = list.style.markerText(forItemNumber: markerNumber)
255+
let markerString = list.style.markerText(forItemNumber: markerNumber, indentLevel: indentLevel)
238256
drawItem(markerString, in: markerRect, styled: attributes, at: enclosingRange.location)
239257
}
240258
}

Aztec/Classes/TextKit/ParagraphProperty/TextList.swift

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import Foundation
22
import UIKit
33

4+
fileprivate let DefaultUnorderedListMarkerText = "\u{2022}"
5+
fileprivate let romanMarker = NSTextList(markerFormat: .lowercaseRoman, options: 0)
46

57
// MARK: - Text List
68
//
@@ -14,14 +16,51 @@ open class TextList: ParagraphProperty {
1416
case ordered
1517
case unordered
1618

17-
func markerText(forItemNumber number: Int) -> String {
19+
func markerText(forItemNumber number: Int, indentLevel: Int? = nil) -> String {
1820
switch self {
19-
case .ordered: return "\(number)."
20-
case .unordered: return "\u{2022}"
21+
case .ordered:
22+
if indentLevel == nil {
23+
return "\(number)."
24+
}
25+
26+
switch indentLevel {
27+
case 1:
28+
return "\(number)."
29+
case 2:
30+
let text = getLetter(for: number)
31+
return "\(text)."
32+
default:
33+
// marker for all levels > 2
34+
let text = romanMarker.marker(forItemNumber: number)
35+
return "\(text)."
36+
}
37+
case .unordered:
38+
if indentLevel == nil {
39+
return DefaultUnorderedListMarkerText
40+
}
41+
42+
switch indentLevel {
43+
case 1:
44+
return DefaultUnorderedListMarkerText
45+
case 2:
46+
return "\u{2E30}"
47+
default:
48+
// marker for all levels > 2
49+
return "\u{2B29}"
50+
}
2151
}
2252
}
2353
}
2454

55+
/// List Indent Styles
56+
///
57+
public enum IndentStyle: Int {
58+
/// A default single bullet style for each indentation level
59+
case `default`
60+
/// Use a varied (distinct) bullet style for each indentation level (i.e., WYSIWYG style)
61+
case varied
62+
}
63+
2564
public let reversed: Bool
2665

2766
public let start: Int?
@@ -90,3 +129,22 @@ open class TextList: ParagraphProperty {
90129
return lhs.style == rhs.style && lhs.start == rhs.start && lhs.reversed == rhs.reversed
91130
}
92131
}
132+
133+
/// Returns the letters to use as the ordered list marker text
134+
fileprivate func getLetter(for number: Int) -> String {
135+
let listChars = "abcdefghijklmnopqrstuvwxyz"
136+
let charCount = listChars.count
137+
138+
// for recursion
139+
func convert(_ value: Int) -> String {
140+
if value <= charCount {
141+
return String(listChars[listChars.index(listChars.startIndex, offsetBy: value - 1)])
142+
}
143+
144+
let quotient = (value - 1) / charCount
145+
let remainder = (value - 1) % charCount
146+
return convert(quotient) + String(listChars[listChars.index(listChars.startIndex, offsetBy: remainder)])
147+
}
148+
149+
return convert(abs(number))
150+
}

Aztec/Classes/TextKit/TextView.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,18 @@ open class TextView: UITextView {
231231
// MARK: - Properties: Text Lists
232232

233233
var maximumListIndentationLevels = 7
234-
234+
235+
/// The list indent style
236+
/// Default is `default`, single style for each level.
237+
public var listIndentStyle: TextList.IndentStyle {
238+
get {
239+
return layout.listIndentStyle
240+
}
241+
set {
242+
layout.listIndentStyle = newValue
243+
}
244+
}
245+
235246
// MARK: - Properties: Blockquotes
236247

237248
/// The max levels of quote indentation allowed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ _None._
3838

3939
### New Features
4040

41-
_None._
41+
* Added support for alternate bullet styles per level for ordered and unordered lists [#1409]
4242

4343
### Bug Fixes
4444

Example/Example/EditorDemoController.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ class EditorDemoController: UIViewController {
174174
view.addSubview(separatorView)
175175

176176
editorView.richTextView.textContainer.lineFragmentPadding = 0
177+
editorView.richTextView.listIndentStyle = .varied
177178
// color setup
178179
if #available(iOS 13.0, *) {
179180
view.backgroundColor = UIColor.systemBackground
@@ -257,7 +258,7 @@ class EditorDemoController: UIViewController {
257258
rightMargin -= view.safeAreaInsets.right
258259

259260
scrollInsets.right = -rightMargin
260-
editorView.scrollIndicatorInsets = scrollInsets
261+
editorView.horizontalScrollIndicatorInsets = scrollInsets
261262
}
262263

263264
func updateTitleHeight() {

0 commit comments

Comments
 (0)