Skip to content

Commit 47fbb05

Browse files
committed
refactor(ios): extract shared text view setup, layout manager, and measurement helpers
1 parent 2d0195e commit 47fbb05

3 files changed

Lines changed: 73 additions & 87 deletions

File tree

ios/EnrichedMarkdownText.mm

Lines changed: 8 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,20 @@
99
#import "ENRMSpoilerOverlayManager.h"
1010
#import "ENRMSpoilerTapUtils.h"
1111
#import "ENRMTailFadeInAnimator.h"
12-
#import "ENRMUIKit.h"
12+
#import "ENRMTextViewSetup.h"
1313
#import "EditMenuUtils.h"
1414
#import "FontScaleObserver.h"
1515
#import "FontUtils.h"
1616
#import "HeightUpdateUtils.h"
17-
#import "LastElementUtils.h"
1817
#import "LinkTapUtils.h"
1918
#import "MarkdownASTNode.h"
2019
#import "MarkdownAccessibilityElementBuilder.h"
2120
#import "MarkdownExtractor.h"
2221
#import "ParagraphStyleUtils.h"
2322
#import "RenderContext.h"
2423
#import "RuntimeKeys.h"
25-
#import "StyleConfig.h"
2624
#import "StylePropsUtils.h"
2725
#import "TaskListTapUtils.h"
28-
#import "TextViewLayoutManager.h"
29-
#import <React/RCTUtils.h>
30-
#import <objc/runtime.h>
3126

3227
#import <ReactNativeEnrichedMarkdown/EnrichedMarkdownTextComponentDescriptor.h>
3328
#import <ReactNativeEnrichedMarkdown/EventEmitters.h>
@@ -98,35 +93,12 @@ + (ComponentDescriptorProvider)componentDescriptorProvider
9893

