Skip to content

Commit 1d4673b

Browse files
authored
Refactor editor with quill structure (#41)
* Refactor editor to show new structure from json * Fix style not applying for newly typed character and update span accordingly * Added attributed string providing variable * Fix apply style by selecting text * Fix apply style by selecting text * Fix remove style from selected text * Fix remove header style * Fix duplication in adding character * Fix active style are applied to whole header * Fix removing work remove extra space between word leads to miscalculation of span lenth * Fix add same style between similar styled text not reflecting on editor * Fix span get broken with emoji in text * Fix header style is not applying to full header when emoji present above it * Update formats and grouped methods * Update template JSON
1 parent 254ed8d commit 1d4673b

21 files changed

+1142
-553
lines changed

RichEditorDemo/RichEditorDemo/ContentView.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,20 @@ import RichEditorSwiftUI
1010

1111
struct ContentView: View {
1212
@ObservedObject var state: RichEditorState
13-
13+
1414
init(state: RichEditorState? = nil) {
1515
if let state {
1616
self.state = state
1717
} else {
18-
if let richText = readJSONFromFile(fileName: "Sample_json", type: RichText.self) {
19-
self.state = .init(input: richText.text, spans: richText.spans)
18+
if let richText = readJSONFromFile(fileName: "Sample_json",
19+
type: RichText.self) {
20+
self.state = .init(richText: richText)
2021
} else {
2122
self.state = .init(input: "Hello World!")
2223
}
2324
}
2425
}
25-
26+
2627
var body: some View {
2728
NavigationStack {
2829
VStack {
@@ -32,7 +33,8 @@ struct ContentView: View {
3233
.toolbar {
3334
ToolbarItem(placement: .topBarTrailing) {
3435
Button(action: {
35-
print("Export JSON")
36+
37+
print("Export JSON == \(state.output())")
3638
}, label: {
3739
Image(systemName: "checkmark")
3840
.padding()

RichEditorDemo/RichEditorDemo/JsonUtils.swift

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88
import Foundation
99

1010

11-
internal func readJSONFromFile<T: Decodable>(fileName: String, type: T.Type, bundle: Bundle? = nil) -> T? {
12-
if let url = (bundle ?? Bundle.main).url(forResource: fileName, withExtension: "json") {
11+
internal func readJSONFromFile<T: Decodable>(fileName: String,
12+
type: T.Type,
13+
bundle: Bundle? = nil) -> T? {
14+
if let url = (bundle ?? Bundle.main)
15+
.url(forResource: fileName, withExtension: "json") {
1316
do {
1417
let data = try Data(contentsOf: url)
1518
let decoder = JSONDecoder()
@@ -30,3 +33,25 @@ internal extension Bundle {
3033
return Bundle(for: RichBundleFakeClass.self)
3134
}
3235
}
36+
37+
38+
func encode<T: Encodable>(model: T?) throws -> String? {
39+
guard let model else { return nil }
40+
do {
41+
let jsonData = try JSONEncoder().encode(model)
42+
let jsonString = String(data: jsonData, encoding: .utf8)
43+
return jsonString
44+
} catch {
45+
throw error
46+
}
47+
}
48+
49+
func decode<T: Decodable>(json string: String) throws -> T? {
50+
guard let data = string.data(using: .utf8) else { return nil }
51+
do {
52+
let content = try JSONDecoder().decode(T.self, from: data)
53+
return content
54+
} catch {
55+
throw error
56+
}
57+
}
Lines changed: 78 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,123 @@
11
{
22
"spans": [
33
{
4-
"from": 0,
5-
"to": 10,
6-
"style": "h1"
4+
"insert": "RichEditor",
5+
"attributes": {
6+
"bold": true,
7+
"header": 1
8+
}
79
},
810
{
9-
"from": 0,
10-
"to": 10,
11-
"style": "bold"
11+
"insert": "\niOS ",
12+
"attributes": {}
1213
},
1314
{
14-
"from": 27,
15-
"to": 35,
16-
"style": "bold"
15+
"insert": "RichEditorSwiftUI ",
16+
"attributes": {
17+
"bold": true,
18+
"italic": true
19+
}
1720
},
1821
{
19-
"from": 52,
20-
"to": 61,
21-
"style": "bold"
22+
"insert": "Rich editor for ",
23+
"attributes": {}
2224
},
2325
{
24-
"from": 52,
25-
"to": 59,
26-
"style": "italic"
26+
"insert": "SwiftUI.\n\n",
27+
"attributes": {
28+
"bold": true,
29+
"underline": true
30+
}
2731
},
2832
{
29-
"from": 52,
30-
"to": 61,
31-
"style": "underline"
33+
"insert": "Features",
34+
"attributes": {
35+
"bold": true,
36+
"header": 3
37+
}
3238
},
3339
{
34-
"from": 61,
35-
"to": 69,
36-
"style": "h3"
40+
"insert": "\nThe editor offers the following ",
41+
"attributes": {}
3742
},
3843
{
39-
"from": 62,
40-
"to": 69,
41-
"style": "bold"
44+
"insert": "options:\n",
45+
"attributes": {
46+
"bold": true,
47+
"italic": true,
48+
"underline": true
49+
}
4250
},
4351
{
44-
"from": 103,
45-
"to": 118,
46-
"style": "bold"
52+
"insert": "\n",
53+
"attributes": {}
4754
},
4855
{
49-
"from": 119,
50-
"to": 126,
51-
"style": "italic"
56+
"insert": "Bold\n",
57+
"attributes": {
58+
"bold": true
59+
}
5260
},
5361
{
54-
"from": 130,
55-
"to": 138,
56-
"style": "underline"
62+
"insert": "Italic\n",
63+
"attributes": {
64+
"italic": true
65+
}
5766
},
5867
{
59-
"from": 160,
60-
"to": 167,
61-
"style": "h3"
68+
"insert": "Underline\n",
69+
"attributes": {
70+
"underline": true
71+
}
6272
},
6373
{
64-
"from": 161,
65-
"to": 167,
66-
"style": "bold"
74+
"insert": "Different ",
75+
"attributes": {}
6776
},
6877
{
69-
"from": 169,
70-
"to": 180,
71-
"style": "bold"
78+
"insert": "Headings\n\n",
79+
"attributes": {
80+
"bold": true,
81+
"italic": true,
82+
"underline": true
83+
}
7284
},
7385
{
74-
"from": 224,
75-
"to": 235,
76-
"style": "bold"
86+
"insert": "\n",
87+
"attributes": {}
7788
},
7889
{
79-
"from": 224,
80-
"to": 235,
81-
"style": "italic"
90+
"insert": "Credits",
91+
"attributes": {
92+
"bold": true,
93+
"header": 3
94+
}
8295
},
8396
{
84-
"from": 224,
85-
"to": 235,
86-
"style": "underline"
97+
"insert": "\n\n",
98+
"attributes": {}
8799
},
88100
{
89-
"from": 237,
90-
"to": 250,
91-
"style": "h4"
101+
"insert": "RichEditor ",
102+
"attributes": {
103+
"bold": true
104+
}
92105
},
93106
{
94-
"from": 238,
95-
"to": 250,
96-
"style": "bold"
107+
"insert": "for SwiftUI is developed and maintained by the ",
108+
"attributes": {}
97109
},
98110
{
99-
"from": 238,
100-
"to": 250,
101-
"style": "italic"
111+
"insert": "Canopas Team.\n\n",
112+
"attributes": {
113+
"bold": true,
114+
"italic": true,
115+
"underline": true
116+
}
102117
},
103118
{
104-
"from": 238,
105-
"to": 250,
106-
"style": "underline"
119+
"insert": "Thank You! 😊\n\n",
120+
"attributes": {}
107121
}
108-
],
109-
"text": "Rich Editor\nApple iOS/macOS WYSIWYG Rich editor for SwiftUI.\n\nFeatures\nThe editor offers the following options\n\n- Bold\n- Italic\n- Underline\n- Different headers\n\nCredits\nRich Editor for swiftUI is owned and maintained by the canopas team\n\nThanks You ☺️\n"
122+
]
110123
}

Sources/RichEditorSwiftUI/Attributes/RichTextAttributeReader.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public extension RichTextAttributeReader {
3232
func richTextAttributes(
3333
at range: NSRange
3434
) -> RichTextAttributes {
35-
if richText.string.count == 0 { return [:] }
35+
if richText.string.utf16Length == 0 { return [:] }
3636
let range = safeRange(for: range, isAttributeOperation: true)
3737
return richText.attributes(at: range.location, effectiveRange: nil)
3838
}

Sources/RichEditorSwiftUI/Attributes/RichTextAttributeWriter+Style.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public extension NSMutableAttributedString {
4646
}
4747

4848
/**
49-
This will reset font size befor multiplying new size
49+
This will reset font size before multiplying new size
5050
*/
5151
private func byTogglingFontSizeFor(style: TextSpanStyle, font: FontRepresentable, shouldAdd: Bool) -> CGFloat {
5252
guard style.isHeaderStyle || style.isDefault else { return font.pointSize }

0 commit comments

Comments
 (0)