|
36 | 36 |
|
37 | 37 | static const CGFloat MDCDialogMessageOpacity = 0.54f;
|
38 | 38 |
|
| 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 | + |
39 | 68 | @interface MDCNonselectableTextView : UITextView
|
40 | 69 | @end
|
41 | 70 |
|
@@ -1059,6 +1088,41 @@ - (void)updateFonts {
|
1059 | 1088 |
|
1060 | 1089 | @implementation MDCNonselectableTextView
|
1061 | 1090 |
|
| 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 | + |
1062 | 1126 | // Disabling text selection when selectable is YES, while allowing gestures for inlined links.
|
1063 | 1127 | - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
|
1064 | 1128 | if (UIAccessibilityIsVoiceOverRunning()) {
|
@@ -1086,4 +1150,28 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
|
1086 | 1150 | return link != nil;
|
1087 | 1151 | }
|
1088 | 1152 |
|
| 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 | + |
1089 | 1177 | @end
|
0 commit comments