if (!_displayingPlaceholder || textView != _textKitComponents.textView) {
// If showing the placeholder, don't propagate backgroundColor/opaque to the editable textView. It is positioned over the placeholder to accept taps to begin editing, and if it's opaque/colored then it'll obscure the placeholder.
// If showing the placeholder, don't propagate backgroundColor/opaque to the editable textView. It is positioned over the placeholder to accept taps to begin editing, and if it's opaque/colored then it'll obscure the placeholder.
// The backgroundColor/opaque will be propagated to the editable textView when editing begins.
// If showing the placeholder, don't propagate backgroundColor/opaque to the editable textView. It is positioned over the placeholder to accept taps to begin editing, and if it's opaque/colored then it'll obscure the placeholder.
// The backgroundColor/opaque will be propagated to the editable textView when editing begins.
ASDisplayNodeAssert(!layerBacked, @"Cannot set layerBacked to YES on ASEditableTextNode – instances must be view-backed in order to ensure touch events can be passed to the internal UITextView during editing.");
// If we (_cmd) are called while the text view itself is updating (-textViewDidUpdate:), you cannot update the text storage and expect perfect propagation to the text view.
// Thus, we always update the textview directly if it's been created already.
if (ASObjectIsEqual((_textKitComponents.textView.attributedText ? : _textKitComponents.textStorage), attributedText))
return;
// If the cursor isn't at the end of the text, we need to preserve the selected range to avoid moving the cursor.
// Otherwise, note that we don't simply nil out attributed text. Because the insertion point is guided by the attributes at index 0, we need to attribute an empty string to ensure the insert point obeys our typing attributes.
// If hiding the placeholder, propagate backgroundColor/opaque to the editable textView. It is positioned over the placeholder to accept taps to begin editing, and was kept transparent so it doesn't obscure the placeholder text. Now that we're editing it and the placeholder is hidden, we can make it opaque to avoid unnecessary blending.
// Note that both of these won't be necessary once we can disable scrolling, pending rdar://14729288
// When we resize to fit (above) the prior layout becomes invalid. For whatever reason, UITextView doesn't invalidate its layout when its frame changes on its own, so we have to do so ourselves.
// This is used by _delegateDidChangeSelectionFromSelectedRange:toSelectedRange: to distinguish between selection changes that happen because of editing or pure selection changes.
_selectionChangedForEditedText = YES;
// Update if the placeholder is visible.
[self_updateDisplayingPlaceholder];
// Invalidate, as our calculated size depends on the textview's seeded text.
// If we're only changing selection to preserve it, don't notify about anything.
if (_isPreservingSelection)
return;
// Note if we receive a -textDidChange: between now and when we delegatify.
// This is used by _delegateDidChangeSelectionFromSelectedRange:toSelectedRange: to distinguish between selection changes that happen because of editing or pure selection changes.
// There are two reasons we're invoking the delegate on the next run of the runloop.
// 1. UITextView invokes its delegate methods when it's in the middle of text-processing. For example, -textViewDidChange: is invoked before you can truly rely on the changes being propagated throughout the Text Kit hierarchy.
// 2. This delegate method (-textViewDidChangeSelection:) is called both before -textViewDidChange: and before the layout manager/etc. has necessarily generated+laid out its glyphs. Because of the former, we need to wait until -textViewDidChange: has had an opportunity to be called so can accurately determine whether this selection change is due to editing (_selectionChangedForEditedText).
// Thus, to avoid calling out to client code in the middle of UITextView's processing, we call the delegate on the next run of the runloop, when all such internal processing is surely done.
dispatch_async(dispatch_get_main_queue(), ^{
if ([_delegate respondsToSelector:@selector(editableTextNodeDidChangeSelection:fromSelectedRange:toSelectedRange:dueToEditing:)])
// Note that because -editableTextNodeDidUpdateText: passes no state, the current state of the receiver will be accessed. Thus, it's not useful to enqueue a second delegation call if the first hasn't happened yet -- doing so will result in the delegate receiving -editableTextNodeDidUpdateText: when the "updated text" has already been processed. This may sound innocuous, but because our delegation may cause additional updates to the textview's string, and because such updates discard spelling suggestions and autocompletions (like double-space to `.`), it can actually be quite dangerous!
if (_delegateDidUpdateEnqueued)
return;
_delegateDidUpdateEnqueued = YES;
// UITextView invokes its delegate methods when it's in the middle of text-processing. For example, -textViewDidChange: is invoked before you can truly rely on the changes being propagated throughout the Text Kit hierarchy.
// Thus, to avoid calling out to client code in the middle of UITextView's processing, we call the delegate on the next run of the runloop, when all such internal processing is surely done.
dispatch_async(dispatch_get_main_queue(), ^{
_delegateDidUpdateEnqueued = NO;
if ([_delegate respondsToSelector:@selector(editableTextNodeDidUpdateText:)])
[_delegate editableTextNodeDidUpdateText:self];
});
}
- (void)_delegateDidFinishEditing
{
if ([_delegate respondsToSelector:@selector(editableTextNodeDidFinishEditing:)])