Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 42 additions & 89 deletions ios/EnrichedTextInputView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ @implementation EnrichedTextInputView {
UIColor *_placeholderColor;
BOOL _emitFocusBlur;
BOOL _emitTextChange;
NSLayoutConstraint *_placeholderTopConstraint;
NSLayoutConstraint *_placeholderLeadingConstraint;
NSLayoutConstraint *_placeholderWidthConstraint;
}

// MARK: - Component utils
Expand Down Expand Up @@ -246,14 +249,16 @@ - (void)setupPlaceholderLabel {
_placeholderLabel = [[UILabel alloc] initWithFrame:CGRectZero];
_placeholderLabel.translatesAutoresizingMaskIntoConstraints = NO;
[textView addSubview:_placeholderLabel];
_placeholderLeadingConstraint = [_placeholderLabel.leadingAnchor
constraintEqualToAnchor:textView.leadingAnchor];
_placeholderWidthConstraint = [_placeholderLabel.widthAnchor
constraintEqualToAnchor:textView.widthAnchor];
_placeholderTopConstraint =
[_placeholderLabel.topAnchor constraintEqualToAnchor:textView.topAnchor];
[NSLayoutConstraint activateConstraints:@[
[_placeholderLabel.leadingAnchor
constraintEqualToAnchor:textView.leadingAnchor],
[_placeholderLabel.widthAnchor
constraintEqualToAnchor:textView.widthAnchor],
[_placeholderLabel.topAnchor constraintEqualToAnchor:textView.topAnchor],
[_placeholderLabel.bottomAnchor
constraintEqualToAnchor:textView.bottomAnchor]
_placeholderLeadingConstraint,
_placeholderWidthConstraint,
_placeholderTopConstraint,
]];
_placeholderLabel.lineBreakMode = NSLineBreakByTruncatingTail;
_placeholderLabel.text = @"";
Expand Down Expand Up @@ -760,6 +765,26 @@ - (void)updateProps:(Props::Shared const &)props
}
}

- (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics {
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];

UIEdgeInsets insets = RCTUIEdgeInsetsFromEdgeInsets(
layoutMetrics.contentInsets - layoutMetrics.borderWidth);

textView.frame = UIEdgeInsetsInsetRect(
self.bounds, RCTUIEdgeInsetsFromEdgeInsets(layoutMetrics.borderWidth));
textView.textContainerInset = insets;

_placeholderTopConstraint.constant = insets.top;
_placeholderLeadingConstraint.constant = insets.left;
// We subtract (left + right) from the width so the text doesn't flow off the
// screen
_placeholderWidthConstraint.constant = -(insets.left + insets.right);

[_placeholderLabel layoutIfNeeded];
}

