Skip to content

Commit 9895c56

Browse files
committed
fix(ios): dont use DTCoreText anymore
many more bug fixes
1 parent e4bbcb5 commit 9895c56

File tree

2 files changed

+127
-151
lines changed

2 files changed

+127
-151
lines changed

Diff for: plugin/platforms/ios/Podfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
pod 'DTCoreText'
1+
# pod 'DTCoreText'

Diff for: src/label.ios.ts

+126-150
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
1-
import { fontInternalProperty, Length, paddingBottomProperty, paddingLeftProperty, paddingRightProperty, paddingTopProperty, View } from 'tns-core-modules/ui/page/page';
1+
import { fontInternalProperty, Length, paddingBottomProperty, paddingLeftProperty, paddingRightProperty, paddingTopProperty, View, colorProperty, Color } from 'tns-core-modules/ui/page/page';
22
import { Font } from 'tns-core-modules/ui/styling/font';
3-
import { borderBottomWidthProperty, borderLeftWidthProperty, borderRightWidthProperty, borderTopWidthProperty, TextTransform, WhiteSpace, whiteSpaceProperty } from 'tns-core-modules/ui/text-base/text-base';
3+
import {
4+
borderBottomWidthProperty,
5+
borderLeftWidthProperty,
6+
borderRightWidthProperty,
7+
borderTopWidthProperty,
8+
TextTransform,
9+
WhiteSpace,
10+
whiteSpaceProperty,
11+
formattedTextProperty,
12+
FormattedString
13+
} from 'tns-core-modules/ui/text-base/text-base';
414
import { isString } from 'tns-core-modules/utils/types';
515
import { layout } from 'tns-core-modules/utils/utils';
616
import { TextShadow, VerticalTextAlignment, verticalTextAlignmentProperty } from './label';
@@ -81,15 +91,14 @@ export class Label extends LabelBase {
8191
private _observer: NSObject;
8292
nativeViewProtected: UITextView;
8393
nativeTextViewProtected: UITextView;
84-
static DTCORETEXT_INIT = false;
94+
// static DTCORETEXT_INIT = false;
8595
constructor() {
8696
super();
87-
if (!Label.DTCORETEXT_INIT) {
88-
Label.DTCORETEXT_INIT = true;
89-
DTCoreTextFontDescriptor.asyncPreloadFontLookupTable();
90-
}
97+
// if (!Label.DTCORETEXT_INIT) {
98+
// Label.DTCORETEXT_INIT = true;
99+
// DTCoreTextFontDescriptor.asyncPreloadFontLookupTable();
100+
// }
91101
}
92-
93102
public createNativeView() {
94103
const view = UITextView.new();
95104
if (!view.font) {
@@ -130,7 +139,16 @@ export class Label extends LabelBase {
130139
this._observer = null;
131140
}
132141
}
133-
142+
computeTextHeight(size: CGSize) {
143+
const tv = this.nativeTextViewProtected;
144+
const font = this.nativeViewProtected.font;
145+
const text = this.formattedText || this.html ? tv.attributedText : tv.text;
146+
if (text instanceof NSAttributedString) {
147+
const rect = text.boundingRectWithSizeOptionsContext(size, NSStringDrawingOptions.UsesLineFragmentOrigin, null);
148+
return rect.size.height;
149+
}
150+
return NSString.stringWithString(text).sizeWithFontConstrainedToSizeLineBreakMode(font, size, tv.textContainer.lineBreakMode).height;
151+
}
134152
updateVerticalAlignment() {
135153
const tv = this.nativeTextViewProtected;
136154
const inset = this.nativeViewProtected.textContainerInset;
@@ -148,7 +166,7 @@ export class Label extends LabelBase {
148166

149167
case 'middle':
150168
case 'center': {
151-
const height = tv.sizeThatFits(CGSizeMake(tv.bounds.size.width, 10000)).height;
169+
const height = this.computeTextHeight(CGSizeMake(tv.bounds.size.width, 10000));
152170
let topCorrect = (tv.bounds.size.height - height * tv.zoomScale) / 2.0;
153171
topCorrect = topCorrect < 0.0 ? 0.0 : topCorrect;
154172
// tv.contentOffset = CGPointMake(0, -topCorrect);
@@ -162,7 +180,7 @@ export class Label extends LabelBase {
162180
}
163181

164182
case 'bottom': {
165-
const height = tv.sizeThatFits(CGSizeMake(tv.bounds.size.width, 10000)).height;
183+
const height = this.computeTextHeight(CGSizeMake(tv.bounds.size.width, 10000));
166184
let bottomCorrect = tv.bounds.size.height - height * tv.zoomScale;
167185
bottomCorrect = bottomCorrect < 0.0 ? 0.0 : bottomCorrect;
168186
// tv.contentOffset = CGPointMake(0, -bottomCorrect);
@@ -182,86 +200,86 @@ export class Label extends LabelBase {
182200
}
183201
private _fixedSize: FixedSize;
184202

185-
setTextDecorationAndTransform() {
186-
const style = this.style;
187-
const dict = new Map<string, any>();
188-
switch (style.textDecoration) {
189-
case 'none':
190-
break;
191-
case 'underline':
192-
// TODO: Replace deprecated `StyleSingle` with `Single` after the next typings update
193-
dict.set(NSUnderlineStyleAttributeName, NSUnderlineStyle.Single);
194-
break;
195-
case 'line-through':
196-
// TODO: Replace deprecated `StyleSingle` with `Single` after the next typings update
197-
dict.set(NSStrikethroughStyleAttributeName, NSUnderlineStyle.Single);
198-
break;
199-
case 'underline line-through':
200-
// TODO: Replace deprecated `StyleSingle` with `Single` after the next typings update
201-
dict.set(NSUnderlineStyleAttributeName, NSUnderlineStyle.Single);
202-
dict.set(NSStrikethroughStyleAttributeName, NSUnderlineStyle.Single);
203-
break;
204-
default:
205-
throw new Error(`Invalid text decoration value: ${style.textDecoration}. Valid values are: 'none', 'underline', 'line-through', 'underline line-through'.`);
206-
}
207-
208-
if (style.letterSpacing !== 0) {
209-
dict.set(NSKernAttributeName, style.letterSpacing * this.nativeTextViewProtected.font.pointSize);
210-
}
211-
212-
const isTextView = this.nativeTextViewProtected instanceof UITextView;
213-
if (style.lineHeight || style.whiteSpace === 'nowrap' || (style['lineBreak'] && style['lineBreak'] !== 'none')) {
214-
const paragraphStyle = NSMutableParagraphStyle.alloc().init();
215-
paragraphStyle.minimumLineHeight = style.lineHeight;
216-
// make sure a possible previously set text alignment setting is not lost when line height is specified
217-
paragraphStyle.alignment = (this.nativeTextViewProtected as UITextField | UITextView | UILabel).textAlignment;
218-
219-
// make sure a possible previously set line break mode is not lost when line height is specified
203+
// setTextDecorationAndTransform() {
204+
// const style = this.style;
205+
// const dict = new Map<string, any>();
206+
// switch (style.textDecoration) {
207+
// case 'none':
208+
// break;
209+
// case 'underline':
210+
// // TODO: Replace deprecated `StyleSingle` with `Single` after the next typings update
211+
// dict.set(NSUnderlineStyleAttributeName, NSUnderlineStyle.Single);
212+
// break;
213+
// case 'line-through':
214+
// // TODO: Replace deprecated `StyleSingle` with `Single` after the next typings update
215+
// dict.set(NSStrikethroughStyleAttributeName, NSUnderlineStyle.Single);
216+
// break;
217+
// case 'underline line-through':
218+
// // TODO: Replace deprecated `StyleSingle` with `Single` after the next typings update
219+
// dict.set(NSUnderlineStyleAttributeName, NSUnderlineStyle.Single);
220+
// dict.set(NSStrikethroughStyleAttributeName, NSUnderlineStyle.Single);
221+
// break;
222+
// default:
223+
// throw new Error(`Invalid text decoration value: ${style.textDecoration}. Valid values are: 'none', 'underline', 'line-through', 'underline line-through'.`);
224+
// }
220225

221-
if (style['lineBreak']) {
222-
paragraphStyle.lineBreakMode = lineBreakToLineBreakMode(style['lineBreak']);
223-
} else if (style.whiteSpace) {
224-
paragraphStyle.lineBreakMode = whiteSpaceToLineBreakMode(style.whiteSpace);
225-
}
226-
dict.set(NSParagraphStyleAttributeName, paragraphStyle);
227-
} else if (isTextView && this.style.textAlignment !== 'initial') {
228-
const paragraphStyle = NSMutableParagraphStyle.alloc().init();
229-
paragraphStyle.alignment = this.nativeTextViewProtected.textAlignment;
230-
dict.set(NSParagraphStyleAttributeName, paragraphStyle);
231-
}
226+
// if (style.letterSpacing !== 0) {
227+
// dict.set(NSKernAttributeName, style.letterSpacing * this.nativeTextViewProtected.font.pointSize);
228+
// }
232229

233-
if (style.color && dict.size > 0) {
234-
dict.set(NSForegroundColorAttributeName, style.color.ios);
235-
}
230+
// if (style.lineHeight || style.whiteSpace === 'nowrap' || (style['lineBreak'] && style['lineBreak'] !== 'none')) {
231+
// const paragraphStyle = NSMutableParagraphStyle.alloc().init();
232+
// paragraphStyle.minimumLineHeight = style.lineHeight;
233+
// // make sure a possible previously set text alignment setting is not lost when line height is specified
234+
// paragraphStyle.alignment = (this.nativeTextViewProtected as UITextField | UITextView | UILabel).textAlignment;
235+
236+
// // make sure a possible previously set line break mode is not lost when line height is specified
237+
238+
// if (style['lineBreak']) {
239+
// paragraphStyle.lineBreakMode = lineBreakToLineBreakMode(style['lineBreak']);
240+
// } else if (style.whiteSpace) {
241+
// paragraphStyle.lineBreakMode = whiteSpaceToLineBreakMode(style.whiteSpace);
242+
// }
243+
// dict.set(NSParagraphStyleAttributeName, paragraphStyle);
244+
// } else if (isTextView && this.style.textAlignment !== 'initial') {
245+
// const paragraphStyle = NSMutableParagraphStyle.alloc().init();
246+
// paragraphStyle.alignment = this.nativeTextViewProtected.textAlignment;
247+
// dict.set(NSParagraphStyleAttributeName, paragraphStyle);
248+
// }
236249

237-
const text = this.text;
238-
const str = text === undefined || text === null ? '' : text.toString();
239-
const source = getTransformedText(str, this.textTransform);
240-
if (dict.size > 0) {
241-
if (isTextView) {
242-
// UITextView's font seems to change inside.
243-
dict.set(NSFontAttributeName, this.nativeTextViewProtected.font);
244-
}
250+
// if (style.color && dict.size > 0) {
251+
// console.log('setTextDecorationAndTransform', style.color);
252+
// // dict.set(NSForegroundColorAttributeName, style.color.ios);
253+
// }
245254

246-
const result = NSMutableAttributedString.alloc().initWithString(source);
247-
result.setAttributesRange(dict as any, { location: 0, length: source.length });
248-
if (this.nativeTextViewProtected instanceof UIButton) {
249-
this.nativeTextViewProtected.setAttributedTitleForState(result, UIControlState.Normal);
250-
} else {
251-
this.nativeTextViewProtected.attributedText = result;
252-
}
253-
} else {
254-
if (this.nativeTextViewProtected instanceof UIButton) {
255-
// Clear attributedText or title won't be affected.
256-
this.nativeTextViewProtected.setAttributedTitleForState(null, UIControlState.Normal);
257-
this.nativeTextViewProtected.setTitleForState(source, UIControlState.Normal);
258-
} else {
259-
// Clear attributedText or text won't be affected.
260-
this.nativeTextViewProtected.attributedText = undefined;
261-
this.nativeTextViewProtected.text = source;
262-
}
263-
}
264-
}
255+
// const text = this.text;
256+
// const str = text === undefined || text === null ? '' : text.toString();
257+
// const source = getTransformedText(str, this.textTransform);
258+
// if (dict.size > 0) {
259+
// if (isTextView) {
260+
// // UITextView's font seems to change inside.
261+
// dict.set(NSFontAttributeName, this.nativeTextViewProtected.font);
262+
// }
263+
264+
// const result = NSMutableAttributedString.alloc().initWithString(source);
265+
// result.setAttributesRange(dict as any, { location: 0, length: source.length });
266+
// if (this.nativeTextViewProtected instanceof UIButton) {
267+
// this.nativeTextViewProtected.setAttributedTitleForState(result, UIControlState.Normal);
268+
// } else {
269+
// this.nativeTextViewProtected.attributedText = result;
270+
// }
271+
// } else {
272+
// if (this.nativeTextViewProtected instanceof UIButton) {
273+
// // Clear attributedText or title won't be affected.
274+
// this.nativeTextViewProtected.setAttributedTitleForState(null, UIControlState.Normal);
275+
// this.nativeTextViewProtected.setTitleForState(source, UIControlState.Normal);
276+
// } else {
277+
// // Clear attributedText or text won't be affected.
278+
// this.nativeTextViewProtected.attributedText = undefined;
279+
// this.nativeTextViewProtected.text = source;
280+
// }
281+
// }
282+
// }
265283

266284
_requestLayoutOnTextChanged(): void {
267285
if (this._fixedSize === FixedSize.BOTH) {
@@ -300,76 +318,28 @@ export class Label extends LabelBase {
300318
}
301319

302320
htmlText: NSMutableAttributedString;
303-
// needsHTMLUpdate = false;
304-
// updatingHTML = false;
305321
updateHTMLString() {
306-
// if (!this.nativeViewProtected || !this.needsHTMLUpdate || this.updatingHTML) {
307-
// return;
308-
// }
309-
// this.updatingHTML = true;
310322
if (!this.html) {
311323
this.htmlText = null;
312324
} else {
313325
let htmlString = this.html;
314-
315-
let fontFamily,
316-
fontSize = UIFont.labelFontSize;
317-
if (!!this.style.fontInternal) {
318-
if (!!this.style.fontInternal.fontFamily) {
319-
fontFamily = this.style.fontInternal.fontFamily[0] === "'" ? this.style.fontInternal.fontFamily.replace(/'/g, '') : this.style.fontInternal.fontFamily;
320-
} else {
321-
fontFamily = UIFont.systemFontOfSize(10).familyName;
322-
}
323-
if (this.style.fontInternal.fontSize) {
324-
fontSize = this.style.fontInternal.fontSize;
325-
}
326-
}
327-
328-
htmlString = `<span style="font-family: ${fontFamily}; font-size:${fontSize};">${htmlString}</span>`;
326+
const font = this.nativeView.font;
327+
htmlString = `<style>body{ color: ${this.color};font-family: '${font.familyName}'; font-size:${font.pointSize}px;}</style>${htmlString}`;
329328
const nsString = NSString.stringWithString(htmlString);
330-
const nsData = nsString.dataUsingEncoding(NSUTF8StringEncoding);
331-
const options = {
332-
[DTDefaultTextAlignment]: kCTLeftTextAlignment,
333-
// [NSTextSizeMultiplierDocumentOption]: 1,
334-
// [DTIgnoreLinkStyleOption]: false,
335-
// [DTDefaultFontFamily]: fontFamily,
336-
// [NSFontAttributeName]: fontFamily,
337-
[NSTextSizeMultiplierDocumentOption]: 17 / 12.0,
338-
[DTUseiOS6Attributes]: true,
339-
[DTDocumentPreserveTrailingSpaces]: true
340-
// [DTDefaultLineBreakMode]: kCTLineBreakByWordWrapping
341-
} as any;
342-
this.htmlText = NSMutableAttributedString.alloc().initWithHTMLDataOptionsDocumentAttributes(nsData, options, null);
343-
this.htmlText.enumerateAttributesInRangeOptionsUsingBlock(
344-
{ location: 0, length: this.htmlText.length },
345-
NSAttributedStringEnumerationReverse,
346-
(attributes: NSDictionary<any, any>, range, stop) => {
347-
if (!!attributes.valueForKey('DTGUID')) {
348-
// We need to remove this attribute or links are not colored right
349-
//
350-
// @see https://github.com/Cocoanetics/DTCoreText/issues/792
351-
this.htmlText.removeAttributeRange('CTForegroundColorFromContext', range);
352-
}
353-
}
329+
const nsData = nsString.dataUsingEncoding(NSUTF16StringEncoding);
330+
this.htmlText = NSMutableAttributedString.alloc().initWithDataOptionsDocumentAttributesError(
331+
nsData,
332+
<any>{
333+
[NSDocumentTypeDocumentAttribute]: NSHTMLTextDocumentType
334+
},
335+
null
354336
);
355-
// const nsString = NSString.stringWithString(htmlString);
356-
// const nsData = nsString.dataUsingEncoding(NSUnicodeStringEncoding);
357-
// this.htmlText = NSAttributedString.alloc().initWithDataOptionsDocumentAttributesError(
358-
// nsData,
359-
// <any>{
360-
// [NSDocumentTypeDocumentAttribute]: NSHTMLTextDocumentType
361-
// // [NSCharacterEncodingDocumentAttribute]: NSUTF8StringEncoding
362-
// },
363-
// null
364-
// );
365-
366-
// this.needsHTMLUpdate = false;
337+
367338
this._requestLayoutOnTextChanged();
368339
}
369340
if (this.nativeViewProtected) {
370341
this.nativeViewProtected.attributedText = this.htmlText;
371342
}
372-
// this.updatingHTML = false;
373343
}
374344
applyingNativeSetters = false;
375345
public onResumeNativeUpdates(): void {
@@ -378,22 +348,27 @@ export class Label extends LabelBase {
378348
super.onResumeNativeUpdates();
379349
this.applyingNativeSetters = false;
380350
}
351+
[colorProperty.setNative](value: Color | UIColor) {
352+
const color = value instanceof Color ? value.ios : value;
353+
if(!this.formattedText && !this.html) {
354+
const nativeView = this.nativeTextViewProtected;
355+
nativeView.textColor = color;
356+
}
357+
}
381358
[htmlProperty.setNative](value: string) {
382-
// this.htmlText = value;
383-
// if (this.needsHTMLUpdate || !this.style.fontInternal) {
384-
// this.needsHTMLUpdate = true;
385359
if (!this.style.fontInternal || !this.applyingNativeSetters) {
386360
this.updateHTMLString();
387361
}
388-
// }
362+
}
363+
[formattedTextProperty.setNative](value: FormattedString) {
364+
super[formattedTextProperty.setNative](value)
389365
}
390366
[fontInternalProperty.getDefault](): UIFont {
391367
const nativeView = this.nativeViewProtected;
392368
return nativeView.font;
393369
}
394370
[fontInternalProperty.setNative](value: Font | UIFont) {
395371
super[fontInternalProperty.setNative](value);
396-
// this.needsHTMLUpdate = true;
397372
// font setter always called after html
398373
if (this.html) {
399374
this.updateHTMLString();
@@ -553,6 +528,7 @@ export class Label extends LabelBase {
553528
// this.nativeViewProtected.textContainer.maximumNumberOfLines = value as number;
554529
// }
555530
// }
531+
556532
[verticalTextAlignmentProperty.setNative](value: VerticalTextAlignment) {
557533
this.updateVerticalAlignment();
558534
}

0 commit comments

Comments
 (0)