Skip to content

Commit

Permalink
[MDCAlertControllerView] Avoid cutting off the top of Thai characters…
Browse files Browse the repository at this point in the history
… with diacritics.

In iOS platform, text font are not strictly required to fit within the UIFont.lineHeight. Some Thai characters with diacritics are rendered outside the UILabel/UITextView's bounds.

One solution is to increase the top margin of the UILabel/UITextView so we leave extra padding between the parent view's top edge and the top edge of the UILabel/UITextView (Like the _titleLabel in MDCAlertControllerView).

closes #10161

PiperOrigin-RevId: 363491472
  • Loading branch information
Nobody authored and material-automation committed Mar 17, 2021
1 parent 0f99d29 commit 08d42c1
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 0 deletions.
88 changes: 88 additions & 0 deletions components/Dialogs/src/private/MDCAlertControllerView+Private.m
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,35 @@

static const CGFloat MDCDialogMessageOpacity = 0.54f;

/** Calculates the minimum text height for a single line text using device metrics. */
static CGFloat SingleLineTextViewHeight(NSString *_Nullable title, UIFont *_Nullable font) {
if (title.length == 0) {
return font.lineHeight;
}

NSDictionary<NSAttributedStringKey, id> *attributes =
font == nil ? nil : @{NSFontAttributeName : font};
CGRect boundingRect = [title boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)
options:NSStringDrawingUsesDeviceMetrics
attributes:attributes
context:nil];
// Some text height may exceed the UIFont.lineHeight.
// https://developer.apple.com/documentation/uikit/uifont?language=objc
// --------------------
// | |
// | <= ascender |
// | | <= lineHeight
// -----------------|
// | <= descender |
// --------------------
// UIFont.lineHeight consist of UIFont.ascender and UIFont.descender. UIFont.descender is the
// bottom offset from the baseline. The boundingRect.origin.y is the amount of space the text will
// occupy in UIFont.descender. So the minimum text view line height is text height + the leftover
// space in the UIFont.descender not occupied by the rendered text.
CGFloat bottomPadding = boundingRect.origin.y - font.descender;
return boundingRect.size.height + bottomPadding;
}

@interface MDCNonselectableTextView : UITextView
@end

Expand Down Expand Up @@ -1059,6 +1088,41 @@ - (void)updateFonts {

@implementation MDCNonselectableTextView

#pragma mark - UITextView

- (void)setAttributedText:(NSAttributedString *)attributedText {
if ([self.attributedText isEqual:attributedText]) {
return;
}

[super setAttributedText:attributedText];
[self updateTopInsetAndTextContainerInset:self.textContainerInset];
}

- (void)setFont:(UIFont *)font {
if ([self.font isEqual:font]) {
return;
}

[super setFont:font];
[self updateTopInsetAndTextContainerInset:self.textContainerInset];
}

- (void)setText:(NSString *)text {
if ([self.text isEqual:text]) {
return;
}

[super setText:text];
[self updateTopInsetAndTextContainerInset:self.textContainerInset];
}

- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset {
[self updateTopInsetAndTextContainerInset:textContainerInset];
}

#pragma mark - UIView

// Disabling text selection when selectable is YES, while allowing gestures for inlined links.
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
if (UIAccessibilityIsVoiceOverRunning()) {
Expand Down Expand Up @@ -1086,4 +1150,28 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
return link != nil;
}

#pragma mark - Private

/**
* Updates the top text inset of the UITextView to avoid rendering text outside the view
* boundary. We need to calculate the text margin when we change the text or the text font.
*
* @note Our title UILabel already has a top margin, so its text won't exceed the parent view's
* boundary.
*
* @param textContainerInset The target text insets to display. It will update the following text
* margin: left, right and bottom.
*/
- (void)updateTopInsetAndTextContainerInset:(UIEdgeInsets)textContainerInset {
// To avoid changing the top margin when the view width changes, we only compare the text height
// for a single line text.
UIFont *font = self.font;
CGFloat singleLineTextViewHeight = SingleLineTextViewHeight(self.text, font);
textContainerInset.top = MAX(0.0f, singleLineTextViewHeight - font.lineHeight);
if (!UIEdgeInsetsEqualToEdgeInsets(super.textContainerInset, textContainerInset)) {
// Note that |self setTextContainerInset:| will call this method.
super.textContainerInset = textContainerInset;
}
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#import "MDCAlertController+Testing.h"
#import "MaterialSnapshot.h"

static NSString *const kThaiTextWithDiacritics = @"นี้ Thai text นี้";
static NSString *const kTitleUrdu = @"عنوان";
static NSString *const kMessageUrdu =
@"براہ کرم اپنا نیٹ ورک کنکشن چیک کریں اور دوبارہ کوشش کریں۔براہ کرم اپنا نیٹ ورک کنکشن چیک "
Expand Down Expand Up @@ -86,6 +87,35 @@ - (void)changeToRTL:(MDCAlertController *)alertController {

#pragma mark - Tests

/** Verifies the top of Thai attributed message with diacritics won't be cut off. */
- (void)testPreferredContentSizeWithThaiAttributedMessage {
// When
self.alertController.attributedMessage =
[[NSAttributedString alloc] initWithString:kThaiTextWithDiacritics];

// Then
[self generateSizedSnapshotAndVerifyForAlert:self.alertController];
}

/** Verifies the top of Thai message with diacritics won't be cut off. */
- (void)testPreferredContentSizeWithThaiMessage {
// When
self.alertController.message = kThaiTextWithDiacritics;

// Then
[self generateSizedSnapshotAndVerifyForAlert:self.alertController];
}

/** Verifies the top of Thai message and title with diacritics won't be cut off. */
- (void)testPreferredContentSizeWithThaiMessageAndTitle {
// When
self.alertController.title = kThaiTextWithDiacritics;
self.alertController.message = kThaiTextWithDiacritics;

// Then
[self generateSizedSnapshotAndVerifyForAlert:self.alertController];
}

- (void)testPreferredContentSizeWithNotoNastaliqUrdu {
// When
self.alertController.title = kTitleUrdu;
Expand Down

0 comments on commit 08d42c1

Please sign in to comment.