-
Notifications
You must be signed in to change notification settings - Fork 74
/
Copy pathMarkdownTextInputDecoratorComponentView.mm
183 lines (152 loc) · 6.68 KB
/
MarkdownTextInputDecoratorComponentView.mm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#import <react/debug/react_native_assert.h>
#import <react/renderer/components/RNLiveMarkdownSpec/Props.h>
#import <React/RCTFabricComponentsPlugins.h>
#import <React/RCTUITextField.h>
#import <RNLiveMarkdown/MarkdownLayoutManager.h>
#import <RNLiveMarkdown/MarkdownShadowFamilyRegistry.h>
#import <RNLiveMarkdown/MarkdownTextInputDecoratorComponentView.h>
#import <RNLiveMarkdown/MarkdownTextInputDecoratorViewComponentDescriptor.h>
#import <RNLiveMarkdown/RCTBackedTextFieldDelegateAdapter+Markdown.h>
#import <RNLiveMarkdown/RCTMarkdownStyle.h>
#import <RNLiveMarkdown/RCTTextInputComponentView+Markdown.h>
#import <RNLiveMarkdown/RCTUITextView+Markdown.h>
#import <objc/runtime.h>
using namespace facebook::react;
@implementation MarkdownTextInputDecoratorComponentView {
RCTMarkdownUtils *_markdownUtils;
RCTMarkdownStyle *_markdownStyle;
NSNumber *_parserId;
__weak RCTTextInputComponentView *_textInput;
__weak UIView<RCTBackedTextInputViewProtocol> *_backedTextInputView;
__weak RCTBackedTextFieldDelegateAdapter *_adapter;
__weak RCTUITextView *_textView;
ShadowNodeFamily::Shared _decoratorFamily;
}
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<MarkdownTextInputDecoratorViewComponentDescriptor>();
}
// Needed because of this: https://github.com/facebook/react-native/pull/37274
+ (void)load
{
[super load];
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const MarkdownTextInputDecoratorViewProps>();
_props = defaultProps;
}
return self;
}
- (void)updateState:(const facebook::react::State::Shared &)state oldState:(const facebook::react::State::Shared &)oldState
{
auto data = std::static_pointer_cast<MarkdownTextInputDecoratorShadowNode::ConcreteState const>(state)->getData();
if (_decoratorFamily != nullptr) {
MarkdownShadowFamilyRegistry::unregisterFamilyForUpdates(_decoratorFamily);
}
_decoratorFamily = data.decoratorFamily;
MarkdownShadowFamilyRegistry::registerFamilyForUpdates(_decoratorFamily);
}
- (void)willMoveToSuperview:(UIView *)newSuperview {
if (newSuperview == nil) {
MarkdownShadowFamilyRegistry::unregisterFamilyForUpdates(_decoratorFamily);
_decoratorFamily = nullptr;
}
[super willMoveToSuperview:newSuperview];
}
- (void)didMoveToWindow {
if (self.superview == nil) {
return;
}
NSArray *viewsArray = self.superview.subviews;
NSUInteger currentIndex = [viewsArray indexOfObject:self];
react_native_assert(currentIndex != 0 && currentIndex != NSNotFound && "Error while finding current component.");
UIView *view = [viewsArray objectAtIndex:currentIndex - 1];
react_native_assert([view isKindOfClass:[RCTTextInputComponentView class]] && "Previous sibling component is not an instance of RCTTextInputComponentView.");
_textInput = (RCTTextInputComponentView *)view;
_backedTextInputView = [_textInput valueForKey:@"_backedTextInputView"];
_markdownUtils = [[RCTMarkdownUtils alloc] init];
react_native_assert(_markdownStyle != nil);
[_markdownUtils setMarkdownStyle:_markdownStyle];
[_markdownUtils setParserId:_parserId];
[_textInput setMarkdownUtils:_markdownUtils];
if ([_backedTextInputView isKindOfClass:[RCTUITextField class]]) {
RCTUITextField *textField = (RCTUITextField *)_backedTextInputView;
_adapter = [textField valueForKey:@"textInputDelegateAdapter"];
[_adapter setMarkdownUtils:_markdownUtils];
} else if ([_backedTextInputView isKindOfClass:[RCTUITextView class]]) {
_textView = (RCTUITextView *)_backedTextInputView;
[_textView setMarkdownUtils:_markdownUtils];
NSLayoutManager *layoutManager = _textView.layoutManager; // switching to TextKit 1 compatibility mode
// Correct content height in TextKit 1 compatibility mode. (See https://github.com/Expensify/App/issues/41567)
// Consider removing this fix if it is no longer needed after migrating to TextKit 2.
CGSize contentSize = _textView.contentSize;
CGRect textBounds = [layoutManager usedRectForTextContainer:_textView.textContainer];
contentSize.height = textBounds.size.height + _textView.textContainerInset.top + _textView.textContainerInset.bottom;
[_textView setContentSize:contentSize];
layoutManager.allowsNonContiguousLayout = NO; // workaround for onScroll issue
object_setClass(layoutManager, [MarkdownLayoutManager class]);
[layoutManager setValue:_markdownUtils forKey:@"markdownUtils"];
} else {
react_native_assert(false && "Cannot enable Markdown for this type of TextInput.");
}
}
- (void)willMoveToWindow:(UIWindow *)newWindow
{
if (_textInput != nil) {
[_textInput setMarkdownUtils:nil];
}
if (_adapter != nil) {
[_adapter setMarkdownUtils:nil];
}
if (_textView != nil) {
[_textView setMarkdownUtils:nil];
if (_textView.layoutManager != nil && [object_getClass(_textView.layoutManager) isEqual:[MarkdownLayoutManager class]]) {
[_textView.layoutManager setValue:nil forKey:@"markdownUtils"];
object_setClass(_textView.layoutManager, [NSLayoutManager class]);
}
}
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &oldViewProps = *std::static_pointer_cast<MarkdownTextInputDecoratorViewProps const>(_props);
const auto &newViewProps = *std::static_pointer_cast<MarkdownTextInputDecoratorViewProps const>(props);
if (oldViewProps.parserId != newViewProps.parserId) {
[self setParserId:@(newViewProps.parserId)];
}
// TODO: if (oldViewProps.markdownStyle != newViewProps.markdownStyle)
RCTMarkdownStyle *markdownStyle = [[RCTMarkdownStyle alloc] initWithStruct:newViewProps.markdownStyle];
[self setMarkdownStyle:markdownStyle];
[super updateProps:props oldProps:oldProps];
}
- (void)setMarkdownStyle:(RCTMarkdownStyle *)markdownStyle
{
// TODO: move to updateProps method
_markdownStyle = markdownStyle;
[_markdownUtils setMarkdownStyle:markdownStyle];
[self applyNewStyles];
}
- (void)setParserId:(NSNumber *)parserId
{
// TODO: move to updateProps method
_parserId = parserId;
[_markdownUtils setParserId:parserId];
[self applyNewStyles];
}
- (void)applyNewStyles
{
// TODO: call applyNewStyles only once if both markdownStyle and parserId change
if (_textView != nil) {
// We want to use `textStorage` for applying markdown when possible. Currently it's only available for UITextView
[_textView textDidChange];
} else {
// apply new styles
[_textInput _setAttributedString:_backedTextInputView.attributedText];
}
}
Class<RCTComponentViewProtocol> MarkdownTextInputDecoratorViewCls(void)
{
return MarkdownTextInputDecoratorComponentView.class;
}
@end