Skip to content

Commit feff416

Browse files
committed
feat(ios): big rewrite using UILabel (when not selectable) for a much faster component
All features have been rewritten to be faster and improved
1 parent 77652e9 commit feff416

11 files changed

+958
-413
lines changed

Diff for: package.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@
4949
"devDependencies": {
5050
"@commitlint/cli": "^17.4.4",
5151
"@commitlint/config-conventional": "^17.4.4",
52-
"@nativescript-community/text": "1.5.11",
52+
"@nativescript-community/text": "1.5.17",
5353
"@nativescript/core": "8.4.7",
5454
"@nativescript/types-android": "8.4.0",
5555
"@nativescript/types-ios": "8.4.0",
5656
"@nativescript/webpack": "5.0.13",
57-
"@types/node": "^18.14.0",
57+
"@types/node": "^20.1.2",
5858
"@typescript-eslint/eslint-plugin": "5.52.0",
5959
"@typescript-eslint/parser": "5.52.0",
6060
"eslint": "8.34.0",
@@ -75,5 +75,8 @@
7575
"extends": [
7676
"@commitlint/config-conventional"
7777
]
78+
},
79+
"resolutions": {
80+
"@nativescript-community/text": "portal:/Volumes/dev/nativescript/nativescript-text/packages/text"
7881
}
7982
}

Diff for: plugin/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"license": "Apache-2.0",
3333
"readmeFilename": "README.md",
3434
"dependencies": {
35-
"@nativescript-community/text": "^1.5.12"
35+
"@nativescript-community/text": "^1.5.17"
3636
},
3737
"gitHead": "a08eb50756c9a5d16fc8aa6ff7fba0051c71d1e0"
3838
}

Diff for: plugin/platforms/android/native-api-usage.json

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"android.os:Build",
1010
"android.util:TypedValue",
1111
"android.graphics:Paint",
12+
"android.graphics:Paint.FontMetrics",
1213
"android.text:Spanned",
1314
"androidx.core.widget:TextViewCompat"
1415
]

Diff for: plugin/platforms/ios/src/NSLabel.swift

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import UIKit
2+
3+
@objcMembers
4+
@objc(NSLabel)
5+
class NSLabel: UILabel {
6+
var zoomScale:Float = 1.0;
7+
var textContainerInset: UIEdgeInsets = UIEdgeInsets.zero
8+
var padding: UIEdgeInsets = UIEdgeInsets.zero
9+
var borderThickness:UIEdgeInsets = UIEdgeInsets.zero
10+
override init(frame: CGRect) {
11+
super.init(frame: frame )
12+
commonInit()
13+
14+
}
15+
16+
required init?(coder: NSCoder) {
17+
super.init(coder: coder)
18+
commonInit()
19+
}
20+
21+
func commonInit() {
22+
if #available(iOS 13, *) {
23+
self.textColor = UIColor.label
24+
}
25+
}
26+
27+
28+
override func textRect(forBounds bounds:CGRect, limitedToNumberOfLines numberOfLines:Int) -> CGRect {
29+
// UILabel.textRectForBounds:limitedToNumberOfLines: returns rect with CGSizeZero when empty
30+
if self.text?.count == 0 {
31+
return super.textRect(forBounds: bounds, limitedToNumberOfLines:numberOfLines)
32+
}
33+
34+
// 1. Subtract the insets (border thickness & padding)
35+
// 2. Calculate the original label bounds
36+
// 3. Add the insets again
37+
let insets:UIEdgeInsets = UIEdgeInsets(top: self.borderThickness.top + self.padding.top,
38+
left: self.borderThickness.left + self.padding.left,
39+
bottom: self.borderThickness.bottom + self.padding.bottom,
40+
right: self.borderThickness.right + self.padding.right)
41+
42+
let rect:CGRect = super.textRect(forBounds: bounds.inset(by: insets), limitedToNumberOfLines:numberOfLines)
43+
44+
let inverseInsets:UIEdgeInsets = UIEdgeInsets(top: -(self.borderThickness.top + self.padding.top),
45+
left: -(self.borderThickness.left + self.padding.left),
46+
bottom: -(self.borderThickness.bottom + self.padding.bottom),
47+
right: -(self.borderThickness.right + self.padding.right))
48+
49+
return rect.inset(by: inverseInsets)
50+
}
51+
52+
override func drawText(in rect:CGRect) {
53+
super.drawText(in: rect.inset(by: self.textContainerInset))
54+
}
55+
// - (void)drawTextInRect:(CGRect)rect {
56+
57+
// [super drawTextInRect:UIEdgeInsetsInsetRect(rect, self.textContainerInset)];
58+
// }
59+
override public var intrinsicContentSize: CGSize {
60+
get {
61+
var intrinsicContentSize:CGSize = super.intrinsicContentSize
62+
intrinsicContentSize.width += self.padding.left + self.padding.right
63+
intrinsicContentSize.height += self.padding.top + self.padding.bottom
64+
return intrinsicContentSize
65+
}
66+
}
67+
68+
func updateFontRatio(_ ratio: CGFloat){
69+
let toChange: NSMutableAttributedString = attributedText as? NSMutableAttributedString ?? NSMutableAttributedString.init(attributedString: attributedText!)
70+
var found = false;
71+
toChange.enumerateAttribute(NSAttributedString.Key(rawValue: "OriginalFontSize"), in: NSRange(location: 0, length: toChange.length)) { value, range, stop in
72+
if ((value != nil) && range.length > 0) {
73+
toChange.enumerateAttribute(NSAttributedString.Key.font, in: range) { value2, range2, stop2 in
74+
if let font = (value2 as? UIFont) {
75+
if ( ratio != font.pointSize) {
76+
let newFont = font.withSize(round(value as! CGFloat * ratio))
77+
found = true;
78+
toChange.removeAttribute(NSAttributedString.Key.font, range: range)
79+
toChange.addAttribute(NSAttributedString.Key.font, value: newFont, range: range)
80+
81+
}
82+
83+
}
84+
85+
}
86+
}
87+
}
88+
89+
if (found) {
90+
self.attributedText = toChange;
91+
}
92+
}
93+
}

