Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Strikethrough #50

Merged
merged 3 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public extension RichTextAttributeReader {
let attributes = richTextAttributes(at: range)
let traits = richTextFont(at: range)?.fontDescriptor.symbolicTraits
var styles = traits?.enabledRichTextStyles ?? []
// if attributes.isStrikethrough { styles.append(.strikethrough) }
if attributes.isStrikethrough { styles.append(.strikethrough) }
if attributes.isUnderlined { styles.append(.underline) }
return styles
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public extension NSMutableAttributedString {

let attributeValue = newValue ? 1 : 0
if style == .underline { return setRichTextAttribute(.underlineStyle, to: attributeValue, at: range) }
if style == .strikethrough { return setRichTextAttribute(.strikethroughStyle, to: attributeValue, at: range) }
guard let font = richTextFont(at: range) else { return }
let styles = richTextStyles(at: range)
let shouldAdd = newValue && !styles.hasStyle(style)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import Foundation

public extension RichTextAttributes {

// /**
// Whether or not the attributes has a strikethrough style.
// */
// var isStrikethrough: Bool {
// get { self[.strikethroughStyle] as? Int == 1 }
// set { self[.strikethroughStyle] = newValue ? 1 : 0 }
// }
//
/**
Whether or not the attributes has a strikethrough style.
*/
var isStrikethrough: Bool {
get { self[.strikethroughStyle] as? Int == 1 }
set { self[.strikethroughStyle] = newValue ? 1 : 0 }
}

/**
Whether or not the attributes has an underline style.
*/
Expand Down
112 changes: 67 additions & 45 deletions Sources/RichEditorSwiftUI/Data/Models/RichAttributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public struct RichAttributes: Codable {
public let bold: Bool?
public let italic: Bool?
public let underline: Bool?
public let strike: Bool?
public let header: HeaderType?
public let list: ListType?
public let indent: Int?
Expand All @@ -22,6 +23,7 @@ public struct RichAttributes: Codable {
bold: Bool? = nil,
italic: Bool? = nil,
underline: Bool? = nil,
strike: Bool? = nil,
header: HeaderType? = nil,
list: ListType? = nil,
indent: Int? = nil
Expand All @@ -30,6 +32,7 @@ public struct RichAttributes: Codable {
self.bold = bold
self.italic = italic
self.underline = underline
self.strike = strike
self.header = header
self.list = list
self.indent = indent
Expand All @@ -39,6 +42,7 @@ public struct RichAttributes: Codable {
case bold = "bold"
case italic = "italic"
case underline = "underline"
case strike = "strike"
case header = "header"
case list = "list"
case indent = "indent"
Expand All @@ -50,6 +54,7 @@ public struct RichAttributes: Codable {
self.bold = try values.decodeIfPresent(Bool.self, forKey: .bold)
self.italic = try values.decodeIfPresent(Bool.self, forKey: .italic)
self.underline = try values.decodeIfPresent(Bool.self, forKey: .underline)
self.strike = try values.decodeIfPresent(Bool.self, forKey: .strike)
self.header = try values.decodeIfPresent(HeaderType.self, forKey: .header)
self.list = try values.decodeIfPresent(ListType.self, forKey: .list)
self.indent = try values.decodeIfPresent(Int.self, forKey: .indent)
Expand All @@ -62,6 +67,7 @@ extension RichAttributes: Hashable {
hasher.combine(bold)
hasher.combine(italic)
hasher.combine(underline)
hasher.combine(strike)
hasher.combine(header)
hasher.combine(list)
hasher.combine(indent)
Expand All @@ -76,6 +82,7 @@ extension RichAttributes: Equatable {
lhs.bold == rhs.bold
&& lhs.italic == rhs.italic
&& lhs.underline == rhs.underline
&& lhs.strike == rhs.strike
&& lhs.header == rhs.header
&& lhs.list == rhs.list
&& lhs.indent == rhs.indent
Expand All @@ -88,13 +95,15 @@ extension RichAttributes {
header: HeaderType? = nil,
italic: Bool? = nil,
underline: Bool? = nil,
strike: Bool? = nil,
list: ListType? = nil,
indent: Int? = nil
) -> RichAttributes {
return RichAttributes(
bold: (bold != nil ? bold! : self.bold),
italic: (italic != nil ? italic! : self.italic),
underline: (underline != nil ? underline! : self.underline),
strike: (strike != nil ? strike! : self.strike),
header: (header != nil ? header! : self.header),
list: (list != nil ? list! : self.list),
indent: (indent != nil ? indent! : self.indent)
Expand All @@ -111,6 +120,7 @@ extension RichAttributes {
bold: (att.bold != nil ? (byAdding ? att.bold! : nil) : self.bold),
italic: (att.italic != nil ? (byAdding ? att.italic! : nil) : self.italic),
underline: (att.underline != nil ? (byAdding ? att.underline! : nil) : self.underline),
strike: (att.strike != nil ? (byAdding ? att.strike! : nil) : self.strike),
header: (att.header != nil ? (byAdding ? att.header! : nil) : self.header),
list: (att.list != nil ? (byAdding ? att.list! : nil) : self.list),
indent: (att.indent != nil ? (byAdding ? att.indent! : nil) : self.indent)
Expand All @@ -130,6 +140,9 @@ extension RichAttributes {
if let underline = underline, underline {
styles.append(.underline)
}
if let strike = strike, strike {
styles.append(.strikethrough)
}
if let header = header {
styles.append(header.getTextSpanStyle())
}
Expand All @@ -150,6 +163,9 @@ extension RichAttributes {
if let underline = underline, underline {
styles.insert(.underline)
}
if let strike = strike, strike {
styles.insert(.strikethrough)
}
if let header = header {
styles.insert(header.getTextSpanStyle())
}
Expand All @@ -163,28 +179,30 @@ extension RichAttributes {
extension RichAttributes {
public func hasStyle(style: RichTextStyle) -> Bool {
switch style {
case .default:
return true
case .bold:
return bold ?? false
case .italic:
return italic ?? false
case .underline:
return underline ?? false
case .h1:
return header == .h1
case .h2:
return header == .h2
case .h3:
return header == .h3
case .h4:
return header == .h4
case .h5:
return header == .h5
case .h6:
return header == .h6
case .bullet:
return list == .bullet(indent)
case .default:
return true
case .bold:
return bold ?? false
case .italic:
return italic ?? false
case .underline:
return underline ?? false
case .strikethrough:
return strike ?? false
case .h1:
return header == .h1
case .h2:
return header == .h2
case .h3:
return header == .h3
case .h4:
return header == .h4
case .h5:
return header == .h5
case .h6:
return header == .h6
case .bullet:
return list == .bullet(indent)
}
}
}
Expand All @@ -198,40 +216,44 @@ internal func getRichAttributesFor(styles: [RichTextStyle]) -> RichAttributes {
var bold: Bool? = nil
var italic: Bool? = nil
var underline: Bool? = nil
var strike: Bool? = nil
var header: HeaderType? = nil
var list: ListType? = nil
var indent: Int? = nil

for style in styles {
switch style {
case .bold:
bold = true
case .italic:
italic = true
case .underline:
underline = true
case .h1:
header = .h1
case .h2:
header = .h2
case .h3:
header = .h3
case .h4:
header = .h4
case .h5:
header = .h5
case .h6:
header = .h6
case .bullet(let indentIndex):
list = .bullet(indentIndex)
indent = indentIndex
case .default:
header = .default
case .bold:
bold = true
case .italic:
italic = true
case .underline:
underline = true
case .strikethrough:
strike = true
case .h1:
header = .h1
case .h2:
header = .h2
case .h3:
header = .h3
case .h4:
header = .h4
case .h5:
header = .h5
case .h6:
header = .h6
case .bullet(let indentIndex):
list = .bullet(indentIndex)
indent = indentIndex
case .default:
header = .default
}
}
return RichAttributes(bold: bold,
italic: italic,
underline: underline,
strike: strike,
header: header,
list: list,
indent: indent
Expand Down
17 changes: 9 additions & 8 deletions Sources/RichEditorSwiftUI/UI/Editor/RichEditorState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -569,10 +569,10 @@ extension RichEditorState {
let toIndex = range.isCollapsed ? fromIndex : range.upperBound

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

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

if newLineEndIndex == nil {
endIndex = (text.utf16Length)
Expand Down Expand Up @@ -639,25 +639,26 @@ extension RichEditorState {
internalSpans = mergeSameStyledSpans(internalSpans)
internalSpans.sort(by: { $0.from < $1.from })

var spansToUpdate = Set(getOverlappingSpans(for: range))

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

} else {
let span = RichTextSpanInternal(from: fromIndex, to: (toIndex - 1), attributes: getRichAttributesFor(style: style))
removeAttributes([span])
///To apply style as remove span is removing other styles as well.
applyStylesToSelectedText(spansToUpdate)
applyStylesToSelectedText(spansToUpdate.map({ $0 }))
}
}

Expand Down
Loading
Loading