9994
- (CGSize)measureSize:(CGFloat)maxWidth
10095
{
101-
NSAttributedString *text = ENRMGetAttributedText(_textView);
102-
CGFloat defaultHeight = UIFontLineHeight([UIFont systemFontOfSize:16.0]);
103-
104-
if (text.length == 0) {
96+
CGSize size = ENRMMeasureMarkdownText(_textView, maxWidth, _config, _allowTrailingMargin, _lastElementMarginBottom);
97+
if (CGSizeEqualToSize(size, CGSizeZero)) {
98+
CGFloat defaultHeight = UIFontLineHeight([UIFont systemFontOfSize:16.0]);
10599
return CGSizeMake(maxWidth, defaultHeight);
106100
}
107-
108-
ENRMTextLayoutResult layout = ENRMMeasureTextLayout(_textView, maxWidth);
109-
110-
CGFloat measuredWidth = layout.usedRect.size.width;
111-
CGFloat measuredHeight = layout.usedRect.size.height;
112-
113-
if (!CGRectIsEmpty(layout.extraLineFragmentRect)) {
114-
measuredHeight -= layout.extraLineFragmentRect.size.height;
115-
}
116-
117-
// Code block's bottom padding is a spacer \n with minimumLineHeight = codeBlockPadding.
118-
// The layout manager may not size it accurately, so add the padding explicitly.
119-
if (isLastElementCodeBlock(text)) {
120-
measuredHeight += [_config codeBlockPadding];
121-
}
122-
123-
if (_allowTrailingMargin && _lastElementMarginBottom > 0) {
124-
measuredHeight += _lastElementMarginBottom;
125-
}
126-
127-
// Round to pixel boundaries to match React Native's <Text> measurement
128-
CGFloat scale = RCTScreenScale();
129-
return CGSizeMake(ceil(measuredWidth * scale) / scale, ceil(measuredHeight * scale) / scale);
101+
return size;
130102
}
131103

132104
- (BOOL)hasRenderedMarkdown:(NSString *)markdown
@@ -250,28 +222,15 @@ - (void)didAddSubview:(RCTUIView *)subview
250222

251223
- (void)willRemoveSubview:(RCTUIView *)subview
252224
{
253-
if (subview == _textView && _textView.layoutManager != nil) {
254-
NSLayoutManager *layoutManager = _textView.layoutManager;
255-
if ([object_getClass(layoutManager) isEqual:[TextViewLayoutManager class]]) {
256-
[layoutManager setValue:nil forKey:@"config"];
257-
object_setClass(layoutManager, [NSLayoutManager class]);
258-
}
225+
if (subview == _textView) {
226+
ENRMDetachLayoutManager(_textView);
259227
}
260228
[super willRemoveSubview:subview];
261229
}
262230

263231
- (void)setupLayoutManager
264232
{
265-
// Custom layout manager handles drawing for code blocks, blockquotes, etc.
266-
NSLayoutManager *layoutManager = _textView.layoutManager;
267-
if (layoutManager != nil) {
268-
layoutManager.allowsNonContiguousLayout = NO; // workaround for onScroll issue
269-
object_setClass(layoutManager, [TextViewLayoutManager class]);
270-
271-
if (_config != nil) {
272-
[layoutManager setValue:_config forKey:@"config"];
273-
}
274-
}
233+
ENRMAttachLayoutManager(_textView, _config);
275234
}
276235

277236
- (void)renderMarkdownContent:(NSString *)markdownString

ios/utils/ENRMTextViewSetup.h

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#pragma once
2+
#import "ENRMUIKit.h"
3+
#import "LastElementUtils.h"
4+
#import "StyleConfig.h"
5+
#import "TextViewLayoutManager.h"
6+
#import <React/RCTUtils.h>
7+
#import <objc/runtime.h>
8+
9+
NS_ASSUME_NONNULL_BEGIN
10+
11+
static inline void ENRMAttachLayoutManager(ENRMPlatformTextView *textView, StyleConfig *_Nullable config)
12+
{
13+
NSLayoutManager *layoutManager = textView.layoutManager;
14+
if (layoutManager == nil) {
15+
return;
16+
}
17+
layoutManager.allowsNonContiguousLayout = NO;
18+
object_setClass(layoutManager, [TextViewLayoutManager class]);
19+
if (config != nil) {
20+
[layoutManager setValue:config forKey:@"config"];
21+
}
22+
}
23+
24+
static inline void ENRMDetachLayoutManager(ENRMPlatformTextView *textView)
25+
{
26+
NSLayoutManager *layoutManager = textView.layoutManager;
27+
if (layoutManager != nil && [object_getClass(layoutManager) isEqual:[TextViewLayoutManager class]]) {
28+
[layoutManager setValue:nil forKey:@"config"];
29+
object_setClass(layoutManager, [NSLayoutManager class]);
30+
}
31+
}
32+
33+
static inline CGSize ENRMMeasureMarkdownText(ENRMPlatformTextView *textView, CGFloat maxWidth, StyleConfig *config,
34+
BOOL allowTrailingMargin, CGFloat lastElementMarginBottom)
35+
{
36+
NSAttributedString *text = ENRMGetAttributedText(textView);
37+
if (text.length == 0) {
38+
return CGSizeZero;
39+
}
40+
41+
ENRMTextLayoutResult layout = ENRMMeasureTextLayout(textView, maxWidth);
42+
43+
CGFloat measuredWidth = layout.usedRect.size.width;
44+
CGFloat measuredHeight = layout.usedRect.size.height;
45+
46+
if (!CGRectIsEmpty(layout.extraLineFragmentRect)) {
47+
measuredHeight -= layout.extraLineFragmentRect.size.height;
48+
}
49+
50+
if (isLastElementCodeBlock(text)) {
51+
measuredHeight += [config codeBlockPadding];
52+
}
53+
54+
if (allowTrailingMargin && lastElementMarginBottom > 0) {
55+
measuredHeight += lastElementMarginBottom;
56+
}
57+
58+
CGFloat scale = RCTScreenScale();
59+
return CGSizeMake(ceil(measuredWidth * scale) / scale, ceil(measuredHeight * scale) / scale);
60+
}
61+
62+
NS_ASSUME_NONNULL_END

ios/views/EnrichedMarkdownInternalText.m

Lines changed: 3 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,11 @@
22
#import "AccessibilityInfo.h"
33
#import "ENRMContextMenuTextView+macOS.h"
44
#import "ENRMSpoilerOverlayManager.h"
5-
#import "ENRMUIKit.h"
6-
#import "LastElementUtils.h"
5+
#import "ENRMTextViewSetup.h"
76
#import "MarkdownAccessibilityElementBuilder.h"
87
#import "RenderContext.h"
98
#import "RuntimeKeys.h"
10-
#import "StyleConfig.h"
11-
#import "TextViewLayoutManager.h"
12-
#import <React/RCTUtils.h>
139
#include <TargetConditionals.h>
14-
#import <objc/runtime.h>
1510

1611
@implementation EnrichedMarkdownInternalText {
1712
ENRMPlatformTextView *_textView;
@@ -59,14 +54,7 @@ - (void)setupTextView
5954

6055
- (void)setupLayoutManager
6156
{
62-
NSLayoutManager *layoutManager = _textView.layoutManager;
63-
if (layoutManager != nil) {
64-
layoutManager.allowsNonContiguousLayout = NO;
65-
object_setClass(layoutManager, [TextViewLayoutManager class]);
66-
if (_config != nil) {
67-
[layoutManager setValue:_config forKey:@"config"];
68-
}
69-
}
57+
ENRMAttachLayoutManager(_textView, _config);
7058
}
7159

7260
- (void)setSpoilerMode:(ENRMSpoilerMode)spoilerMode
@@ -108,30 +96,7 @@ - (CGFloat)measureHeight:(CGFloat)maxWidth
10896

10997
- (CGSize)measureSize:(CGFloat)maxWidth
11098
{
111-
NSAttributedString *text = ENRMGetAttributedText(_textView);
112-
if (text.length == 0) {
113-
return CGSizeZero;
114-
}
115-
116-
ENRMTextLayoutResult layout = ENRMMeasureTextLayout(_textView, maxWidth);
117-
118-
CGFloat measuredHeight = layout.usedRect.size.height;
119-
CGFloat measuredWidth = layout.usedRect.size.width;
120-
121-
if (!CGRectIsEmpty(layout.extraLineFragmentRect)) {
122-
measuredHeight -= layout.extraLineFragmentRect.size.height;
123-
}
124-
125-
if (isLastElementCodeBlock(text)) {
126-
measuredHeight += [_config codeBlockPadding];
127-
}
128-
129-
if (_allowTrailingMargin && _lastElementMarginBottom > 0) {
130-
measuredHeight += _lastElementMarginBottom;
131-
}
132-
133-
CGFloat scale = RCTScreenScale();
134-
return CGSizeMake(ceil(measuredWidth * scale) / scale, ceil(measuredHeight * scale) / scale);
99+
return ENRMMeasureMarkdownText(_textView, maxWidth, _config, _allowTrailingMargin, _lastElementMarginBottom);
135100
}
136101

137102
- (void)layoutSubviews

0 commit comments

Comments
 (0)