From b40e0305f82bebecc46b94f192311cac8ce2fb69 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 3 Dec 2019 14:19:50 -0500 Subject: [PATCH] feat: Add attributes to strings (#126) * adding attributes dictionary * update * added highlighted text element * use attributes * tests * pr fix * Update RichTextViewUITests.swift --- Example/Source/InputOutputModuleView.swift | 9 ++++- Example/Source/ViewController.swift | 3 +- Podfile.lock | 2 +- .../NSMutableAttributedStringExtension.swift | 1 + Source/RichTextView.swift | 8 +++- Source/Text Parsing/RichTextParser.swift | 35 ++++++++++++++++- ...t__Renders_a_string_with_highlights@2x.png | Bin 0 -> 22504 bytes ...e__Updates_highlight_Color_Properly@2x.png | Bin 0 -> 24143 bytes UITests/RichTextViewUITests.swift | 37 ++++++++++++++++++ .../Text Parsing/RichTextParserSpec.swift | 23 ++++++++++- 10 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 UITests/ReferenceImages/RichTextViewUITests/RichTextView_UI__Rendering_Various_Strings_of_Rich_Text__Renders_a_string_with_highlights@2x.png create mode 100644 UITests/ReferenceImages/RichTextViewUITests/RichTextView_UI__Update__Updates_highlight_Color_Properly@2x.png diff --git a/Example/Source/InputOutputModuleView.swift b/Example/Source/InputOutputModuleView.swift index ce58e55..b377dee 100644 --- a/Example/Source/InputOutputModuleView.swift +++ b/Example/Source/InputOutputModuleView.swift @@ -11,6 +11,10 @@ import UIKit class InputOutputModuleView: UIView, RichTextViewDelegate { + // MARK: - Constants + + let attributes = ["456": [NSAttributedString.Key.backgroundColor: UIColor.lightGray]] + // MARK: - IBOutlets @IBOutlet var contentView: UIView! @@ -42,7 +46,10 @@ class InputOutputModuleView: UIView, RichTextViewDelegate { private func updateText(_ text: String) { self.inputLabel.text = text self.outputRichTextView.textViewDelegate = self - self.outputRichTextView.update(input: text) + self.outputRichTextView.update( + input: text, + attributes: self.attributes + ) } // MARK: - RichTextViewDelegate diff --git a/Example/Source/ViewController.swift b/Example/Source/ViewController.swift index 6435bde..59bbe3c 100644 --- a/Example/Source/ViewController.swift +++ b/Example/Source/ViewController.swift @@ -51,7 +51,8 @@ class ViewController: UIViewController { InputOutputModuleView(text: "jump to page"), InputOutputModuleView(text: Text.tableHTML), InputOutputModuleView(text: "
Here is a blockquote
"), - InputOutputModuleView(text: "Look [interactive-element id=123]This is an interactive element[/interactive-element] Wow") + InputOutputModuleView(text: "Look [interactive-element id=123]This is an interactive element[/interactive-element] Wow"), + InputOutputModuleView(text: "Look [highlighted-element id=456]This is an highlighted element[/highlighted-element] Wow") ] } diff --git a/Podfile.lock b/Podfile.lock index 2c7f6fd..4981f04 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -79,4 +79,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: f6fe77da859ca9643b5846ec01310846dcbfa657 -COCOAPODS: 1.8.3 +COCOAPODS: 1.8.4 diff --git a/Source/Extensions/NSMutableAttributedStringExtension.swift b/Source/Extensions/NSMutableAttributedStringExtension.swift index fc78cd1..6cc8986 100644 --- a/Source/Extensions/NSMutableAttributedStringExtension.swift +++ b/Source/Extensions/NSMutableAttributedStringExtension.swift @@ -8,6 +8,7 @@ extension NSAttributedString.Key { static let customLink = NSAttributedString.Key(rawValue: "customLink") + static let highlight = NSAttributedString.Key(rawValue: "highlight") } extension NSMutableAttributedString { diff --git a/Source/RichTextView.swift b/Source/RichTextView.swift index 770ffaf..f26175d 100644 --- a/Source/RichTextView.swift +++ b/Source/RichTextView.swift @@ -39,6 +39,7 @@ public class RichTextView: UIView { latexTextBaselineOffset: CGFloat = 0, interactiveTextColor: UIColor = UIColor.blue, textViewDelegate: RichTextViewDelegate? = nil, + customAdditionalAttributes: [String: [NSAttributedString.Key: Any]]? = nil, frame: CGRect, completion: (([ParsingError]?) -> Void)? = nil) { self.input = input @@ -49,7 +50,8 @@ public class RichTextView: UIView { font: font, textColor: textColor, latexTextBaselineOffset: latexTextBaselineOffset, - interactiveTextColor: interactiveTextColor + interactiveTextColor: interactiveTextColor, + attributes: customAdditionalAttributes ) self.textColor = textColor self.textViewDelegate = textViewDelegate @@ -76,6 +78,7 @@ public class RichTextView: UIView { textColor: UIColor? = nil, latexTextBaselineOffset: CGFloat? = nil, interactiveTextColor: UIColor? = nil, + attributes: [String: [NSAttributedString.Key: Any]]? = nil, completion: (([ParsingError]?) -> Void)? = nil) { self.input = input ?? self.input self.richTextParser = RichTextParser( @@ -83,7 +86,8 @@ public class RichTextView: UIView { font: font ?? self.richTextParser.font, textColor: textColor ?? self.textColor, latexTextBaselineOffset: latexTextBaselineOffset ?? self.richTextParser.latexTextBaselineOffset, - interactiveTextColor: interactiveTextColor ?? self.richTextParser.interactiveTextColor + interactiveTextColor: interactiveTextColor ?? self.richTextParser.interactiveTextColor, + attributes: attributes ) self.textColor = textColor ?? self.textColor self.subviews.forEach { $0.removeFromSuperview() } diff --git a/Source/Text Parsing/RichTextParser.swift b/Source/Text Parsing/RichTextParser.swift index 8aa5e5a..657a206 100644 --- a/Source/Text Parsing/RichTextParser.swift +++ b/Source/Text Parsing/RichTextParser.swift @@ -14,11 +14,15 @@ class RichTextParser { enum ParserConstants { static let mathTagName = "math" static let interactiveElementTagName = "interactive-element" + static let highlightedElementTagName = "highlighted-element" static let latexRegex = "\\[\(ParserConstants.mathTagName)\\](.*?)\\[\\/\(ParserConstants.mathTagName)\\]" static let latexRegexCaptureGroupIndex = 0 static let interactiveElementRegex = """ \\[\(ParserConstants.interactiveElementTagName)\\sid=.+?\\].*?\\[\\/\(ParserConstants.interactiveElementTagName)\\] """ + static let highlightedElementRegex = """ + \\[\(ParserConstants.highlightedElementTagName)\\sid=.+?\\].*?\\[\\/\(ParserConstants.highlightedElementTagName)\\] + """ typealias RichTextWithErrors = (output: NSAttributedString, errors: [ParsingError]?) } @@ -29,6 +33,7 @@ class RichTextParser { let textColor: UIColor let latexTextBaselineOffset: CGFloat let interactiveTextColor: UIColor + let attributes: [String: [NSAttributedString.Key: Any]]? // MARK: - Init @@ -36,12 +41,14 @@ class RichTextParser { font: UIFont = UIFont.systemFont(ofSize: UIFont.systemFontSize), textColor: UIColor = UIColor.black, latexTextBaselineOffset: CGFloat = 0, - interactiveTextColor: UIColor = UIColor.blue) { + interactiveTextColor: UIColor = UIColor.blue, + attributes: [String: [NSAttributedString.Key: Any]]? = nil) { self.latexParser = latexParser self.font = font self.textColor = textColor self.latexTextBaselineOffset = latexTextBaselineOffset self.interactiveTextColor = interactiveTextColor + self.attributes = attributes } // MARK: - Utility Functions @@ -125,8 +132,11 @@ class RichTextParser { let interactiveElementPositions = self.extractPositions( fromRanges: mutableAttributedString.string.ranges(of: ParserConstants.interactiveElementRegex, options: .regularExpression) ) + let highlightedElementPositions = self.extractPositions( + fromRanges: mutableAttributedString.string.ranges(of: ParserConstants.highlightedElementRegex, options: .regularExpression) + ) let latexPositions = self.extractPositions(fromRanges: self.getLatexRanges(inText: mutableAttributedString.string)) - let splitPositions = interactiveElementPositions + latexPositions + let splitPositions = interactiveElementPositions + latexPositions + highlightedElementPositions if splitPositions.isEmpty { return (mutableAttributedString.trimmingTrailingNewlinesAndWhitespaces(), nil) } @@ -150,6 +160,8 @@ class RichTextParser { for attributedString in attributedStringComponents { if self.isTextInteractiveElement(attributedString.string) { output.append(self.extractInteractiveElement(from: attributedString)) + } else if self.isTextHighlightedElement(attributedString.string) { + output.append(self.extractHighlightedElement(from: attributedString)) } else if self.isTextLatex(attributedString.string) { if let attributedLatexString = self.extractLatex(from: attributedString.string) { output.append(attributedLatexString) @@ -189,6 +201,21 @@ class RichTextParser { return mutableAttributedInput } + func extractHighlightedElement(from input: NSAttributedString) -> NSMutableAttributedString { + let highlightedElementTagName = ParserConstants.highlightedElementTagName + let highlightedElementID = input.string.getSubstring(inBetween: "[\(highlightedElementTagName) id=", and: "]") ?? input.string + let highlightedElementText = input.string.getSubstring(inBetween: "]", and: "[/\(highlightedElementTagName)]") ?? input.string + guard let richTextAttributes = self.attributes else { + return NSMutableAttributedString(string: highlightedElementText) + } + let attributes: [NSAttributedString.Key: Any] = [ + .highlight: highlightedElementID, + .backgroundColor: richTextAttributes[highlightedElementID]?[.backgroundColor] + ].merging(input.attributes(at: 0, effectiveRange: nil)) { (current, _) in current } + let mutableAttributedInput = NSMutableAttributedString(string: " " + highlightedElementText + " ", attributes: attributes) + return mutableAttributedInput + } + func isTextLatex(_ text: String) -> Bool { return !self.getLatexRanges(inText: text).isEmpty } @@ -197,6 +224,10 @@ class RichTextParser { return text.ranges(of: ParserConstants.interactiveElementRegex, options: .regularExpression).count != 0 } + func isTextHighlightedElement(_ text: String) -> Bool { + return text.ranges(of: ParserConstants.highlightedElementRegex, options: .regularExpression).count != 0 + } + private func extractPositions(fromRanges ranges: [Range]) -> [String.Index] { return ranges.flatMap { range in return [range.lowerBound, range.upperBound] diff --git a/UITests/ReferenceImages/RichTextViewUITests/RichTextView_UI__Rendering_Various_Strings_of_Rich_Text__Renders_a_string_with_highlights@2x.png b/UITests/ReferenceImages/RichTextViewUITests/RichTextView_UI__Rendering_Various_Strings_of_Rich_Text__Renders_a_string_with_highlights@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..86dda79b05b0e9c1ca45f112771b6863c93cb526 GIT binary patch literal 22504 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 literal 0 HcmV?d00001 diff --git a/UITests/ReferenceImages/RichTextViewUITests/RichTextView_UI__Update__Updates_highlight_Color_Properly@2x.png b/UITests/ReferenceImages/RichTextViewUITests/RichTextView_UI__Update__Updates_highlight_Color_Properly@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..99f339f86979658143d4ee556722ad11d41637c1 GIT binary patch literal 24143 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 literal 0 HcmV?d00001 diff --git a/UITests/RichTextViewUITests.swift b/UITests/RichTextViewUITests.swift index 441c73a..f37dd70 100644 --- a/UITests/RichTextViewUITests.swift +++ b/UITests/RichTextViewUITests.swift @@ -104,6 +104,24 @@ class RichTextViewUITests: QuickSpec { }) } } + it("Renders a string with highlights") { + let richTextView = RichTextView( + input: "[highlighted-element id=123]Test[/highlighted-element]", + customAdditionalAttributes: ["123": [NSAttributedString.Key.backgroundColor: UIColor.lightGray]], + frame: CGRect(origin: .zero, size: Defaults.size + )) + richTextView.backgroundColor = UIColor.white + self.richTextView = richTextView + self.viewController = UIViewController() + self.viewController?.view.addSubview(richTextView) + self.window?.rootViewController = self.viewController + waitUntil(timeout: Defaults.timeOut) { done in + DispatchQueue.main.asyncAfter(deadline: .now() + Defaults.delay, execute: { + expect(self.window).to(haveValidSnapshot()) + done() + }) + } + } } context("Update") { it("Updates Input Properly") { @@ -151,6 +169,25 @@ class RichTextViewUITests: QuickSpec { }) } } + it("Updates highlight Color Properly") { + let richTextView = RichTextView(frame: CGRect(origin: .zero, size: Defaults.size)) + richTextView.backgroundColor = UIColor.white + richTextView.update( + input: "[highlighted-element id=123]* Heading[/highlighted-element]", + attributes: ["123": [NSMutableAttributedString.Key.backgroundColor: UIColor.lightGray]] + ) + self.richTextView = richTextView + self.viewController = UIViewController() + self.viewController?.view.addSubview(richTextView) + self.window?.rootViewController = self.viewController + waitUntil(timeout: Defaults.timeOut) { done in + DispatchQueue.main.asyncAfter(deadline: .now() + Defaults.delay, execute: { + expect(self.window).to(haveValidSnapshot()) + done() + }) + } + } + } afterEach { UIView.setAnimationsEnabled(false) diff --git a/UnitTests/Text Parsing/RichTextParserSpec.swift b/UnitTests/Text Parsing/RichTextParserSpec.swift index c209957..51f87af 100644 --- a/UnitTests/Text Parsing/RichTextParserSpec.swift +++ b/UnitTests/Text Parsing/RichTextParserSpec.swift @@ -21,6 +21,8 @@ class RichTextParserSpec: QuickSpec { static let codeText = "[code]print('Hello World')[/code]" static let basicInteractiveElement = "[interactive-element id=123]This is an interactive element[/interactive-element]" static let complexInteractiveElement = "Look! An interactive element: [interactive-element id=123]element[/interactive-element]" + static let highlightedElement = "[highlighted-element id=123]This is an highlighted element[/highlighted-element]" + static let complexHighlightedElement = "Look! An highlighted element: [highlighted-element id=123]element[/highlighted-element]" } var richTextParser: RichTextParser! @@ -28,7 +30,7 @@ class RichTextParserSpec: QuickSpec { override func spec() { describe("RichTextParser") { beforeEach { - self.richTextParser = RichTextParser() + self.richTextParser = RichTextParser(attributes: ["123": [NSAttributedString.Key.backgroundColor: UIColor.lightGray]]) } context("Latex Parsing") { it("succesfully returns an NSAttributedString with an image") { @@ -58,6 +60,25 @@ class RichTextParserSpec: QuickSpec { expect(output).to(equal(expectedAttributedString)) } } + context("highlighted Element") { + it("succesfully returns an NSAttributedString with the highlighted property from a basic highlighted element") { let output = self.richTextParser.extractHighlightedElement(from: NSAttributedString(string: MarkDownText.highlightedElement)) + let attributes: [NSAttributedString.Key: Any] = [ + NSAttributedString.Key(rawValue: "highlight"): "123", + .backgroundColor: UIColor.lightGray + ] + let expectedAttributedString = NSAttributedString(string: " This is an highlighted element ", attributes: attributes) + expect(output).to(equal(expectedAttributedString)) + } + it("succesfully returns an NSAttributedString with the highlighted property from a complex highlighted element") { + let output = self.richTextParser.extractHighlightedElement(from: NSAttributedString(string: MarkDownText.complexHighlightedElement)) + let attributes: [NSAttributedString.Key: Any] = [ + NSAttributedString.Key(rawValue: "highlight"): "123", + .backgroundColor: UIColor.lightGray + ] + let expectedAttributedString = NSAttributedString(string: " element ", attributes: attributes) + expect(output).to(equal(expectedAttributedString)) + } + } context("Rich text to attributed string") { it("generates a single attributed string with multiple rich text types") { let regularText = self.richTextParser.getAttributedText(from: MarkDownText.regularText).output