mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
TKUnit default message change to empty string isSet method is now instance method of Property classes fix detaching from parent bindingContext - were using oldParent.parent instead of parent editable-text-base.android - onTextChanged implementation commented. Does nothing. frame - onCreateView wrapped in try/catch and shows label with exception message if any text-base.android - should support reset of nativeView. TransformationMethod won’t be set if TextField is secure Change some types to their string couterparts TextField.android won’t support multilines anymore in order to work as iOS In android when page is removed from native backstack we won’t call tearDownUI again a second time
329 lines
12 KiB
TypeScript
329 lines
12 KiB
TypeScript
import { Font } from "../styling/font";
|
|
import {
|
|
TextBaseCommon, textProperty, formattedTextProperty, textAlignmentProperty, textDecorationProperty,
|
|
textTransformProperty, letterSpacingProperty, colorProperty, fontInternalProperty, FormattedString,
|
|
TextDecoration, TextAlignment, TextTransform, Span, Color, isBold
|
|
} from "./text-base-common";
|
|
|
|
export * from "./text-base-common";
|
|
|
|
export class TextBase extends TextBaseCommon {
|
|
|
|
public nativeView: UITextField | UITextView | UILabel | UIButton;
|
|
|
|
get [textProperty.native](): string {
|
|
return '';
|
|
}
|
|
set [textProperty.native](value: string) {
|
|
if (this.formattedText) {
|
|
return;
|
|
}
|
|
|
|
this._setNativeText();
|
|
this._requestLayoutOnTextChanged();
|
|
}
|
|
|
|
get [formattedTextProperty.native](): FormattedString {
|
|
return null;
|
|
}
|
|
set [formattedTextProperty.native](value: FormattedString) {
|
|
this._setNativeText();
|
|
textProperty.nativeValueChange(this, !value ? '' : value.toString());
|
|
this._requestLayoutOnTextChanged();
|
|
}
|
|
|
|
get [colorProperty.native](): UIColor {
|
|
let nativeView = this.nativeView;
|
|
if (nativeView instanceof UIButton) {
|
|
return nativeView.titleColorForState(UIControlState.Normal);
|
|
} else {
|
|
return nativeView.textColor;
|
|
}
|
|
}
|
|
set [colorProperty.native](value: Color | UIColor) {
|
|
const color = value instanceof Color ? value.ios : value;
|
|
if (!this.formattedText) {
|
|
let nativeView = this.nativeView;
|
|
if (nativeView instanceof UIButton) {
|
|
nativeView.setTitleColorForState(color, UIControlState.Normal);
|
|
} else {
|
|
nativeView.textColor = color;
|
|
}
|
|
}
|
|
}
|
|
|
|
get [fontInternalProperty.native](): UIFont {
|
|
let nativeView = this.nativeView;
|
|
nativeView = nativeView instanceof UIButton ? nativeView.titleLabel : nativeView;
|
|
return nativeView.font;
|
|
}
|
|
set [fontInternalProperty.native](value: Font | UIFont) {
|
|
if (!this.formattedText) {
|
|
let nativeView = this.nativeView;
|
|
nativeView = nativeView instanceof UIButton ? nativeView.titleLabel : nativeView;
|
|
let font = value instanceof Font ? value.getUIFont(nativeView.font) : value;
|
|
nativeView.font = font;
|
|
}
|
|
}
|
|
|
|
get [textAlignmentProperty.native](): TextAlignment {
|
|
let nativeView = this.nativeView;
|
|
nativeView = nativeView instanceof UIButton ? nativeView.titleLabel : nativeView;
|
|
switch (nativeView.textAlignment) {
|
|
case NSTextAlignment.Natural:
|
|
case NSTextAlignment.Left:
|
|
return "left";
|
|
case NSTextAlignment.Center:
|
|
return "center";
|
|
case NSTextAlignment.Right:
|
|
return "right";
|
|
default:
|
|
throw new Error(`Unsupported NSTextAlignment: ${nativeView.textAlignment}. Currently supported values are NSTextAlignment.Left, NSTextAlignment.Center, and NSTextAlignment.Right.`);
|
|
}
|
|
}
|
|
set [textAlignmentProperty.native](value: TextAlignment) {
|
|
let nativeView = <UITextField | UITextView | UILabel>this.nativeView;
|
|
switch (value) {
|
|
case "left":
|
|
nativeView.textAlignment = NSTextAlignment.Left;
|
|
break;
|
|
|
|
case "center":
|
|
nativeView.textAlignment = NSTextAlignment.Center;
|
|
break;
|
|
|
|
case "right":
|
|
nativeView.textAlignment = NSTextAlignment.Right;
|
|
break;
|
|
|
|
default:
|
|
throw new Error(`Invalid text alignment value: ${value}. Valid values are: 'left', 'center', 'right'.`);
|
|
}
|
|
}
|
|
|
|
get [textDecorationProperty.native](): TextDecoration {
|
|
return "none";
|
|
}
|
|
set [textDecorationProperty.native](value: TextDecoration) {
|
|
this._setNativeText();
|
|
}
|
|
|
|
get [textTransformProperty.native](): TextTransform {
|
|
return "none";
|
|
}
|
|
set [textTransformProperty.native](value: TextTransform) {
|
|
this._setNativeText();
|
|
}
|
|
|
|
get [letterSpacingProperty.native](): number {
|
|
return 0;
|
|
}
|
|
set [letterSpacingProperty.native](value: number) {
|
|
this._setNativeText();
|
|
}
|
|
|
|
_setNativeText() {
|
|
if (this.formattedText) {
|
|
this.setFormattedTextDecorationAndTransform();
|
|
} else {
|
|
this.setTextDecorationAndTransform();
|
|
}
|
|
}
|
|
|
|
setFormattedTextDecorationAndTransform() {
|
|
const attrText = this.createNSMutableAttributedString(this.formattedText);
|
|
// TODO: letterSpacing should be applied per Span.
|
|
if (this.letterSpacing !== 0) {
|
|
attrText.addAttributeValueRange(NSKernAttributeName, this.letterSpacing * this.nativeView.font.pointSize, { location: 0, length: attrText.length });
|
|
}
|
|
|
|
if (this.nativeView instanceof UIButton) {
|
|
this.nativeView.setAttributedTitleForState(attrText, UIControlState.Normal);
|
|
}
|
|
else {
|
|
this.nativeView.attributedText = attrText;
|
|
}
|
|
}
|
|
|
|
setTextDecorationAndTransform() {
|
|
const style = this.style;
|
|
|
|
let dict = new Map<string, any>();
|
|
switch (style.textDecoration) {
|
|
case "none":
|
|
break;
|
|
case "underline":
|
|
dict.set(NSUnderlineStyleAttributeName, NSUnderlineStyle.StyleSingle);
|
|
break;
|
|
case "line-through":
|
|
dict.set(NSStrikethroughStyleAttributeName, NSUnderlineStyle.StyleSingle);
|
|
break;
|
|
case "underline line-through":
|
|
dict.set(NSUnderlineStyleAttributeName, NSUnderlineStyle.StyleSingle);
|
|
dict.set(NSStrikethroughStyleAttributeName, NSUnderlineStyle.StyleSingle);
|
|
break;
|
|
default:
|
|
throw new Error(`Invalid text decoration value: ${style.textDecoration}. Valid values are: 'none', 'underline', 'line-through', 'underline line-through'.`);
|
|
}
|
|
|
|
if (style.letterSpacing !== 0) {
|
|
dict.set(NSKernAttributeName, style.letterSpacing * this.nativeView.font.pointSize);
|
|
}
|
|
|
|
if (style.color) {
|
|
dict.set(NSForegroundColorAttributeName, style.color.ios);
|
|
}
|
|
|
|
const text = this.text;
|
|
const string = (text === undefined || text === null) ? '' : text.toString();
|
|
const source = getTransformedText(string, this.textTransform);
|
|
const isTextView = this.nativeView instanceof UITextView;
|
|
if (dict.size > 0 || isTextView) {
|
|
if (isTextView) {
|
|
// UITextView's font seems to change inside.
|
|
dict.set(NSFontAttributeName, this.nativeView.font);
|
|
}
|
|
let result = NSMutableAttributedString.alloc().initWithString(source);
|
|
result.setAttributesRange(<any>dict, { location: 0, length: source.length });
|
|
if (this.nativeView instanceof UIButton) {
|
|
this.nativeView.setAttributedTitleForState(result, UIControlState.Normal);
|
|
} else {
|
|
this.nativeView.attributedText = result;
|
|
}
|
|
} else {
|
|
if (this.nativeView instanceof UIButton) {
|
|
// Clear attributedText or title won't be affected.
|
|
this.nativeView.setAttributedTitleForState(null, UIControlState.Normal);
|
|
this.nativeView.setTitleForState(source, UIControlState.Normal);
|
|
} else {
|
|
// Clear attributedText or text won't be affected.
|
|
this.nativeView.attributedText = undefined;
|
|
this.nativeView.text = source;
|
|
}
|
|
}
|
|
}
|
|
|
|
createNSMutableAttributedString(formattedString: FormattedString): NSMutableAttributedString {
|
|
let mas = NSMutableAttributedString.alloc().init();
|
|
if (formattedString) {
|
|
for (let i = 0, spanStart = 0, length = formattedString.spans.length; i < length; i++) {
|
|
const span = formattedString.spans.getItem(i);
|
|
const text = span.text;
|
|
const textTransform = (<TextBase>formattedString.parent).textTransform;
|
|
let spanText = (text === null || text === undefined) ? '' : text.toString();
|
|
if (textTransform !== "none") {
|
|
spanText = getTransformedText(spanText, textTransform);
|
|
}
|
|
|
|
const nsAttributedString = this.createMutableStringForSpan(span, spanText);
|
|
mas.insertAttributedStringAtIndex(nsAttributedString, spanStart);
|
|
spanStart += spanText.length;
|
|
}
|
|
}
|
|
return mas;
|
|
}
|
|
|
|
createMutableStringForSpan(span: Span, text: string): NSMutableAttributedString {
|
|
const viewFont = this.nativeView.font;
|
|
let attrDict = <{ key: string, value: any }>{};
|
|
const style = span.style;
|
|
const bold = isBold(style.fontWeight);
|
|
const italic = style.fontStyle === "italic";
|
|
|
|
let fontFamily = span.fontFamily;
|
|
let fontSize = span.fontSize;
|
|
|
|
if (bold || italic || fontFamily || fontSize) {
|
|
if (!fontSize) {
|
|
fontSize = viewFont.pointSize;
|
|
}
|
|
|
|
if (!fontFamily) {
|
|
fontFamily = viewFont.fontName;
|
|
}
|
|
|
|
let font;
|
|
|
|
let fontDescriptor: UIFontDescriptor = viewFont.fontDescriptor;
|
|
if (fontFamily) {
|
|
fontDescriptor = fontDescriptor.fontDescriptorWithFamily(fontFamily);
|
|
}
|
|
|
|
let symbolicTraits;
|
|
if (bold) {
|
|
symbolicTraits |= UIFontDescriptorSymbolicTraits.TraitBold;
|
|
}
|
|
|
|
if (italic) {
|
|
symbolicTraits |= UIFontDescriptorSymbolicTraits.TraitItalic;
|
|
}
|
|
|
|
if (symbolicTraits) {
|
|
font = UIFont.fontWithDescriptorSize(fontDescriptor.fontDescriptorWithSymbolicTraits(symbolicTraits), fontSize);
|
|
} else {
|
|
font = UIFont.fontWithDescriptorSize(fontDescriptor, fontSize);
|
|
}
|
|
|
|
attrDict[NSFontAttributeName] = font;
|
|
}
|
|
|
|
const color = span.color;
|
|
if (color) {
|
|
attrDict[NSForegroundColorAttributeName] = color.ios;
|
|
}
|
|
|
|
// We don't use isSet function here because defaultValue for backgroundColor is null.
|
|
const backgroundColor = style.backgroundColor
|
|
|| (<FormattedString>span.parent).backgroundColor
|
|
|| (<TextBase>(<FormattedString>span.parent).parent).backgroundColor;
|
|
if (backgroundColor) {
|
|
attrDict[NSBackgroundColorAttributeName] = backgroundColor.ios;
|
|
}
|
|
|
|
let valueSource: typeof style;
|
|
if (textDecorationProperty.isSet(style)) {
|
|
valueSource = style;
|
|
} else if (textDecorationProperty.isSet(span.parent.style)) {
|
|
// span.parent is FormattedString
|
|
valueSource = span.parent.style;
|
|
} else if (textDecorationProperty.isSet(span.parent.parent.style)) {
|
|
// span.parent.parent is TextBase
|
|
valueSource = span.parent.parent.style;
|
|
}
|
|
|
|
if (valueSource) {
|
|
const textDecorations = valueSource.textDecoration;
|
|
const underline = textDecorations.indexOf('underline') !== -1;
|
|
if (underline) {
|
|
attrDict[NSUnderlineStyleAttributeName] = underline;
|
|
}
|
|
|
|
const strikethrough = textDecorations.indexOf('line-through') !== -1;
|
|
if (strikethrough) {
|
|
attrDict[NSStrikethroughStyleAttributeName] = strikethrough;
|
|
}
|
|
}
|
|
|
|
return NSMutableAttributedString.alloc().initWithStringAttributes(text, <any>attrDict);
|
|
}
|
|
}
|
|
|
|
export function getTransformedText(text: string, textTransform: TextTransform): string {
|
|
switch (textTransform) {
|
|
case "none":
|
|
return text;
|
|
case "uppercase":
|
|
return NSStringFromNSAttributedString(text).uppercaseString;
|
|
case "lowercase":
|
|
return NSStringFromNSAttributedString(text).lowercaseString;
|
|
case "capitalize":
|
|
return NSStringFromNSAttributedString(text).capitalizedString;
|
|
default:
|
|
throw new Error(`Invalid text transform value: ${textTransform}. Valid values are: 'none', 'capitalize', 'uppercase', 'lowercase'.`);
|
|
}
|
|
}
|
|
|
|
function NSStringFromNSAttributedString(source: NSAttributedString | string): NSString {
|
|
return NSString.stringWithString(source instanceof NSAttributedString && source.string || <string>source);
|
|
}
|