Skip to content

Commit 08d42c1

Browse files
Nobodymaterial-automation
authored andcommitted
[MDCAlertControllerView] Avoid cutting off the top of Thai characters 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
1 parent 0f99d29 commit 08d42c1

File tree

2 files changed

+118
-0
lines changed

2 files changed

+118
-0
lines changed

components/Dialogs/src/private/MDCAlertControllerView+Private.m

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,35 @@
3636

3737
static const CGFloat MDCDialogMessageOpacity = 0.54f;
3838

39+
/** Calculates the minimum text height for a single line text using device metrics. */
40+
static CGFloat SingleLineTextViewHeight(NSString *_Nullable title, UIFont *_Nullable font) {
41+
if (title.length == 0) {
42+
return font.lineHeight;
43+
}
44+
45+
NSDictionary<NSAttributedStringKey, id> *attributes =
46+
font == nil ? nil : @{NSFontAttributeName : font};
47+
CGRect boundingRect = [title boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)
48+
options:NSStringDrawingUsesDeviceMetrics
49+
attributes:attributes
50+
context:nil];
51+
// Some text height may exceed the UIFont.lineHeight.
52+
// https://developer.apple.com/documentation/uikit/uifont?language=objc
53+
// --------------------
54+
// | |
55+
// | <= ascender |
56+
// | | <= lineHeight
57+
// -----------------|
58+
// | <= descender |
59+
// --------------------
60+
// UIFont.lineHeight consist of UIFont.ascender and UIFont.descender. UIFont.descender is the
61+
// bottom offset from the baseline. The boundingRect.origin.y is the amount of space the text will
62+
// occupy in UIFont.descender. So the minimum text view line height is text height + the leftover
63+
// space in the UIFont.descender not occupied by the rendered text.
64+
CGFloat bottomPadding = boundingRect.origin.y - font.descender;
65+
return boundingRect.size.height + bottomPadding;
66+
}
67+
3968
@interface MDCNonselectableTextView : UITextView
4069
@end
4170

@@ -1059,6 +1088,41 @@ - (void)updateFonts {
10591088

10601089
@implementation MDCNonselectableTextView
10611090

1091+
#pragma mark - UITextView
1092+
1093+
- (void)setAttributedText:(NSAttributedString *)attributedText {
1094+
if ([self.attributedText isEqual:attributedText]) {
1095+
return;
1096+
}
1097+
1098+
[super setAttributedText:attributedText];
1099+
[self updateTopInsetAndTextContainerInset:self.textContainerInset];
1100+
}
1101+
1102+
- (void)setFont:(UIFont *)font {
1103+
if ([self.font isEqual:font]) {
1104+
return;
1105+
}
1106+
1107+
[super setFont:font];
1108+
[self updateTopInsetAndTextContainerInset:self.textContainerInset];
1109+
}
1110+
1111+
- (void)setText:(NSString *)text {
1112+
if ([self.text isEqual:text]) {
1113+
return;
1114+
}
1115+
1116+
[super setText:text];
1117+
[self updateTopInsetAndTextContainerInset:self.textContainerInset];
1118+
}
1119+
1120+
- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset {
1121+
[self updateTopInsetAndTextContainerInset:textContainerInset];
1122+
}
1123+
1124+
#pragma mark - UIView
1125+
10621126
// Disabling text selection when selectable is YES, while allowing gestures for inlined links.
10631127
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
10641128
if (UIAccessibilityIsVoiceOverRunning()) {
@@ -1086,4 +1150,28 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
10861150
return link != nil;
10871151
}
10881152

1153+
#pragma mark - Private
1154+
1155+
/**
1156+
* Updates the top text inset of the UITextView to avoid rendering text outside the view
1157+
* boundary. We need to calculate the text margin when we change the text or the text font.
1158+
*
1159+
* @note Our title UILabel already has a top margin, so its text won't exceed the parent view's
1160+
* boundary.
1161+
*
1162+
* @param textContainerInset The target text insets to display. It will update the following text
1163+
* margin: left, right and bottom.
1164+
*/
1165+
- (void)updateTopInsetAndTextContainerInset:(UIEdgeInsets)textContainerInset {
1166+
// To avoid changing the top margin when the view width changes, we only compare the text height
1167+
// for a single line text.
1168+
UIFont *font = self.font;
1169+
CGFloat singleLineTextViewHeight = SingleLineTextViewHeight(self.text, font);
1170+
textContainerInset.top = MAX(0.0f, singleLineTextViewHeight - font.lineHeight);
1171+
if (!UIEdgeInsetsEqualToEdgeInsets(super.textContainerInset, textContainerInset)) {
1172+
// Note that |self setTextContainerInset:| will call this method.
1173+
super.textContainerInset = textContainerInset;
1174+
}
1175+
}
1176+
10891177
@end

components/Dialogs/tests/snapshot/MDCAlertControllerLocalizationTests.m

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#import "MDCAlertController+Testing.h"
2222
#import "MaterialSnapshot.h"
2323

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

8788
#pragma mark - Tests
8889

90+
/** Verifies the top of Thai attributed message with diacritics won't be cut off. */
91+
- (void)testPreferredContentSizeWithThaiAttributedMessage {
92+
// When
93+
self.alertController.attributedMessage =
94+
[[NSAttributedString alloc] initWithString:kThaiTextWithDiacritics];
95+
96+
// Then
97+
[self generateSizedSnapshotAndVerifyForAlert:self.alertController];
98+
}
99+
100+
/** Verifies the top of Thai message with diacritics won't be cut off. */
101+
- (void)testPreferredContentSizeWithThaiMessage {
102+
// When
103+
self.alertController.message = kThaiTextWithDiacritics;
104+
105+
// Then
106+
[self generateSizedSnapshotAndVerifyForAlert:self.alertController];
107+
}
108+
109+
/** Verifies the top of Thai message and title with diacritics won't be cut off. */
110+
- (void)testPreferredContentSizeWithThaiMessageAndTitle {
111+
// When
112+
self.alertController.title = kThaiTextWithDiacritics;
113+
self.alertController.message = kThaiTextWithDiacritics;
114+
115+
// Then
116+
[self generateSizedSnapshotAndVerifyForAlert:self.alertController];
117+
}
118+
89119
- (void)testPreferredContentSizeWithNotoNastaliqUrdu {
90120
// When
91121
self.alertController.title = kTitleUrdu;

0 commit comments

Comments
 (0)