Skip to content

Commit

Permalink
Defer setting attributedText until Editor is loaded and ready (#180)
Browse files Browse the repository at this point in the history
  • Loading branch information
rajdeep authored Jun 19, 2023
1 parent ea67d6b commit 166e068
Show file tree
Hide file tree
Showing 11 changed files with 78 additions and 2 deletions.
33 changes: 32 additions & 1 deletion Proton/Sources/Swift/Editor/EditorView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ open class EditorView: UIView {
let context: RichTextViewContext
var needsAsyncTextResolution = false

// Holds `attributedText` until Editor move to a window
// Setting attributed text without Editor being fully ready
// causes issues with cached bounds that shows up when rotating the device.
private var pendingAttributedText: NSAttributedString?

var editorContextDelegate: EditorViewDelegate? {
get { editorViewContext.delegate }
}
Expand Down Expand Up @@ -403,13 +408,27 @@ open class EditorView: UIView {
}
}

/// Forces setting attributed text in `EditorView` even if it is not
/// yet in view hierarchy.
/// - Note: This may result in misplaced `Attachment`s and is recommended to be set to `true` only in unit tests.
public var forceApplyAttributedText = false

/// Text to be set in the `EditorView`
public var attributedText: NSAttributedString {
get { richTextView.attributedText }
get {
pendingAttributedText ?? richTextView.attributedText
}
set {
if forceApplyAttributedText == false && window == nil {
pendingAttributedText = newValue
return
}

delegate?.editor(self, willSetAttributedText: newValue)
isSettingAttributedText = true
richTextView.attributedText = newValue
isSettingAttributedText = false
delegate?.editor(self, didSetAttributedText: newValue)
}
}

Expand Down Expand Up @@ -641,6 +660,18 @@ open class EditorView: UIView {
.paragraphStyle: paragraphStyle
]
richTextView.adjustsFontForContentSizeCategory = true
delegate?.editor(_editor: self, isReady: false)
}

/// Subclasses can override it to perform additional actions whenever the window changes.
/// - IMPORTANT: Overriding implementations must call `super.didMoveToWindow()`
open override func didMoveToWindow() {
super.didMoveToWindow()
if let pendingAttributedText {
attributedText = pendingAttributedText
self.pendingAttributedText = nil
}
delegate?.editor(_editor: self, isReady: true)
}

/// Asks the view to calculate and return the size that best fits the specified size.
Expand Down
25 changes: 25 additions & 0 deletions Proton/Sources/Swift/Editor/EditorViewDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,29 @@ public protocol EditorViewDelegate: AnyObject {
/// - characterRange: Range of character at the tapped location, if available.
func editor(_ editor: EditorView, didTapAtLocation location: CGPoint, characterRange: NSRange?)

/// Invoked whenever layout pass completes as a result of changing or text or attributes
/// - Parameters:
/// - editor: Editor view receiving the event.
/// - content: Attributed text value.
func editor(_ editor: EditorView, didLayout content: NSAttributedString)

/// Invoked before attributedText is set on the `EditorView`
/// - Parameters:
/// - editor: Editor view receiving the event.
/// - content: Attributed text value to be set.
func editor(_ editor: EditorView, willSetAttributedText attributedText: NSAttributedString)

/// Invoked after attributedText is set on the `EditorView`
/// - Parameters:
/// - editor: Editor view receiving the event.
/// - content: Attributed text value set on the editor.
func editor(_ editor: EditorView, didSetAttributedText attributedText: NSAttributedString)

/// Invoked when Editor has been added to the view hierarchy and is ready to receive events.
/// - Parameters:
/// - editor: Editor view receiving the event.
/// - isReady: `true` if Editor is loaded. `false` when Editor is initialized but not yet in view hierarchy.
func editor(_editor: EditorView, isReady: Bool)
}

