Skip to content

Commit 38c8264

Browse files
committed
fix:(ios) autoFontSize / color / size fixes for html and formattedText
1 parent c9f9c72 commit 38c8264

File tree

2 files changed

+153
-38
lines changed

2 files changed

+153
-38
lines changed

Diff for: src/label-common.ts

+25-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
Span,
1010
Style,
1111
Label as TNLabel,
12-
booleanConverter
12+
booleanConverter,
13+
fontInternalProperty
1314
} from '@nativescript/core';
1415
import { layout } from '@nativescript/core/utils/utils';
1516
import { Label as LabelViewDefinition, LineBreak, TextShadow } from './label';
@@ -48,11 +49,20 @@ export const needFormattedStringComputation = function (
4849
return originalMethod.apply(this, args);
4950
};
5051
};
52+
export const needFontComputation = function (target: any, propertyKey: string | Symbol, descriptor: PropertyDescriptor) {
53+
const originalMethod = descriptor.value;
54+
descriptor.value = function (...args: any[]) {
55+
if (!this._canChangeText) {
56+
this._needFontComputation = true;
57+
return;
58+
}
59+
return originalMethod.apply(this, args);
60+
};
61+
};
5162

5263
@CSSType('HTMLLabel')
5364
export abstract class LabelBase extends TNLabel implements LabelViewDefinition {
5465
@cssProperty maxLines: string | number;
55-
@cssProperty autoFontSize: boolean;
5666
@cssProperty verticalTextAlignment: VerticalTextAlignment;
5767
@cssProperty lineBreak: LineBreak;
5868
@cssProperty linkColor: Color;
@@ -62,11 +72,14 @@ export abstract class LabelBase extends TNLabel implements LabelViewDefinition {
6272
//@ts-ignore
6373
formattedText: FormattedString;
6474

75+
@cssProperty autoFontSize: boolean;
6576
@cssProperty minFontSize: number;
6677
@cssProperty maxFontSize: number;
78+
@cssProperty autoFontSizeStep: number;
6779

6880
_canChangeText = true;
6981
_needFormattedStringComputation = false;
82+
_needFontComputation = false;
7083
public onResumeNativeUpdates(): void {
7184
// {N} suspends properties update on `_suspendNativeUpdates`. So we only need to do this in onResumeNativeUpdates
7285
this._canChangeText = false;
@@ -76,6 +89,10 @@ export abstract class LabelBase extends TNLabel implements LabelViewDefinition {
7689
this._needFormattedStringComputation = false;
7790
this._setNativeText();
7891
}
92+
if (this._needFontComputation) {
93+
this._needFontComputation = false;
94+
this[fontInternalProperty.setNative](this.style.fontInternal);
95+
}
7996
}
8097
}
8198

@@ -124,6 +141,12 @@ export const autoFontSizeProperty = new CssProperty<Style, boolean>({
124141
valueConverter: booleanConverter
125142
});
126143
autoFontSizeProperty.register(Style);
144+
export const autoFontSizeStepProperty = new CssProperty<Style, number>({
145+
name: 'autoFontSizeStep',
146+
cssName: 'auto-font-size-step',
147+
valueConverter: (v) => parseFloat(v)
148+
});
149+
autoFontSizeStepProperty.register(Style);
127150
export const minFontSizeProperty = new CssProperty<Style, number>({
128151
name: 'minFontSize',
129152
cssName: 'min-font-size',

Diff for: src/label.ios.ts

+128-36
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ import {
1818
paddingRightProperty,
1919
paddingTopProperty
2020
} from '@nativescript/core/ui/styling/style-properties';
21-
import { letterSpacingProperty, textDecorationProperty, whiteSpaceProperty } from '@nativescript/core/ui/text-base';
21+
import {
22+
formattedTextProperty,
23+
letterSpacingProperty,
24+
textDecorationProperty,
25+
whiteSpaceProperty
26+
} from '@nativescript/core/ui/text-base';
2227
import { getClosestPropertyValue, lineHeightProperty } from '@nativescript/core/ui/text-base/text-base-common';
2328
import { isNullOrUndefined, isString } from '@nativescript/core/utils/types';
2429
import { iOSNativeHelper, layout } from '@nativescript/core/utils/utils';
@@ -31,6 +36,7 @@ import {
3136
linkColorProperty,
3237
linkUnderlineProperty,
3338
maxLinesProperty,
39+
needFontComputation,
3440
needFormattedStringComputation,
3541
selectableProperty,
3642
textShadowProperty
@@ -41,6 +47,8 @@ export * from './label-common';
4147

4248
const majorVersion = iOSNativeHelper.MajorVersion;
4349

50+
const AttributeOriginalFontSize = 'OriginalFontSize';
51+
4452
enum FixedSize {
4553
NONE = 0,
4654
WIDTH = 1,
@@ -404,6 +412,7 @@ export class Label extends LabelBase {
404412
const height = layout.getMeasureSpecSize(heightMeasureSpec);
405413
const heightMode = layout.getMeasureSpecMode(heightMeasureSpec);
406414
if (this.autoFontSize) {
415+
// this.needsAutoFontSize = true;
407416
this.textViewDidChange(
408417
nativeView,
409418
layout.toDeviceIndependentPixels(width),
@@ -460,7 +469,9 @@ export class Label extends LabelBase {
460469
lineHeight: this.lineHeight,
461470
textAlignment: this.nativeTextViewProtected.textAlignment
462471
},
463-
this
472+
this,
473+
this.autoFontSize,
474+
this.fontSizeRatio
464475
) as NSMutableAttributedString;
465476
let hasLink = false;
466477
result &&
@@ -482,24 +493,30 @@ export class Label extends LabelBase {
482493
if (this.nativeViewProtected) {
483494
this.nativeViewProtected.attributedText = this.attributedString;
484495
}
485-
this._requestLayoutOnTextChanged();
486496
}
487497
updateHTMLString(fontSize?: number) {
488498
// when in collectionView or pager
489499
// if this is done sync (without DTCoreText) while init the cell
490500
// it breaks the UICollectionView :s
491501
if (usingIOSDTCoreText()) {
492502
this._updateHTMLString();
493-
}else {
494-
setTimeout(() => {
495-
this._updateHTMLString();
496-
}, 0);
503+
} else {
504+
// setTimeout(() => {
505+
this._updateHTMLString();
506+
// }, 0);
497507
}
498508
}
499-
[colorProperty.setNative](value: Color | string) {
500-
const color = !value || value instanceof Color ? (value as Color) : new Color(value);
501-
const nativeView = this.nativeTextViewProtected;
502-
nativeView.textColor = color ? color.ios : null;
509+
_setColor(color) {
510+
if (this.nativeTextViewProtected instanceof UIButton) {
511+
this.nativeTextViewProtected.setTitleColorForState(color, 0 /* Normal */);
512+
this.nativeTextViewProtected.titleLabel.textColor = color;
513+
} else {
514+
if (this.formattedText || this.html) {
515+
this._setNativeText();
516+
} else {
517+
this.nativeTextViewProtected.textColor = color;
518+
}
519+
}
503520
}
504521
[linkColorProperty.setNative](value: Color | string) {
505522
const color = !value || value instanceof Color ? (value as Color) : new Color(value);
@@ -539,28 +556,90 @@ export class Label extends LabelBase {
539556
}
540557
@needFormattedStringComputation
541558
[htmlProperty.setNative](value: string) {
542-
// if (!this.style.fontInternal) {
559+
this.fontSizeRatio = 1;
560+
this.needsAutoFontSize = this.autoFontSize;
543561
this.updateHTMLString();
544-
// }
562+
}
563+
@needFormattedStringComputation
564+
[formattedTextProperty.setNative](value: string) {
565+
this.fontSizeRatio = 1;
566+
this.needsAutoFontSize = this.autoFontSize;
567+
super[formattedTextProperty.setNative](value);
545568
}
546569
@needFormattedStringComputation
547570
[letterSpacingProperty.setNative](value: number) {
571+
this.fontSizeRatio = 1;
572+
this.needsAutoFontSize = this.autoFontSize;
548573
super[letterSpacingProperty.setNative](value);
549574
}
550575
@needFormattedStringComputation
551576
[lineHeightProperty.setNative](value: number) {
577+
this.fontSizeRatio = 1;
578+
this.needsAutoFontSize = this.autoFontSize;
552579
super[lineHeightProperty.setNative](value);
553580
}
554-
// @needFormattedStringComputation
555-
// [textAlignmentProperty.setNative](value: number) {
556-
// super[textAlignmentProperty.setNative](value);
557-
// }
581+
@needFormattedStringComputation
582+
[colorProperty.setNative](value: number) {
583+
super[colorProperty.setNative](value);
584+
}
585+
[fontInternalProperty.setNative](value: any) {
586+
const nativeView = this.nativeTextViewProtected;
587+
this.fontSizeRatio = 1;
588+
this.needsAutoFontSize = this.autoFontSize;
589+
const newFont: UIFont = value instanceof Font ? value.getUIFont(nativeView.font) : value;
590+
if (!this.formattedText && !this.html) {
591+
nativeView.font = newFont;
592+
} else if (newFont) {
593+
if (!this._canChangeText) {
594+
this._needFormattedStringComputation = true;
595+
return;
596+
}
597+
this._setNativeText();
598+
}
599+
}
600+
_setSpannablesFontSizeWithRatio(ratio) {
601+
const nativeView = this.nativeTextViewProtected;
602+
const toChange: NSMutableAttributedString =
603+
nativeView.attributedText instanceof NSMutableAttributedString
604+
? nativeView.attributedText
605+
: NSMutableAttributedString.alloc().initWithAttributedString(nativeView.attributedText);
606+
let found = false;
607+
toChange.enumerateAttributeInRangeOptionsUsingBlock(
608+
AttributeOriginalFontSize,
609+
{ location: 0, length: nativeView.attributedText.length },
610+
0,
611+
(value, range: NSRange, stop) => {
612+
if (!value) {
613+
return;
614+
}
615+
toChange.enumerateAttributeInRangeOptionsUsingBlock(
616+
NSFontAttributeName,
617+
range,
618+
0,
619+
(value2: UIFont, range: NSRange, stop) => {
620+
if (value2 && value * ratio !== value2.pointSize) {
621+
const newFont = value2.fontWithSize(Math.round(value * ratio));
622+
if (newFont) {
623+
found = true;
624+
toChange.removeAttributeRange(NSFontAttributeName, range);
625+
toChange.addAttributeValueRange(NSFontAttributeName, newFont, range);
626+
}
627+
}
628+
}
629+
);
630+
}
631+
);
632+
if (found) {
633+
nativeView.attributedText = toChange;
634+
}
635+
}
558636
_setNativeText() {
559637
if (this.html) {
560638
this.updateHTMLString();
561639
} else {
562640
super._setNativeText();
563641
}
642+
this._requestLayoutOnTextChanged();
564643
}
565644
setTextDecorationAndTransform() {
566645
const style = this.style;
@@ -830,8 +909,11 @@ export class Label extends LabelBase {
830909
}
831910
}
832911

912+
fontSizeRatio = 1;
913+
needsAutoFontSize = false;
833914
textViewDidChange(textView: UITextView, width?, height?) {
834-
if (this.autoFontSize) {
915+
if (this.autoFontSize && this.needsAutoFontSize) {
916+
this.needsAutoFontSize = false;
835917
if (
836918
(!textView.attributedText && !textView.text) ||
837919
(width === undefined && height === undefined && CGSizeEqualToSize(textView.bounds.size, CGSizeZero))
@@ -852,8 +934,18 @@ export class Label extends LabelBase {
852934
const fontSize = this.style.fontSize || 17;
853935
let expectFont: UIFont = (this.style.fontInternal || Font.default).getUIFont(UIFont.systemFontOfSize(fontSize));
854936
//first reset the font size
855-
textView.font = expectFont;
856937
let expectSize;
938+
939+
const stepSize = this.autoFontSizeStep || 1;
940+
941+
const updateFontSize = (font) => {
942+
if (this.formattedText || this.html) {
943+
this._setSpannablesFontSizeWithRatio(font.pointSize / fontSize);
944+
} else {
945+
textView.font = font;
946+
}
947+
};
948+
updateFontSize(expectFont);
857949
const size = () => {
858950
if (nbLines === 1) {
859951
expectSize = textView.sizeThatFits(CGSizeMake(Number.MAX_SAFE_INTEGER, fixedHeight));
@@ -867,18 +959,16 @@ export class Label extends LabelBase {
867959
(expectSize.height > fixedHeight || expectSize.width > fixedWidth) &&
868960
expectFont.pointSize > (this.minFontSize || 12)
869961
) {
870-
const newFont = expectFont.fontWithSize(expectFont.pointSize - 1);
871-
textView.font = newFont;
872-
// TODO: find a better way i dont want to have
873-
// to recompute html each time!
874-
if (this.html) {
875-
this.updateHTMLString(newFont.pointSize);
876-
}
962+
const newFont = expectFont.fontWithSize(expectFont.pointSize - stepSize);
963+
updateFontSize(newFont);
877964
size();
878965
if (expectSize.height >= fixedHeight || expectSize.width >= fixedWidth) {
879966
expectFont = newFont;
880967
} else {
881-
textView.font = newFont;
968+
expectFont = newFont;
969+
if (!this.formattedText && !this.html) {
970+
textView.font = newFont;
971+
}
882972
break;
883973
}
884974
}
@@ -887,28 +977,30 @@ export class Label extends LabelBase {
887977
(expectSize.height < fixedHeight || expectSize.width < fixedWidth) &&
888978
expectFont.pointSize < (this.maxFontSize || 200)
889979
) {
890-
const newFont = expectFont.fontWithSize(expectFont.pointSize + 1);
891-
textView.font = newFont;
892-
// TODO: find a better way i dont want to have
893-
// to recompute html each time!
894-
if (this.html) {
895-
this.updateHTMLString(newFont.pointSize);
896-
}
980+
const newFont = expectFont.fontWithSize(expectFont.pointSize + stepSize);
981+
updateFontSize(newFont);
982+
897983
size();
898984

899985
if (expectSize.height <= fixedHeight || expectSize.width <= fixedWidth) {
900986
expectFont = newFont;
901987
} else {
902-
textView.font = newFont;
988+
expectFont = newFont;
989+
if (!this.formattedText && !this.html) {
990+
textView.font = newFont;
991+
}
903992
break;
904993
}
905994
}
906995
}
996+
this.fontSizeRatio = expectFont.pointSize / fontSize;
907997
this.updateTextContainerInset();
908998
}
909999
}
9101000
[autoFontSizeProperty.setNative](value: boolean) {
911-
if (value && (this.text || this.html)) {
1001+
if (value && (this.text || this.html || this.formattedText)) {
1002+
this.fontSizeRatio = 1;
1003+
this.needsAutoFontSize = true;
9121004
this.textViewDidChange(this.nativeTextViewProtected);
9131005
} else {
9141006
this[fontInternalProperty.setNative](this.style.fontInternal);

0 commit comments

Comments
 (0)