Diff for: plugin/platforms/ios/src/NSLabelUtils.swift

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import Foundation
2+
import UIKit
3+
4+
@objcMembers
5+
@objc(NSLabelUtils)
6+
class NSLabelUtils: NSObject {
7+
class func setTextDecorationAndTransformOn(view:UIView!, text:String!, textDecoration:String!, letterSpacing:CGFloat, lineHeight:CGFloat, color:UIColor!) {
8+
let attrDict:NSMutableDictionary! = NSMutableDictionary()
9+
var paragraphStyle:NSMutableParagraphStyle! = nil
10+
let isTextType:Bool = (view is UITextField) || (view is UITextView) || (view is UILabel) || (view is UIButton)
11+
let isTextView:Bool = (view is UITextView)
12+
13+
if textDecoration.contains("underline") {
14+
attrDict[NSAttributedString.Key.underlineStyle] = (NSUnderlineStyle.single)
15+
}
16+
17+
if textDecoration.contains("line-through") {
18+
attrDict[NSAttributedString.Key.strikethroughStyle] = (NSUnderlineStyle.single)
19+
}
20+
21+
if letterSpacing != 0 && isTextType && (view as! UILabel).font != nil {
22+
let kern:NSNumber! = NSNumber.init(value: letterSpacing * (view as! UILabel).font.pointSize)
23+
attrDict[NSAttributedString.Key.kern] = kern
24+
}
25+
var fLineHeight = lineHeight
26+
if fLineHeight >= 0 {
27+
if fLineHeight == 0 {
28+
fLineHeight = 0.00001
29+
}
30+
if paragraphStyle == nil {
31+
paragraphStyle = NSMutableParagraphStyle()
32+
}
33+
paragraphStyle.minimumLineHeight = fLineHeight
34+
paragraphStyle.maximumLineHeight = fLineHeight
35+
// make sure a possible previously set text alignment setting is not lost when line height is specified
36+
37+
38+
}
39+
if (paragraphStyle != nil) {
40+
if (view is UIButton) {
41+
paragraphStyle.alignment = (view as! UIButton).titleLabel!.textAlignment
42+
} else {
43+
paragraphStyle.alignment = (view as! UILabel).textAlignment
44+
}
45+
if (view is UILabel) {
46+
// make sure a possible previously set line break mode is not lost when line height is specified
47+
paragraphStyle.lineBreakMode = (view as! UILabel).lineBreakMode
48+
}
49+
attrDict[NSAttributedString.Key.paragraphStyle] = paragraphStyle
50+
}
51+
52+
if attrDict.count > 0 {
53+
if isTextView && ((view as! UITextView).font != nil) {
54+
// UITextView's font seems to change inside.
55+
attrDict[NSAttributedString.Key.font] = (view as! UITextView).font
56+
}
57+
58+
if color != nil {
59+
attrDict[NSAttributedString.Key.foregroundColor] = color
60+
}
61+
62+
let result:NSMutableAttributedString! = NSMutableAttributedString(string:text)
63+
result.setAttributes((attrDict as! [NSAttributedString.Key : Any]), range:NSRange(location: 0, length: text.count))
64+
65+
if (view is UIButton) {
66+
(view as! UIButton).setAttributedTitle(result, for:UIControl.State.normal)
67+
}
68+
else if(view is UILabel) {
69+
(view as! UILabel).attributedText = result
70+
} else if(view is UITextView) {
71+
(view as! UITextView).attributedText = result
72+
}
73+
} else {
74+
if (view is UIButton) {
75+
// Clear attributedText or title won't be affected.
76+
(view as! UIButton).setAttributedTitle(nil, for:UIControl.State.normal)
77+
(view as! UIButton).setTitle(text, for:UIControl.State.normal)
78+
} else if(view is UILabel) {
79+
// Clear attributedText or text won't be affected.
80+
(view as! UILabel).attributedText = nil
81+
(view as! UILabel).text = text
82+
} else if(view is UITextView) {
83+
// Clear attributedText or text won't be affected.
84+
(view as! UITextView).attributedText = nil
85+
(view as! UITextView).text = text
86+
}
87+
}
88+
}
89+
class func inset(rect:CGRect, uIEdgeInsets:UIEdgeInsets) -> CGRect {
90+
return rect.inset(by: uIEdgeInsets)
91+
}
92+
enum NSLabelOrNSTextView: Equatable {
93+
case NSLabel(NSLabel)
94+
case NSTextView(NSTextView)
95+
}
96+
enum Component {
97+
case `switch`(UISwitch)
98+
case stepper(UIStepper)
99+
100+
var control: UIControl {
101+
switch self {
102+
case .switch(let comp):
103+
return comp
104+
case .stepper(let comp):
105+
return comp
106+
}
107+
}
108+
}
109+
110+
class func updateFontRatio(_ view: UIView, ratio: CGFloat){
111+
var currentAttributedString: NSAttributedString? = nil
112+
switch view {
113+
case is NSLabel:
114+
currentAttributedString = (view as! NSLabel).attributedText
115+
case is NSTextView:
116+
currentAttributedString = (view as! NSTextView).attributedText
117+
default: break
118+
}
119+
if (currentAttributedString == nil) {
120+
return;
121+
}
122+
let toChange: NSMutableAttributedString = (currentAttributedString as? NSMutableAttributedString) ?? NSMutableAttributedString.init(attributedString: currentAttributedString!)
123+
var found = false;
124+
toChange.enumerateAttribute(NSAttributedString.Key(rawValue: "OriginalFontSize"), in: NSRange(location: 0, length: toChange.length)) { value, range, stop in
125+
if ((value != nil) && range.length > 0) {
126+
toChange.enumerateAttribute(NSAttributedString.Key.font, in: range) { value2, range2, stop2 in
127+
if let font = (value2 as? UIFont) {
128+
if ( ratio != font.pointSize) {
129+
let newFont = font.withSize(round(value as! CGFloat * ratio))
130+
found = true;
131+
toChange.removeAttribute(NSAttributedString.Key.font, range: range)
132+
toChange.addAttribute(NSAttributedString.Key.font, value: newFont, range: range)
133+
134+
}
135+
136+
}
137+
138+
}
139+
}
140+
}
141+
142+
if (found) {
143+
switch view {
144+
case is NSLabel:
145+
(view as! NSLabel).attributedText = toChange;
146+
case is NSTextView:
147+
(view as! NSTextView).attributedText = toChange;
148+
default: break
149+
}
150+
}
151+
}
152+
}
153+
154+
@objc extension NSAttributedString {
155+
func hasAttribute(_ attribute: String) -> Bool {
156+
var result = false;
157+
self.enumerateAttribute(NSAttributedString.Key(rawValue: attribute), in: NSRange(location: 0, length: length)) { value, range, stop in
158+
if ((value != nil) && range.length > 0) {
159+
result = true
160+
stop.pointee = true
161+
}
162+
}
163+
return result;
164+
}
165+
}