public extension EditorViewDelegate {
Expand All @@ -100,4 +122,7 @@ public extension EditorViewDelegate {
func editor(_ editor: EditorView, didChangeSize currentSize: CGSize, previousSize: CGSize) { }
func editor(_ editor: EditorView, didTapAtLocation location: CGPoint, characterRange: NSRange?) { }
func editor(_ editor: EditorView, didLayout content: NSAttributedString) { }
func editor(_ editor: EditorView, willSetAttributedText attributedText: NSAttributedString) { }
func editor(_ editor: EditorView, didSetAttributedText attributedText: NSAttributedString) { }
func editor(_editor: EditorView, isReady: Bool) { }
}
3 changes: 3 additions & 0 deletions Proton/Tests/Editor/EditorSnapshotTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class EditorSnapshotTests: SnapshotTestCase {
editor.font = UIFont.systemFont(ofSize: 12)

var panel = PanelView()
panel.editor.forceApplyAttributedText = true
panel.backgroundColor = .cyan
panel.layer.borderWidth = 1.0
panel.layer.cornerRadius = 4.0
Expand Down Expand Up @@ -378,6 +379,7 @@ class EditorSnapshotTests: SnapshotTestCase {
editor.attributedText = NSAttributedString(string: "One\nTwo\nThree")

var panel = PanelView()
panel.editor.forceApplyAttributedText = true
panel.backgroundColor = .cyan
panel.layer.borderWidth = 1.0
panel.layer.cornerRadius = 4.0
Expand Down Expand Up @@ -418,6 +420,7 @@ class EditorSnapshotTests: SnapshotTestCase {
let attachment = Attachment(panel, size: .fullWidth)
panel.boundsObserver = attachment
panel.editor.font = editor.font
panel.editor.forceApplyAttributedText = true

panel.attributedText = NSAttributedString(string: "In \nfull-width \nattachment")

Expand Down
2 changes: 2 additions & 0 deletions Proton/Tests/Editor/EditorViewContextTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class EditorViewContextTests: XCTestCase {

func testCarriesOverCustomTypingAttributes() {
let editor = EditorView()
editor.forceApplyAttributedText = true
let context = EditorViewContext.shared
context.richTextViewContext.textViewDidBeginEditing(editor.richTextView)

Expand All @@ -79,6 +80,7 @@ class EditorViewContextTests: XCTestCase {

func testLockedAttributes() {
let editor = EditorView()
editor.forceApplyAttributedText = true
let context = EditorViewContext.shared
context.richTextViewContext.textViewDidBeginEditing(editor.richTextView)

Expand Down
9 changes: 9 additions & 0 deletions Proton/Tests/Editor/EditorViewMenuTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,15 @@ class TestEditorView: EditorView {
var onToggleUnderline: (()->Void)?
var onToggleItalics: (()->Void)?

init() {
super.init()
forceApplyAttributedText = true
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func copy(_ sender: Any?) {
onCopy?()
}
Expand Down
4 changes: 4 additions & 0 deletions Proton/Tests/Editor/EditorViewTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ class EditorViewTests: XCTestCase {

let delegate = MockEditorViewDelegate()
let editor = EditorView()
editor.forceApplyAttributedText = true
let attachment = Attachment(PanelView(), size: .fullWidth)
let attrString = NSMutableAttributedString(string: "This is a test string")
attrString.append(attachment.string)
Expand Down Expand Up @@ -385,6 +386,7 @@ class EditorViewTests: XCTestCase {

func testReturnsNilForInvalidNextLine() throws {
let editor = EditorView()
editor.forceApplyAttributedText = true
let attrString = NSMutableAttributedString(string: "This is a test string")
editor.attributedText = attrString

Expand All @@ -395,6 +397,7 @@ class EditorViewTests: XCTestCase {

func testReturnsNilForInvalidPreviousLine() throws {
let editor = EditorView()
editor.forceApplyAttributedText = true
let attrString = NSMutableAttributedString(string: "This is a test string")
editor.attributedText = attrString

Expand All @@ -405,6 +408,7 @@ class EditorViewTests: XCTestCase {

func testResetsAttributesWhenCleared() {
let editor = EditorView()
editor.forceApplyAttributedText = true
editor.textColor = UIColor.red
let attrString = NSMutableAttributedString(string: "This is a test string", attributes: [.foregroundColor: UIColor.blue])
editor.attributedText = attrString
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions Proton/Tests/Helpers/EditorTestViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class EditorTestViewController: SnapshotTestViewController {
private func setup() {
editor.translatesAutoresizingMaskIntoConstraints = false
editor.addBorder()
editor.forceApplyAttributedText = true

view.addSubview(editor)
NSLayoutConstraint.activate([
Expand Down
3 changes: 2 additions & 1 deletion Proton/Tests/TextProcessors/TextProcessorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ class TextProcessorTests: XCTestCase {
processorExpectation.expectedFulfillmentCount = 3

let editor = EditorView()
editor.forceApplyAttributedText = true
editor.attributedText = NSAttributedString(string: "Test")
let testAttribute = NSAttributedString.Key("testAttr")

Expand Down Expand Up @@ -182,7 +183,7 @@ class TextProcessorTests: XCTestCase {
func testGetsNotifiedOfSelectedRangeChanges() {
let testExpectation = functionExpectation()
let editor = EditorView()

editor.forceApplyAttributedText = true
let name = "TextProcessorTest"
let mockProcessor = MockTextProcessor(name: name)
let originalRange = NSRange(location: 2, length: 1)
Expand Down

0 comments on commit 166e068

Please sign in to comment.