22#import < react/renderer/components/RNLiveMarkdownSpec/Props.h>
33#import < React/RCTFabricComponentsPlugins.h>
44#import < React/RCTUITextField.h>
5+ #import < React/RCTUITextView.h>
6+ #import < React/RCTTextInputComponentView.h>
57
68#import < RNLiveMarkdown/MarkdownBackedTextInputDelegate.h>
79#import < RNLiveMarkdown/MarkdownLayoutManager.h>
10+ #import < RNLiveMarkdown/MarkdownTextFieldObserver.h>
11+ #import < RNLiveMarkdown/MarkdownTextViewObserver.h>
812#import < RNLiveMarkdown/MarkdownTextInputDecoratorComponentView.h>
913#import < RNLiveMarkdown/MarkdownTextInputDecoratorViewComponentDescriptor.h>
10- #import < RNLiveMarkdown/RCTBackedTextFieldDelegateAdapter+Markdown .h>
14+ #import < RNLiveMarkdown/MarkdownTextStorageDelegate .h>
1115#import < RNLiveMarkdown/RCTMarkdownStyle.h>
12- #import < RNLiveMarkdown/RCTTextInputComponentView+Markdown.h>
13- #import < RNLiveMarkdown/RCTUITextView+Markdown.h>
1416
1517#import < objc/runtime.h>
1618
@@ -21,10 +23,11 @@ @implementation MarkdownTextInputDecoratorComponentView {
2123 RCTMarkdownStyle *_markdownStyle;
2224 NSNumber *_parserId;
2325 MarkdownBackedTextInputDelegate *_markdownBackedTextInputDelegate;
24- __weak RCTTextInputComponentView *_textInput ;
25- __weak UIView<RCTBackedTextInputViewProtocol> *_backedTextInputView ;
26- __weak RCTBackedTextFieldDelegateAdapter *_adapter ;
26+ MarkdownTextStorageDelegate *_markdownTextStorageDelegate ;
27+ MarkdownTextViewObserver *_markdownTextViewObserver ;
28+ MarkdownTextFieldObserver *_markdownTextFieldObserver ;
2729 __weak RCTUITextView *_textView;
30+ __weak RCTUITextField *_textField;
2831}
2932
3033+ (ComponentDescriptorProvider)componentDescriptorProvider
@@ -51,21 +54,47 @@ - (instancetype)initWithFrame:(CGRect)frame
5154- (void )didAddSubview : (UIView *)subview
5255{
5356 react_native_assert ([subview isKindOfClass: [RCTTextInputComponentView class ]] && " Child component of MarkdownTextInputDecoratorComponentView is not an instance of RCTTextInputComponentView." );
54- _textInput = (RCTTextInputComponentView *)subview;
55- _backedTextInputView = [_textInput valueForKey: @" _backedTextInputView" ];
57+ RCTTextInputComponentView *textInputComponentView = (RCTTextInputComponentView *)subview;
58+ UIView<RCTBackedTextInputViewProtocol> *backedTextInputView = [textInputComponentView valueForKey: @" _backedTextInputView" ];
5659
5760 _markdownUtils = [[RCTMarkdownUtils alloc ] init ];
5861 [_markdownUtils setMarkdownStyle: _markdownStyle];
5962 [_markdownUtils setParserId: _parserId];
6063
61- [_textInput setMarkdownUtils: _markdownUtils];
62- if ([_backedTextInputView isKindOfClass: [RCTUITextField class ]]) {
63- RCTUITextField *textField = (RCTUITextField *)_backedTextInputView;
64- _adapter = [textField valueForKey: @" textInputDelegateAdapter" ];
65- [_adapter setMarkdownUtils: _markdownUtils];
66- } else if ([_backedTextInputView isKindOfClass: [RCTUITextView class ]]) {
67- _textView = (RCTUITextView *)_backedTextInputView;
68- [_textView setMarkdownUtils: _markdownUtils];
64+ if ([backedTextInputView isKindOfClass: [RCTUITextField class ]]) {
65+ _textField = (RCTUITextField *)backedTextInputView;
66+
67+ // make sure `adjustsFontSizeToFitWidth` is disabled, otherwise formatting will be overwritten
68+ react_native_assert (_textField.adjustsFontSizeToFitWidth == NO );
69+
70+ _markdownTextFieldObserver = [[MarkdownTextFieldObserver alloc ] initWithTextField: _textField markdownUtils: _markdownUtils];
71+
72+ // register observers for future edits
73+ [_textField addTarget: _markdownTextFieldObserver action: @selector (textFieldDidChange: ) forControlEvents: UIControlEventEditingChanged];
74+ [_textField addTarget: _markdownTextFieldObserver action: @selector (textFieldDidEndEditing: ) forControlEvents: UIControlEventEditingDidEnd];
75+ [_textField addObserver: _markdownTextFieldObserver forKeyPath: @" text" options: NSKeyValueObservingOptionNew context: NULL ];
76+ [_textField addObserver: _markdownTextFieldObserver forKeyPath: @" attributedText" options: NSKeyValueObservingOptionNew context: NULL ];
77+
78+ // format initial value
79+ [_markdownTextFieldObserver textFieldDidChange: _textField];
80+
81+ // TODO: register blockquotes layout manager
82+ // https://github.com/Expensify/react-native-live-markdown/issues/87
83+ } else if ([backedTextInputView isKindOfClass: [RCTUITextView class ]]) {
84+ _textView = (RCTUITextView *)backedTextInputView;
85+
86+ // register delegate for future edits
87+ react_native_assert (_textView.textStorage .delegate == nil );
88+ _markdownTextStorageDelegate = [[MarkdownTextStorageDelegate alloc ] initWithTextView: _textView markdownUtils: _markdownUtils];
89+ _textView.textStorage .delegate = _markdownTextStorageDelegate;
90+
91+ // register observer for default text attributes
92+ _markdownTextViewObserver = [[MarkdownTextViewObserver alloc ] initWithTextView: _textView markdownUtils: _markdownUtils];
93+ [_textView addObserver: _markdownTextViewObserver forKeyPath: @" defaultTextAttributes" options: NSKeyValueObservingOptionNew context: NULL ];
94+
95+ // format initial value
96+ [_textView.textStorage setAttributedString: _textView.attributedText];
97+
6998 NSLayoutManager *layoutManager = _textView.layoutManager ; // switching to TextKit 1 compatibility mode
7099
71100 // Correct content height in TextKit 1 compatibility mode. (See https://github.com/Expensify/App/issues/41567)
@@ -91,19 +120,26 @@ - (void)willMoveToWindow:(UIWindow *)newWindow
91120 if (newWindow != nil ) {
92121 return ;
93122 }
94- if (_textInput != nil ) {
95- [_textInput setMarkdownUtils: nil ];
96- }
97- if (_adapter != nil ) {
98- [_adapter setMarkdownUtils: nil ];
99- }
100123 if (_textView != nil ) {
101- _markdownBackedTextInputDelegate = nil ;
102- [_textView setMarkdownUtils: nil ];
103124 if (_textView.layoutManager != nil && [object_getClass (_textView.layoutManager) isEqual: [MarkdownLayoutManager class ]]) {
104125 [_textView.layoutManager setValue: nil forKey: @" markdownUtils" ];
105126 object_setClass (_textView.layoutManager , [NSLayoutManager class ]);
106127 }
128+ _markdownBackedTextInputDelegate = nil ;
129+ [_textView removeObserver: _markdownTextViewObserver forKeyPath: @" defaultTextAttributes" context: NULL ];
130+ _markdownTextViewObserver = nil ;
131+ _markdownTextStorageDelegate = nil ;
132+ _textView.textStorage .delegate = nil ;
133+ _textView = nil ;
134+ }
135+
136+ if (_textField != nil ) {
137+ [_textField removeTarget: _markdownTextFieldObserver action: @selector (textFieldDidChange: ) forControlEvents: UIControlEventEditingChanged];
138+ [_textField removeTarget: _markdownTextFieldObserver action: @selector (textFieldDidEndEditing: ) forControlEvents: UIControlEventEditingDidEnd];
139+ [_textField removeObserver: _markdownTextFieldObserver forKeyPath: @" text" context: NULL ];
140+ [_textField removeObserver: _markdownTextFieldObserver forKeyPath: @" attributedText" context: NULL ];
141+ _markdownTextFieldObserver = nil ;
142+ _textField = nil ;
107143 }
108144}
109145
@@ -130,11 +166,10 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
130166- (void )applyNewStyles
131167{
132168 if (_textView != nil ) {
133- // We want to use `textStorage` for applying markdown when possible. Currently it's only available for UITextView
134- [_textView textDidChange ];
135- } else {
136- // apply new styles
137- [_textInput _setAttributedString: _backedTextInputView.attributedText];
169+ [_textView.textStorage setAttributedString: _textView.attributedText];
170+ }
171+ if (_textField != nil ) {
172+ [_markdownTextFieldObserver textFieldDidChange: _textField];
138173 }
139174}
140175
0 commit comments