Skip to content

Commit 2a07709

Browse files
authored
Support Strikethrough (#50)
* Added strike through support * fixed under line added to one extra index * make key same as flutter quill
1 parent cb357b9 commit 2a07709

7 files changed

+262
-195
lines changed

Sources/RichEditorSwiftUI/Attributes/RichTextAttributeReader.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public extension RichTextAttributeReader {
6060
let attributes = richTextAttributes(at: range)
6161
let traits = richTextFont(at: range)?.fontDescriptor.symbolicTraits
6262
var styles = traits?.enabledRichTextStyles ?? []
63-
// if attributes.isStrikethrough { styles.append(.strikethrough) }
63+
if attributes.isStrikethrough { styles.append(.strikethrough) }
6464
if attributes.isUnderlined { styles.append(.underline) }
6565
return styles
6666
}

Sources/RichEditorSwiftUI/Attributes/RichTextAttributeWriter+Style.swift

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public extension NSMutableAttributedString {
3636

3737
let attributeValue = newValue ? 1 : 0
3838
if style == .underline { return setRichTextAttribute(.underlineStyle, to: attributeValue, at: range) }
39+
if style == .strikethrough { return setRichTextAttribute(.strikethroughStyle, to: attributeValue, at: range) }
3940
guard let font = richTextFont(at: range) else { return }
4041
let styles = richTextStyles(at: range)
4142
let shouldAdd = newValue && !styles.hasStyle(style)

Sources/RichEditorSwiftUI/Attributes/RichTextAttributes+RichTextStyle.swift

+8-8
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ import Foundation
99

1010
public extension RichTextAttributes {
1111

12-
// /**
13-
// Whether or not the attributes has a strikethrough style.
14-
// */
15-
// var isStrikethrough: Bool {
16-
// get { self[.strikethroughStyle] as? Int == 1 }
17-
// set { self[.strikethroughStyle] = newValue ? 1 : 0 }
18-
// }
19-
//
12+
/**
13+
Whether or not the attributes has a strikethrough style.
14+
*/
15+
var isStrikethrough: Bool {
16+
get { self[.strikethroughStyle] as? Int == 1 }
17+
set { self[.strikethroughStyle] = newValue ? 1 : 0 }
18+
}
19+
2020
/**
2121
Whether or not the attributes has an underline style.
2222
*/

Sources/RichEditorSwiftUI/Data/Models/RichAttributes.swift

+67-45
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public struct RichAttributes: Codable {
1313
public let bold: Bool?
1414
public let italic: Bool?
1515
public let underline: Bool?
16+
public let strike: Bool?
1617
public let header: HeaderType?
1718
public let list: ListType?
1819
public let indent: Int?
@@ -22,6 +23,7 @@ public struct RichAttributes: Codable {
2223
bold: Bool? = nil,
2324
italic: Bool? = nil,
2425
underline: Bool? = nil,
26+
strike: Bool? = nil,
2527
header: HeaderType? = nil,
2628
list: ListType? = nil,
2729
indent: Int? = nil
@@ -30,6 +32,7 @@ public struct RichAttributes: Codable {
3032
self.bold = bold
3133
self.italic = italic
3234
self.underline = underline
35+
self.strike = strike
3336
self.header = header
3437
self.list = list
3538
self.indent = indent
@@ -39,6 +42,7 @@ public struct RichAttributes: Codable {
3942
case bold = "bold"
4043
case italic = "italic"
4144
case underline = "underline"
45+
case strike = "strike"
4246
case header = "header"
4347
case list = "list"
4448
case indent = "indent"
@@ -50,6 +54,7 @@ public struct RichAttributes: Codable {
5054
self.bold = try values.decodeIfPresent(Bool.self, forKey: .bold)
5155
self.italic = try values.decodeIfPresent(Bool.self, forKey: .italic)
5256
self.underline = try values.decodeIfPresent(Bool.self, forKey: .underline)
57+
self.strike = try values.decodeIfPresent(Bool.self, forKey: .strike)
5358
self.header = try values.decodeIfPresent(HeaderType.self, forKey: .header)
5459
self.list = try values.decodeIfPresent(ListType.self, forKey: .list)
5560
self.indent = try values.decodeIfPresent(Int.self, forKey: .indent)
@@ -62,6 +67,7 @@ extension RichAttributes: Hashable {
6267
hasher.combine(bold)
6368
hasher.combine(italic)
6469
hasher.combine(underline)
70+
hasher.combine(strike)
6571
hasher.combine(header)
6672
hasher.combine(list)
6773
hasher.combine(indent)
@@ -76,6 +82,7 @@ extension RichAttributes: Equatable {
7682
lhs.bold == rhs.bold
7783
&& lhs.italic == rhs.italic
7884
&& lhs.underline == rhs.underline
85+
&& lhs.strike == rhs.strike
7986
&& lhs.header == rhs.header
8087
&& lhs.list == rhs.list
8188
&& lhs.indent == rhs.indent
@@ -88,13 +95,15 @@ extension RichAttributes {
8895
header: HeaderType? = nil,
8996
italic: Bool? = nil,
9097
underline: Bool? = nil,
98+
strike: Bool? = nil,
9199
list: ListType? = nil,
92100
indent: Int? = nil
93101
) -> RichAttributes {
94102
return RichAttributes(
95103
bold: (bold != nil ? bold! : self.bold),
96104
italic: (italic != nil ? italic! : self.italic),
97105
underline: (underline != nil ? underline! : self.underline),
106+
strike: (strike != nil ? strike! : self.strike),
98107
header: (header != nil ? header! : self.header),
99108
list: (list != nil ? list! : self.list),
100109
indent: (indent != nil ? indent! : self.indent)
@@ -111,6 +120,7 @@ extension RichAttributes {
111120
bold: (att.bold != nil ? (byAdding ? att.bold! : nil) : self.bold),
112121
italic: (att.italic != nil ? (byAdding ? att.italic! : nil) : self.italic),
113122
underline: (att.underline != nil ? (byAdding ? att.underline! : nil) : self.underline),
123+
strike: (att.strike != nil ? (byAdding ? att.strike! : nil) : self.strike),
114124
header: (att.header != nil ? (byAdding ? att.header! : nil) : self.header),
115125
list: (att.list != nil ? (byAdding ? att.list! : nil) : self.list),
116126
indent: (att.indent != nil ? (byAdding ? att.indent! : nil) : self.indent)
@@ -130,6 +140,9 @@ extension RichAttributes {
130140
if let underline = underline, underline {
131141
styles.append(.underline)
132142
}
143+
if let strike = strike, strike {
144+
styles.append(.strikethrough)
145+
}
133146
if let header = header {
134147
styles.append(header.getTextSpanStyle())
135148
}
@@ -150,6 +163,9 @@ extension RichAttributes {
150163
if let underline = underline, underline {
151164
styles.insert(.underline)
152165
}
166+
if let strike = strike, strike {
167+
styles.insert(.strikethrough)
168+
}
153169
if let header = header {
154170
styles.insert(header.getTextSpanStyle())
155171
}
@@ -163,28 +179,30 @@ extension RichAttributes {
163179
extension RichAttributes {
164180
public func hasStyle(style: RichTextStyle) -> Bool {
165181
switch style {
166-
case .default:
167-
return true
168-
case .bold:
169-
return bold ?? false
170-
case .italic:
171-
return italic ?? false
172-
case .underline:
173-
return underline ?? false
174-
case .h1:
175-
return header == .h1
176-
case .h2:
177-
return header == .h2
178-
case .h3:
179-
return header == .h3
180-
case .h4:
181-
return header == .h4
182-
case .h5:
183-
return header == .h5
184-
case .h6:
185-
return header == .h6
186-
case .bullet:
187-
return list == .bullet(indent)
182+
case .default:
183+
return true
184+
case .bold:
185+
return bold ?? false
186+
case .italic:
187+
return italic ?? false
188+
case .underline:
189+
return underline ?? false
190+
case .strikethrough:
191+
return strike ?? false
192+
case .h1:
193+
return header == .h1
194+
case .h2:
195+
return header == .h2
196+
case .h3:
197+
return header == .h3
198+
case .h4:
199+
return header == .h4
200+
case .h5:
201+
return header == .h5
202+
case .h6:
203+
return header == .h6
204+
case .bullet:
205+
return list == .bullet(indent)
188206
}
189207
}
190208
}
@@ -198,40 +216,44 @@ internal func getRichAttributesFor(styles: [RichTextStyle]) -> RichAttributes {
198216
var bold: Bool? = nil
199217
var italic: Bool? = nil
200218
var underline: Bool? = nil
219+
var strike: Bool? = nil
201220
var header: HeaderType? = nil
202221
var list: ListType? = nil
203222
var indent: Int? = nil
204223

205224
for style in styles {
206225
switch style {
207-
case .bold:
208-
bold = true
209-
case .italic:
210-
italic = true
211-
case .underline:
212-
underline = true
213-
case .h1:
214-
header = .h1
215-
case .h2:
216-
header = .h2
217-
case .h3:
218-
header = .h3
219-
case .h4:
220-
header = .h4
221-
case .h5:
222-
header = .h5
223-
case .h6:
224-
header = .h6
225-
case .bullet(let indentIndex):
226-
list = .bullet(indentIndex)
227-
indent = indentIndex
228-
case .default:
229-
header = .default
226+
case .bold:
227+
bold = true
228+
case .italic:
229+
italic = true
230+
case .underline:
231+
underline = true
232+
case .strikethrough:
233+
strike = true
234+
case .h1:
235+
header = .h1
236+
case .h2:
237+
header = .h2
238+
case .h3:
239+
header = .h3
240+
case .h4:
241+
header = .h4
242+
case .h5:
243+
header = .h5
244+
case .h6:
245+
header = .h6
246+
case .bullet(let indentIndex):
247+
list = .bullet(indentIndex)
248+
indent = indentIndex
249+
case .default:
250+
header = .default
230251
}
231252
}
232253
return RichAttributes(bold: bold,
233254
italic: italic,
234255
underline: underline,
256+
strike: strike,
235257
header: header,
236258
list: list,
237259
indent: indent

Sources/RichEditorSwiftUI/UI/Editor/RichEditorState.swift

+9-8
Original file line numberDiff line numberDiff line change
@@ -569,10 +569,10 @@ extension RichEditorState {
569569
let toIndex = range.isCollapsed ? fromIndex : range.upperBound
570570

571571
let newLineStartIndex = text.utf16.prefix(fromIndex).map({ $0 }).lastIndex(of: "\n".utf16.last) ?? 0
572-
let newLineEndIndex = text.utf16.suffix(from: text.utf16.index(text.utf16.startIndex, offsetBy: toIndex - 1)).map({ $0 }).firstIndex(of: "\n".utf16.last)
572+
let newLineEndIndex = text.utf16.suffix(from: text.utf16.index(text.utf16.startIndex, offsetBy: max(0, toIndex - 1))).map({ $0 }).firstIndex(of: "\n".utf16.last)
573573

574574
let startIndex = max(0, newLineStartIndex)
575-
var endIndex = (toIndex - 1) + (newLineEndIndex ?? 0)
575+
var endIndex = (toIndex) + (newLineEndIndex ?? 0)
576576

577577
if newLineEndIndex == nil {
578578
endIndex = (text.utf16Length)
@@ -639,25 +639,26 @@ extension RichEditorState {
639639
internalSpans = mergeSameStyledSpans(internalSpans)
640640
internalSpans.sort(by: { $0.from < $1.from })
641641

642+
var spansToUpdate = Set(getOverlappingSpans(for: range))
642643

643-
var spansToUpdate = getOverlappingSpans(for: range)
644644
if addStyle || style.isDefault {
645645
if style.isDefault {
646646
/// This will help to apply header style without loosing other style
647647
let span = RichTextSpanInternal(from: fromIndex, to: toIndex, attributes: style == .default ? .init(header: .default) : getRichAttributesFor(style: style))
648-
spansToUpdate.append(span)
648+
spansToUpdate.insert(span)
649649
} else if !style.isHeaderStyle && !style.isList {
650650
///When selected range's is surrounded with same styled text it helps to update selected text in editor
651-
let span = RichTextSpanInternal(from: fromIndex, to: toIndex, attributes: getRichAttributesFor(style: style))
652-
spansToUpdate.append(span)
651+
let span = RichTextSpanInternal(from: fromIndex, to: max((toIndex - 1), 0), attributes: getRichAttributesFor(style: style))
652+
spansToUpdate.insert(span)
653+
spansToUpdate.insert(span)
653654
}
654-
applyStylesToSelectedText(spansToUpdate)
655+
applyStylesToSelectedText(spansToUpdate.map({ $0 }))
655656

656657
} else {
657658
let span = RichTextSpanInternal(from: fromIndex, to: (toIndex - 1), attributes: getRichAttributesFor(style: style))
658659
removeAttributes([span])
659660
///To apply style as remove span is removing other styles as well.
660-
applyStylesToSelectedText(spansToUpdate)
661+
applyStylesToSelectedText(spansToUpdate.map({ $0 }))
661662
}
662663
}
663664

0 commit comments

Comments
 (0)