Skip to content

Commit e5102ea

Browse files
committed
Fixed a bug in TextStorage that would prevent textkit update notifications for character changes
1 parent a134e7d commit e5102ea

17 files changed

+12
-70
lines changed

Proton/Sources/ObjC/PRTextStorage.m

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ - (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str {
168168
NSInteger delta = str.length - range.length;
169169
[_storage replaceCharactersInRange:range withString:str];
170170
[_storage fixAttributesInRange:NSMakeRange(0, _storage.length)];
171-
[self edited:NSTextStorageEditedCharacters & NSTextStorageEditedAttributes range:range changeInLength:delta];
171+
[self edited:NSTextStorageEditedCharacters | NSTextStorageEditedAttributes range:range changeInLength:delta];
172172

173173
[self endEditing];
174174
}

Proton/Sources/Swift/Core/RichTextView.swift

+9
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,15 @@ class RichTextView: AutogrowingTextView {
205205
draw(CGRect(origin: .zero, size: contentSize))
206206
}
207207

208+
override func becomeFirstResponder() -> Bool {
209+
let didBecomeFirstResponder = super.becomeFirstResponder()
210+
if didBecomeFirstResponder {
211+
context?.selectedTextView = self
212+
context?.activeTextView = self
213+
}
214+
return didBecomeFirstResponder
215+
}
216+
208217
func updateSelectedRangeIgnoringCallback(_ selectedRange: NSRange) {
209218
ignoreSelectedRangeChangeCallback = true
210219
self.selectedRange = selectedRange

Proton/Sources/Swift/Editor/EditorView.swift

+2-33
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,6 @@ open class EditorView: UIView {
150150
}
151151
}
152152

153-
154-
// Holds `attributedText` until Editor move to a window
155-
// Setting attributed text without Editor being fully ready
156-
// causes issues with cached bounds that shows up when rotating the device.
157-
private var pendingAttributedText: NSAttributedString?
158-
159153
var editorContextDelegate: EditorViewDelegate? {
160154
get { editorViewContext.delegate }
161155
}
@@ -172,12 +166,6 @@ open class EditorView: UIView {
172166
/// Context for the current Editor
173167
public let editorViewContext: EditorViewContext
174168

175-
/// Returns if `attributedText` change is pending. `AttributedText` may not have been applied if the `EditorView` is not already on
176-
/// `window` and `forceApplyAttributedText` is not set to `true`.
177-
public var isAttributedTextPending: Bool {
178-
pendingAttributedText != nil
179-
}
180-
181169
/// Enables asynchronous rendering of attachments.
182170
/// - Note:
183171
/// Since attachments must me rendered on main thread, the rendering only continues when there is no user interaction. By default, rendering starts
@@ -419,7 +407,6 @@ open class EditorView: UIView {
419407
/// An attachment is only counted as a single character. Content length does not include
420408
/// length of content within the Attachment that is hosting another `EditorView`.
421409
public var contentLength: Int {
422-
guard pendingAttributedText == nil else { return attributedText.length }
423410
return richTextView.contentLength
424411
}
425412

@@ -539,35 +526,20 @@ open class EditorView: UIView {
539526
}
540527
}
541528

542-
/// Forces setting attributed text in `EditorView` even if it is not
543-
/// yet in view hierarchy.
544-
/// - Note: This may result in misplaced `Attachment`s and is recommended to be set to `true` only in unit tests.
545-
public var forceApplyAttributedText = false
546-
547529
/// Text to be set in the `EditorView`
548-
/// - Important: `attributedText` is not set for rendering in `EditorView` if the `EditorView` is not already in a `Window`. Value of `true`
549-
/// for `isAttributedTextPending` confirms that the text has not yet been rendered even though it is set in the `EditorView`.
550-
/// Notification of text being set can be observed by subscribing to `didSetAttributedText` in `EditorViewDelegate`.
551-
/// Alternatively, `forceApplyAttributedText` may be set to `true` to always apply `attributedText` irrespective of `EditorView` being
552-
/// in a `Window` or not.
553530
public var attributedText: NSAttributedString {
554531
get {
555-
pendingAttributedText ?? richTextView.attributedText
532+
richTextView.attributedText
556533
}
557534
set {
558-
if forceApplyAttributedText == false && window == nil {
559-
pendingAttributedText = newValue
560-
return
561-
}
562535
isSettingAttributedText = true
563536
attachmentRenderingScheduler.cancel()
564537
renderedViewport = nil
565538
// Clear text before setting new value to avoid issues with formatting/layout when
566539
// editor is hosted in a scrollable container and content is set multiple times.
567540
richTextView.attributedText = NSAttributedString()
568541

569-
let isDeferred = pendingAttributedText != nil
570-
pendingAttributedText = nil
542+
let isDeferred = false
571543

572544
AggregateEditorViewDelegate.editor(self, willSetAttributedText: newValue, isDeferred: isDeferred)
573545

@@ -872,9 +844,6 @@ open class EditorView: UIView {
872844
/// - IMPORTANT: Overriding implementations must call `super.didMoveToWindow()`
873845
open override func didMoveToWindow() {
874846
super.didMoveToWindow()
875-
if let pendingAttributedText {
876-
attributedText = pendingAttributedText
877-
}
878847
let isReady = window != nil
879848
AggregateEditorViewDelegate.editor(self, isReady: isReady)
880849
}

Proton/Sources/Swift/TextProcessors/TextProcessor.swift

-15
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,6 @@ class TextProcessor: NSObject, NSTextStorageDelegate {
6464
var processed = false
6565
let changedText = textStorage.substring(from: editedRange)
6666

67-
let editedMask = getEditedMask(delta: delta)
68-
6967
let executableProcessors = filteringExecutableOn(editor: editor)
7068

7169
executableProcessors.forEach {
@@ -97,7 +95,6 @@ class TextProcessor: NSObject, NSTextStorageDelegate {
9795

9896
func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorage.EditActions, range editedRange: NSRange, changeInLength delta: Int) {
9997
guard let editor = editor else { return }
100-
let editedMask = getEditedMask(delta: delta)
10198
let executableProcessors = filteringExecutableOn(editor: editor)
10299

103100
executableProcessors.forEach {
@@ -113,18 +110,6 @@ class TextProcessor: NSObject, NSTextStorageDelegate {
113110
}
114111
}
115112

116-
// The editedMask is computed here as fixing the actual bug in PRTextStorage.replaceCharacter ([self edited:])
117-
// causing incorrect editedMask coming-in in this delegate causes TableViewAttachmentSnapshotTests.testRendersTableViewAttachmentInViewportRotation
118-
// to hang, possibly due to persistent layout invalidations. This can be fixed if cell has foreApplyAttributedText on
119-
// which ensures TextStorage to always be consistent state. However, given that there is some unknown, the proper fix
120-
// in PRTextStorage will be added at a later time. It may include dropping need for forceApplyAttributedText.
121-
private func getEditedMask(delta: Int) -> NSTextStorage.EditActions {
122-
guard delta != 0 else {
123-
return .editedAttributes
124-
}
125-
return [.editedCharacters, .editedAttributes]
126-
}
127-
128113
private func notifyInterruption(by processor: TextProcessing, editor: EditorView, at range: NSRange) {
129114
let processors = activeProcessors.filter { $0.name != processor.name }
130115
processors.forEach { $0.processInterrupted(editor: editor, at: range) }

Proton/Tests/Editor/EditorSnapshotTests.swift

-6
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ class EditorSnapshotTests: SnapshotTestCase {
9999
editor.font = UIFont.systemFont(ofSize: 12)
100100

101101
var panel = PanelView()
102-
panel.editor.forceApplyAttributedText = true
103102
panel.backgroundColor = .cyan
104103
panel.layer.borderWidth = 1.0
105104
panel.layer.cornerRadius = 4.0
@@ -266,7 +265,6 @@ class EditorSnapshotTests: SnapshotTestCase {
266265

267266
for i in 1...10 {
268267
var panel = PanelView()
269-
panel.editor.forceApplyAttributedText = true
270268
panel.backgroundColor = .cyan
271269
panel.layer.borderWidth = 1.0
272270
panel.layer.cornerRadius = 4.0
@@ -425,7 +423,6 @@ class EditorSnapshotTests: SnapshotTestCase {
425423
editor.attributedText = NSAttributedString(string: "One\nTwo\nThree")
426424

427425
var panel = PanelView()
428-
panel.editor.forceApplyAttributedText = true
429426
panel.backgroundColor = .cyan
430427
panel.layer.borderWidth = 1.0
431428
panel.layer.cornerRadius = 4.0
@@ -466,7 +463,6 @@ class EditorSnapshotTests: SnapshotTestCase {
466463
let attachment = Attachment(panel, size: .fullWidth)
467464
panel.boundsObserver = attachment
468465
panel.editor.font = editor.font
469-
panel.editor.forceApplyAttributedText = true
470466

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

@@ -1421,7 +1417,6 @@ class EditorSnapshotTests: SnapshotTestCase {
14211417
editor.font = UIFont.systemFont(ofSize: 12)
14221418

14231419
var panel = PanelView()
1424-
panel.editor.forceApplyAttributedText = true
14251420
panel.backgroundColor = .cyan
14261421
panel.layer.borderWidth = 1.0
14271422
panel.layer.cornerRadius = 4.0
@@ -1455,7 +1450,6 @@ class EditorSnapshotTests: SnapshotTestCase {
14551450
editor.font = UIFont.systemFont(ofSize: 12)
14561451

14571452
var panel = PanelView()
1458-
panel.editor.forceApplyAttributedText = true
14591453
panel.backgroundColor = .cyan
14601454
panel.layer.borderWidth = 1.0
14611455
panel.layer.cornerRadius = 4.0

Proton/Tests/Editor/EditorViewContextTests.swift

-2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ class EditorViewContextTests: XCTestCase {
6363

6464
func testCarriesOverCustomTypingAttributes() {
6565
let editor = EditorView()
66-
editor.forceApplyAttributedText = true
6766
let context = EditorViewContext.shared
6867
context.richTextViewContext.textViewDidBeginEditing(editor.richTextView)
6968

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

8180
func testLockedAttributes() {
8281
let editor = EditorView()
83-
editor.forceApplyAttributedText = true
8482
let context = EditorViewContext.shared
8583
context.richTextViewContext.textViewDidBeginEditing(editor.richTextView)
8684

Proton/Tests/Editor/EditorViewMenuTests.swift

-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@ class TestEditorView: EditorView {
128128

129129
init() {
130130
super.init()
131-
forceApplyAttributedText = true
132131
}
133132

134133
required init?(coder aDecoder: NSCoder) {

Proton/Tests/Editor/EditorViewTests.swift

-5
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,6 @@ class EditorViewTests: XCTestCase {
224224

225225
let delegate = MockEditorViewDelegate()
226226
let editor = EditorView()
227-
editor.forceApplyAttributedText = true
228227
let attachment = Attachment(PanelView(), size: .fullWidth)
229228
let attrString = NSMutableAttributedString(string: "This is a test string")
230229
attrString.append(attachment.string)
@@ -386,7 +385,6 @@ class EditorViewTests: XCTestCase {
386385

387386
func testReturnsNilForInvalidNextLine() throws {
388387
let editor = EditorView()
389-
editor.forceApplyAttributedText = true
390388
let attrString = NSMutableAttributedString(string: "This is a test string")
391389
editor.attributedText = attrString
392390

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

398396
func testReturnsNilForInvalidPreviousLine() throws {
399397
let editor = EditorView()
400-
editor.forceApplyAttributedText = true
401398
let attrString = NSMutableAttributedString(string: "This is a test string")
402399
editor.attributedText = attrString
403400

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

409406
func testResetsAttributesWhenCleared() {
410407
let editor = EditorView()
411-
editor.forceApplyAttributedText = true
412408
editor.textColor = UIColor.red
413409
let attrString = NSMutableAttributedString(string: "This is a test string", attributes: [.foregroundColor: UIColor.blue])
414410
editor.attributedText = attrString
@@ -924,7 +920,6 @@ class EditorViewTests: XCTestCase {
924920

925921
func makePanelAttachment() -> Attachment {
926922
let panel = PanelView()
927-
panel.editor.forceApplyAttributedText = true
928923
panel.backgroundColor = .cyan
929924
panel.layer.borderWidth = 1.0
930925
panel.layer.cornerRadius = 4.0

Proton/Tests/Editor/EditorViewportSnapshotTests.swift

-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,6 @@ class EditorViewportSnapshotTests: SnapshotTestCase {
168168

169169
for i in 0..<count {
170170
var panel = PanelView()
171-
panel.editor.forceApplyAttributedText = true
172171
panel.backgroundColor = .cyan
173172
panel.layer.borderWidth = 1.0
174173
panel.layer.cornerRadius = 4.0

Proton/Tests/Grid/GridViewAttachmentSnapshotTests.swift

-1
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,6 @@ class GridViewAttachmentSnapshotTests: SnapshotTestCase {
158158
])
159159
let gridAttachment = GridViewAttachment(config: config)
160160
var panel = PanelView()
161-
panel.editor.forceApplyAttributedText = true
162161
panel.backgroundColor = .cyan
163162
panel.layer.borderWidth = 1.0
164163
panel.layer.cornerRadius = 4.0

Proton/Tests/Helpers/EditorTestViewController.swift

-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ class EditorTestViewController: SnapshotTestViewController {
4141
private func setup() {
4242
editor.translatesAutoresizingMaskIntoConstraints = false
4343
editor.addBorder()
44-
editor.forceApplyAttributedText = true
4544

4645
view.addSubview(editor)
4746
NSLayoutConstraint.activate([

Proton/Tests/Table/TableViewAttachmentSnapshotTests.swift

-1
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,6 @@ class TableViewAttachmentSnapshotTests: SnapshotTestCase {
191191
])
192192
let tableAttachment = makeTableViewAttachment(config: config)
193193
var panel = PanelView()
194-
panel.editor.forceApplyAttributedText = true
195194
panel.backgroundColor = .cyan
196195
panel.layer.borderWidth = 1.0
197196
panel.layer.cornerRadius = 4.0

Proton/Tests/TextProcessors/TextProcessorTests.swift

-3
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,6 @@ class TextProcessorTests: XCTestCase {
220220
processorExpectation.expectedFulfillmentCount = 3
221221

222222
let editor = EditorView()
223-
editor.forceApplyAttributedText = true
224223
editor.attributedText = NSAttributedString(string: "Test")
225224
let testAttribute = NSAttributedString.Key("testAttr")
226225

@@ -252,7 +251,6 @@ class TextProcessorTests: XCTestCase {
252251
func testGetsNotifiedOfSelectedRangeChanges() {
253252
let testExpectation = functionExpectation()
254253
let editor = EditorView()
255-
editor.forceApplyAttributedText = true
256254
let name = "TextProcessorTest"
257255
let mockProcessor = MockTextProcessor(name: name)
258256
let originalRange = NSRange(location: 2, length: 1)
@@ -529,7 +527,6 @@ class TextProcessorTests: XCTestCase {
529527

530528
private func assertProcessorInvocationOnSetAttributedText(_ expectation: XCTestExpectation, isRunOnSettingText: Bool, file: StaticString = #file, line: UInt = #line, assertion: (MockTextProcessor) -> Void) throws {
531529
let editor = EditorView()
532-
editor.forceApplyAttributedText = true
533530

534531
let name = "TextProcessorTest"
535532
let mockProcessor = MockTextProcessor(name: name)

0 commit comments

Comments
 (0)