From 5aa12d9d985b54215980d6dbdfd619add7a128d9 Mon Sep 17 00:00:00 2001 From: wwwcg Date: Mon, 17 Jun 2024 17:57:15 +0800 Subject: [PATCH] feat(ios): add paragraph styles support for TextInput TextInput Current support: 1.lineHeight 2.lineSpacing 3.lineHeightMultiple --- .../src/components/TextInput/index.jsx | 5 + .../src/components/demos/demo-textarea.vue | 5 + .../component/textinput/HippyShadowTextView.h | 8 ++ .../textinput/HippyShadowTextView.mm | 41 ++++++-- .../component/textinput/HippyTextField.h | 7 ++ .../component/textinput/HippyTextField.m | 19 ++++ .../component/textinput/HippyTextView.h | 8 ++ .../component/textinput/HippyTextView.mm | 99 ++++++++++++++++++- .../textinput/HippyTextViewManager.mm | 6 ++ 9 files changed, 187 insertions(+), 11 deletions(-) diff --git a/driver/js/examples/hippy-react-demo/src/components/TextInput/index.jsx b/driver/js/examples/hippy-react-demo/src/components/TextInput/index.jsx index 69923279f03..e5ec555a782 100644 --- a/driver/js/examples/hippy-react-demo/src/components/TextInput/index.jsx +++ b/driver/js/examples/hippy-react-demo/src/components/TextInput/index.jsx @@ -20,7 +20,12 @@ const styles = StyleSheet.create({ fontSize: 16, color: '#242424', height: 30, + // you can use lineHeight/lineSpacing/lineHeightMultiple + // to control the space between lines in multi-line input.(iOS only for now) + // for example: lineHeight: 30, + // lineSpacing: 50, + // lineHeightMultiple: 1.5, }, input_style_block: { height: 100, diff --git a/driver/js/examples/hippy-vue-demo/src/components/demos/demo-textarea.vue b/driver/js/examples/hippy-vue-demo/src/components/demos/demo-textarea.vue index 646e6755830..b5cdf22ff59 100644 --- a/driver/js/examples/hippy-vue-demo/src/components/demos/demo-textarea.vue +++ b/driver/js/examples/hippy-vue-demo/src/components/demos/demo-textarea.vue @@ -94,6 +94,11 @@ export default { underline-color-android: #40b883; placeholder-text-color: #666; align-self: center; + /* you can use line-height/line-spacing/line-height-multiple */ + /* to control the space between lines in multi-line input. (iOS only for now) */ + line-height: 30; + /*line-spacing: 20;*/ + /*line-height-multiple: 1.5;*/ } #demo-textarea .output { diff --git a/renderer/native/ios/renderer/component/textinput/HippyShadowTextView.h b/renderer/native/ios/renderer/component/textinput/HippyShadowTextView.h index 730a0748edb..412fc6bb2d6 100644 --- a/renderer/native/ios/renderer/component/textinput/HippyShadowTextView.h +++ b/renderer/native/ios/renderer/component/textinput/HippyShadowTextView.h @@ -28,4 +28,12 @@ @property (nonatomic, copy) NSString *placeholder; @property (nonatomic, strong) UIFont *font; + +/// ParagraphStyles - lineHeight +@property (nonatomic, strong) NSNumber *lineHeight; +/// ParagraphStyles - lineSpacing +@property (nonatomic, strong) NSNumber *lineSpacing; +/// ParagraphStyles - lineHeightMultiple +@property (nonatomic, strong) NSNumber *lineHeightMultiple; + @end diff --git a/renderer/native/ios/renderer/component/textinput/HippyShadowTextView.mm b/renderer/native/ios/renderer/component/textinput/HippyShadowTextView.mm index f2e63f35691..ab14107a402 100644 --- a/renderer/native/ios/renderer/component/textinput/HippyShadowTextView.mm +++ b/renderer/native/ios/renderer/component/textinput/HippyShadowTextView.mm @@ -23,13 +23,18 @@ #import "HippyUtils.h" #import "HippyShadowTextView.h" #import "HippyShadowView+Internal.h" - #include "dom/dom_manager.h" #include "dom/dom_node.h" #include "dom/layout_node.h" +/// Default font size of TextView +/// Note that in `HippyFont` it is defined as 14, +/// For the sake of compatibility, keep it the way it is. +static const CGFloat defaultFontSize = 16.0; + @interface HippyShadowTextView () +/// Cached text attributes @property (nonatomic, strong) NSDictionary *dicAttributes; @end @@ -40,15 +45,35 @@ @interface HippyShadowTextView () hippy::LayoutMeasureMode heightMeasureMode, void *layoutContext) { hippy::LayoutSize result; if (weakShadowText) { - HippyShadowTextView *strongShadowText = weakShadowText; - NSString *text = strongShadowText.text ?: strongShadowText.placeholder; - if (nil == strongShadowText.dicAttributes) { - if (strongShadowText.font == nil) { - strongShadowText.font = [UIFont systemFontOfSize:16]; + HippyShadowTextView *shadowText = weakShadowText; + NSString *text = shadowText.text ?: shadowText.placeholder; + if (nil == shadowText.dicAttributes) { + if (shadowText.font == nil) { + + shadowText.font = [UIFont systemFontOfSize:defaultFontSize]; + } + NSDictionary *attrs = nil; + if ((id)shadowText.lineHeight != nil || + (id)shadowText.lineSpacing != nil || + (id)shadowText.lineHeightMultiple != nil) { + // Add paragraphStyle + NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; + if ((id)shadowText.lineHeight != nil) { + paragraphStyle.minimumLineHeight = [shadowText.lineHeight doubleValue]; + paragraphStyle.maximumLineHeight = [shadowText.lineHeight doubleValue]; + } else if ((id)shadowText.lineSpacing != nil) { + paragraphStyle.lineSpacing = [shadowText.lineSpacing doubleValue]; + } else if ((id)shadowText.lineHeightMultiple != nil) { + paragraphStyle.lineHeightMultiple = [shadowText.lineHeightMultiple doubleValue]; + } + attrs = @{ NSFontAttributeName: shadowText.font, + NSParagraphStyleAttributeName : paragraphStyle }; + } else { + attrs = @{ NSFontAttributeName: shadowText.font }; } - strongShadowText.dicAttributes = @ { NSFontAttributeName: strongShadowText.font }; + shadowText.dicAttributes = attrs; } - CGSize computedSize = [text sizeWithAttributes:strongShadowText.dicAttributes]; + CGSize computedSize = [text sizeWithAttributes:shadowText.dicAttributes]; result.width = ceil(computedSize.width); result.height = ceil(computedSize.height); } diff --git a/renderer/native/ios/renderer/component/textinput/HippyTextField.h b/renderer/native/ios/renderer/component/textinput/HippyTextField.h index 790ebc5cc32..0b61e7c9f82 100644 --- a/renderer/native/ios/renderer/component/textinput/HippyTextField.h +++ b/renderer/native/ios/renderer/component/textinput/HippyTextField.h @@ -65,4 +65,11 @@ @property (nonatomic, copy) NSString *text; @property (nonatomic, strong) UIColor *textColor; +/// ParagraphStyles - lineHeight +@property (nonatomic, strong) NSNumber *lineHeight; +/// ParagraphStyles - lineSpacing +@property (nonatomic, strong) NSNumber *lineSpacing; +/// ParagraphStyles - lineHeightMultiple +@property (nonatomic, strong) NSNumber *lineHeightMultiple; + @end diff --git a/renderer/native/ios/renderer/component/textinput/HippyTextField.m b/renderer/native/ios/renderer/component/textinput/HippyTextField.m index 0ea33f045c2..10137be9b6d 100644 --- a/renderer/native/ios/renderer/component/textinput/HippyTextField.m +++ b/renderer/native/ios/renderer/component/textinput/HippyTextField.m @@ -119,6 +119,10 @@ @implementation HippyTextField { HippyUITextField *_textView; } +@dynamic lineHeight; +@dynamic lineSpacing; +@dynamic lineHeightMultiple; + - (void)keyboardWillShow:(NSNotification *)aNotification { [super keyboardWillShow:aNotification]; NSDictionary *userInfo = [aNotification userInfo]; @@ -427,4 +431,19 @@ - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRang return YES; } + +#pragma mark - LineHeight Related + +- (void)setLineHeight:(NSNumber *)lineHeight { + // LineHeight does not take effect on single-line input. +} + +- (void)setLineSpacing:(NSNumber *)lineSpacing { + // LineSpacing does not take effect on single-line input. +} + +- (void)setLineHeightMultiple:(NSNumber *)lineHeightMultiple { + // LineHeightMultiple does not take effect on single-line input. +} + @end diff --git a/renderer/native/ios/renderer/component/textinput/HippyTextView.h b/renderer/native/ios/renderer/component/textinput/HippyTextView.h index 1d6c9c48521..2a48201b8e8 100644 --- a/renderer/native/ios/renderer/component/textinput/HippyTextView.h +++ b/renderer/native/ios/renderer/component/textinput/HippyTextView.h @@ -62,6 +62,14 @@ @property (nonatomic, copy) NSString *value; @property (nonatomic, strong) NSString *defaultValue; @property (nonatomic, strong) UIColor *textColor; + +/// ParagraphStyles - lineHeight +@property (nonatomic, strong) NSNumber *lineHeight; +/// ParagraphStyles - lineSpacing +@property (nonatomic, strong) NSNumber *lineSpacing; +/// ParagraphStyles - lineHeightMultiple +@property (nonatomic, strong) NSNumber *lineHeightMultiple; + @property (nonatomic, copy) HippyDirectEventBlock onChangeText; @property (nonatomic, copy) HippyDirectEventBlock onBlur; @property (nonatomic, copy) HippyDirectEventBlock onFocus; diff --git a/renderer/native/ios/renderer/component/textinput/HippyTextView.mm b/renderer/native/ios/renderer/component/textinput/HippyTextView.mm index 13ea95249cb..eb13261d753 100644 --- a/renderer/native/ios/renderer/component/textinput/HippyTextView.mm +++ b/renderer/native/ios/renderer/component/textinput/HippyTextView.mm @@ -73,6 +73,11 @@ - (void)setCaretColor:(UIColor*)color{ @end @interface HippyTextView () + +/// ParagraphStyle for TextView and PlaceholderView, +/// used for lineHeight config and etc. +@property (nonatomic, strong) NSMutableParagraphStyle *paragraphStyle; + @end @implementation HippyTextView { @@ -95,6 +100,10 @@ @implementation HippyTextView { BOOL _viewDidCompleteInitialLayout; } +@dynamic lineHeight; +@dynamic lineSpacing; +@dynamic lineHeightMultiple; + #pragma mark - Keyboard Events - (void)keyboardWillShow:(NSNotification *)aNotification { @@ -303,11 +312,19 @@ - (void)updatePlaceholder { _placeholderView.scrollEnabled = NO; _placeholderView.editable = NO; _placeholderView.scrollsToTop = NO; - _placeholderView.attributedText = [[NSAttributedString alloc] initWithString:_placeholder attributes:@{ + NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:_placeholder + attributes:@{ NSFontAttributeName: (_textView.font ? _textView.font : [self defaultPlaceholderFont]), - NSForegroundColorAttributeName: _placeholderTextColor + NSForegroundColorAttributeName: _placeholderTextColor }]; + if (self.paragraphStyle) { + // Apply paragraph style to the entire string + [attrStr addAttribute:NSParagraphStyleAttributeName + value:self.paragraphStyle + range:NSMakeRange(0, attrStr.length)]; + } _placeholderView.textAlignment = _textView.textAlignment; + _placeholderView.attributedText = attrStr; [self insertSubview:_placeholderView belowSubview:_textView]; [self updatePlaceholderVisibility]; @@ -323,6 +340,40 @@ - (void)setFont:(UIFont *)font { [self updatePlaceholder]; } +- (void)setLineHeight:(NSNumber *)lineHeight { + // create paragraphStyle, update placeholder and textView + if (!self.paragraphStyle) { + self.paragraphStyle = [[NSMutableParagraphStyle alloc] init]; + } + self.paragraphStyle.minimumLineHeight = [lineHeight doubleValue]; + self.paragraphStyle.maximumLineHeight = [lineHeight doubleValue]; + + [self updateParagraphAndFontStyleForTextView:_textView]; + [self updatePlaceholder]; +} + +- (void)setLineSpacing:(NSNumber *)lineSpacing { + // create paragraphStyle, update placeholder and textView + if (!self.paragraphStyle) { + self.paragraphStyle = [[NSMutableParagraphStyle alloc] init]; + } + self.paragraphStyle.lineSpacing = [lineSpacing doubleValue]; + + [self updateParagraphAndFontStyleForTextView:_textView]; + [self updatePlaceholder]; +} + +- (void)setLineHeightMultiple:(NSNumber *)lineHeightMultiple { + // create paragraphStyle, update placeholder and textView + if (!self.paragraphStyle) { + self.paragraphStyle = [[NSMutableParagraphStyle alloc] init]; + } + self.paragraphStyle.lineHeightMultiple = [lineHeightMultiple doubleValue]; + + [self updateParagraphAndFontStyleForTextView:_textView]; + [self updatePlaceholder]; +} + - (void)setPlaceholder:(NSString *)placeholder { _placeholder = placeholder; [self updatePlaceholder]; @@ -416,7 +467,8 @@ - (void)setText:(NSString *)text { NSInteger oldTextLength = _textView.text.length; _predictedText = text; - _textView.text = text; + // Use `attribuedText` instead of `text` to show paragraphStyle + _textView.attributedText = [self attributedTextAfterApplyingParagraphStyle:text]; [self textViewDidChange:_textView]; if (selection.empty) { // maintain cursor position relative to the end of the old text @@ -473,6 +525,8 @@ - (void)textViewDidBeginEditing:(__unused UITextView *)textView { _textView.text = @""; [self updatePlaceholderVisibility]; } + // update typingAttributes + [self updateTypingAttributes]; } static BOOL findMismatch(NSString *first, NSString *second, NSRange *firstRange, NSRange *secondRange) { @@ -740,4 +794,43 @@ - (void)setTextViewKeyboardAppearance { } } +#pragma mark - ParagraphStyle Related + +- (void)updateTypingAttributes { + if (self.paragraphStyle) { + // Set typingAttributes if needed + NSMutableDictionary *attrs = [NSMutableDictionary dictionary]; + attrs[NSParagraphStyleAttributeName] = self.paragraphStyle; + if (_textView.font) { + attrs[NSFontAttributeName] = _textView.font; + } + _textView.typingAttributes = attrs; + } +} + +- (NSAttributedString *)attributedTextAfterApplyingParagraphStyle:(NSString *)text { + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:text]; + + if (self.paragraphStyle) { + // add paragraph style to the entire string + [attributedString addAttribute:NSParagraphStyleAttributeName + value:self.paragraphStyle + range:NSMakeRange(0, attributedString.length)]; + } + if (_textView.font) { + // add font style to the entire string + [attributedString addAttribute:NSFontAttributeName + value:_textView.font + range:NSMakeRange(0, attributedString.length)]; + } + return attributedString; +} + +- (void)updateParagraphAndFontStyleForTextView:(UITextView *)textView { + if (textView.text.length == 0) { + return; + } + textView.attributedText = [self attributedTextAfterApplyingParagraphStyle:textView.text]; +} + @end diff --git a/renderer/native/ios/renderer/component/textinput/HippyTextViewManager.mm b/renderer/native/ios/renderer/component/textinput/HippyTextViewManager.mm index 56f21a09f1f..2b7892b1fad 100644 --- a/renderer/native/ios/renderer/component/textinput/HippyTextViewManager.mm +++ b/renderer/native/ios/renderer/component/textinput/HippyTextViewManager.mm @@ -146,7 +146,13 @@ - (HippyShadowView *)shadowView { HIPPY_EXPORT_SHADOW_PROPERTY(text, NSString) HIPPY_EXPORT_SHADOW_PROPERTY(placeholder, NSString) +HIPPY_EXPORT_SHADOW_PROPERTY(lineHeight, NSNumber) +HIPPY_EXPORT_SHADOW_PROPERTY(lineSpacing, NSNumber) +HIPPY_EXPORT_SHADOW_PROPERTY(lineHeightMultiple, NSNumber) +HIPPY_EXPORT_VIEW_PROPERTY(lineHeight, NSNumber) +HIPPY_EXPORT_VIEW_PROPERTY(lineSpacing, NSNumber) +HIPPY_EXPORT_VIEW_PROPERTY(lineHeightMultiple, NSNumber) HIPPY_REMAP_VIEW_PROPERTY(autoCapitalize, textView.autocapitalizationType, UITextAutocapitalizationType) HIPPY_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL) HIPPY_EXPORT_VIEW_PROPERTY(blurOnSubmit, BOOL)