- (void)setPlaceholderLabelShown:(BOOL)shown {
if (shown) {
[self refreshPlaceholderLabelStyles];
Expand All @@ -783,50 +808,18 @@ - (void)refreshPlaceholderLabelStyles {
// MARK: - Measuring and states

- (CGSize)measureSize:(CGFloat)maxWidth {
// copy the the whole attributed string
NSMutableAttributedString *currentStr = [[NSMutableAttributedString alloc]
initWithAttributedString:textView.textStorage];

// edge case: empty input should still be of a height of a single line, so we
// add a mock "I" character
if ([currentStr length] == 0) {
[currentStr
appendAttributedString:[[NSAttributedString alloc]
initWithString:@"I"
attributes:textView.typingAttributes]];
}

// edge case: input with only a zero width space should still be of a height
// of a single line, so we add a mock "I" character
if ([currentStr length] == 1 &&
[[currentStr.string substringWithRange:NSMakeRange(0, 1)]
isEqualToString:@"\u200B"]) {
[currentStr
appendAttributedString:[[NSAttributedString alloc]
initWithString:@"I"
attributes:textView.typingAttributes]];
}

// edge case: trailing newlines aren't counted towards height calculations, so
// we add a mock "I" character
if (currentStr.length > 0) {
unichar lastChar =
[currentStr.string characterAtIndex:currentStr.length - 1];
if ([[NSCharacterSet newlineCharacterSet] characterIsMember:lastChar]) {
[currentStr
appendAttributedString:[[NSAttributedString alloc]
initWithString:@"I"
attributes:defaultTypingAttributes]];
}
}
UIEdgeInsets savedInset = textView.textContainerInset;
CGFloat savedLinePadding = textView.textContainer.lineFragmentPadding;

textView.textContainerInset = UIEdgeInsetsZero;
textView.textContainer.lineFragmentPadding = 0;

CGSize fitSize = [textView sizeThatFits:CGSizeMake(maxWidth, CGFLOAT_MAX)];

CGRect boundingBox =
[currentStr boundingRectWithSize:CGSizeMake(maxWidth, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin |
NSStringDrawingUsesFontLeading
context:nullptr];
textView.textContainerInset = savedInset;
textView.textContainer.lineFragmentPadding = savedLinePadding;

return CGSizeMake(maxWidth, ceil(boundingBox.size.height));
return CGSizeMake(maxWidth, ceil(fitSize.height));
}

// make sure the newest state is kept in _state property
Expand Down Expand Up @@ -1535,46 +1528,6 @@ - (void)anyTextMayHaveBeenModified {
[self tryUpdatingHeight];
// update active styles as well
[self tryUpdatingActiveStyles];
// update drawing - schedule debounced relayout
[self scheduleRelayoutIfNeeded];
}

// Debounced relayout helper - coalesces multiple requests into one per runloop
// tick
- (void)scheduleRelayoutIfNeeded {
// Cancel any previously scheduled invocation to debounce
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:@selector(_performRelayout)
object:nil];
// Schedule on next runloop cycle
[self performSelector:@selector(_performRelayout)
withObject:nil
afterDelay:0];
}

- (void)_performRelayout {
if (!textView) {
return;
}

dispatch_async(dispatch_get_main_queue(), ^{
NSRange wholeRange =
NSMakeRange(0, self->textView.textStorage.string.length);
NSRange actualRange = NSMakeRange(0, 0);
[self->textView.layoutManager
invalidateLayoutForCharacterRange:wholeRange
actualCharacterRange:&actualRange];
[self->textView.layoutManager ensureLayoutForCharacterRange:actualRange];
[self->textView.layoutManager
invalidateDisplayForCharacterRange:wholeRange];

// We have to explicitly set contentSize
// That way textView knows if content overflows and if should be scrollable
// We recall measureSize here because value returned from previous
// measureSize may not be up-to date at that point
CGSize measuredSize = [self measureSize:self->textView.frame.size.width];
self->textView.contentSize = measuredSize;
});
}

- (void)didMoveToWindow {
Expand Down
14 changes: 7 additions & 7 deletions ios/utils/LayoutManagerExtension.mm
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,9 @@ - (void)drawLists:(EnrichedTextInputView *)typedInput
[marker sizeWithAttributes:
markerAttributes]
.width;
CGFloat markerX = usedRect.origin.x -
gapWidth -
markerWidth / 2;
CGFloat markerX =
origin.x + usedRect.origin.x -
gapWidth - markerWidth / 2;

[marker drawAtPoint:CGPointMake(
markerX,
Expand All @@ -317,11 +317,11 @@ - (void)drawLists:(EnrichedTextInputView *)typedInput
CGFloat bulletSize =
[typedInput->config
unorderedListBulletSize];
CGFloat bulletX = usedRect.origin.x -
gapWidth -
bulletSize / 2;
CGFloat bulletX =
origin.x + usedRect.origin.x -
gapWidth - bulletSize / 2;
CGFloat centerY =
CGRectGetMidY(usedRect);
CGRectGetMidY(usedRect) + origin.y;

CGContextRef context =
UIGraphicsGetCurrentContext();
Expand Down
Loading