Diff for: plugin/platforms/ios/src/NSTextView.swift

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import UIKit
2+
3+
@objcMembers
4+
@objc(NSTextView)
5+
class NSTextView: UITextView {
6+
var padding: UIEdgeInsets = UIEdgeInsets.zero
7+
var borderThickness:UIEdgeInsets = UIEdgeInsets.zero
8+
override init(frame: CGRect, textContainer: NSTextContainer?) {
9+
super.init(frame: frame, textContainer:textContainer )
10+
commonInit()
11+
12+
}
13+
14+
required init?(coder: NSCoder) {
15+
super.init(coder: coder)
16+
commonInit()
17+
}
18+
func commonInit() {
19+
if ((self.font == nil)) {
20+
self.font = UIFont.systemFont(ofSize: 12);
21+
}
22+
if #available(iOS 13, *) {
23+
self.textColor = UIColor.label;
24+
}
25+
// view.linkTextAttributes = NSDictionary.new();
26+
self.isScrollEnabled = false;
27+
self.isEditable = false;
28+
self.isSelectable = false;
29+
self.backgroundColor = UIColor.clear;
30+
// self.userInteractionEnabled = true;
31+
self.dataDetectorTypes = UIDataDetectorTypes.all;
32+
self.textContainerInset = UIEdgeInsets.zero;
33+
self.textContainer.lineFragmentPadding = 0;
34+
// ignore font leading just like UILabel does
35+
self.layoutManager.usesFontLeading = false;
36+
}
37+
}

0 commit comments

Comments
 (0)