2525//
2626
2727import UIKit
28+ import SwiftUI
2829
2930
30- extension UILabel : TypographyExtensions {
31-
32- /// Update attributed string layout due to (unknown) UIKit internals.
33- fileprivate func updateAttributedTextLayout( ) {
34- _ = self . attributedText
31+ extension Optional where Wrapped == String {
32+
33+ var count : Int {
34+ switch self {
35+ case . none:
36+ return 0
37+ case . some( let wrapped) :
38+ return wrapped. count
39+ }
3540 }
36-
41+ }
42+
43+
44+ extension UILabel : TypographyExtensions {
45+
3746 var paragraphStyle : NSParagraphStyle ? {
3847 getAttribute ( . paragraphStyle)
3948 }
@@ -48,29 +57,51 @@ extension UILabel: TypographyExtensions {
4857 . paragraphStyle,
4958 value: ( paragraphStyle ?? NSParagraphStyle ( ) )
5059 . mutable
60+ . withProperty ( textAlignment, for: \. alignment)
5161 . withProperty ( lineHeight, for: \. minimumLineHeight)
5262 . withProperty ( lineHeight, for: \. maximumLineHeight)
53- . withProperty ( textAlignment, for: \. alignment)
5463 )
55- onTextChange { [ weak self] in
56- self ? . updateAttributedTextLayout ( )
57- }
64+ setupCache ( )
65+ }
66+ }
67+
68+ func setupCache( ) {
69+ onTextChange { [ unowned self] oldText, newText in
70+
71+ // Apply cached attributes (if any) in case text have just changed from empty.
72+ if oldText. count == 0 ,
73+ newText. count > 0 ,
74+ let newText = newText {
75+ self . attributedText = NSAttributedString ( string: newText, attributes: cachedAttributes)
76+ }
77+
78+ // Update attributed string layout due to (unknown) UIKit internals.
79+ _ = self . attributedText
5880 }
5981 }
6082
6183 public var letterSpacing : CGFloat ? {
6284 get { getAttribute ( . kern) }
63- set { setAttribute ( . kern, value: newValue) }
85+ set {
86+ setAttribute ( . kern, value: newValue)
87+ setupCache ( )
88+ }
6489 }
6590
6691 public var underline : NSUnderlineStyle ? {
6792 get { getAttribute ( . underlineStyle) }
68- set { setAttribute ( . underlineStyle, value: newValue) }
93+ set {
94+ setAttribute ( . underlineStyle, value: newValue)
95+ setupCache ( )
96+ }
6997 }
7098
7199 public var strikethrough : NSUnderlineStyle ? {
72100 get { getAttribute ( . strikethroughStyle) }
73- set { setAttribute ( . strikethroughStyle, value: newValue) }
101+ set {
102+ setAttribute ( . strikethroughStyle, value: newValue)
103+ setupCache ( )
104+ }
74105 }
75106
76107 public var leadingImage : Typography . Image ? {
@@ -105,14 +136,16 @@ fileprivate extension NSAttributedString {
105136}
106137
107138
139+ // MARK: Attributes
140+
108141fileprivate extension UILabel {
109142
110143 struct Keys {
111144 static var placeholder : UInt8 = 0
112145 }
113146
114147 /// An attributed string property to cache typography even when the label text is empty.
115- var placeholder : NSAttributedString {
148+ var cache : NSAttributedString {
116149 get {
117150 objc_getAssociatedObject ( self , & Keys. placeholder) as? NSAttributedString ?? NSAttributedString ( string: " Placeholder " )
118151 }
@@ -121,26 +154,31 @@ fileprivate extension UILabel {
121154 }
122155 }
123156
124- /// Attributes of `attributedText` (or the cached placeholder string if text is empty ).
125- var attributes : [ NSAttributedString . Key : Any ] {
157+ /// Attributes of `attributedText` (if any ).
158+ var attributes : [ NSAttributedString . Key : Any ] ? {
126159 get {
127160 if let attributedText = attributedText,
128161 attributedText. length > 0 {
129162 return attributedText. attributes ( at: 0 , effectiveRange: nil )
130163 } else {
131- return placeholder . attributes ( at : 0 , effectiveRange : nil )
164+ return nil
132165 }
133166 }
134167 }
135168
169+ /// Attributes of `cache`.
170+ var cachedAttributes : [ NSAttributedString . Key : Any ] {
171+ cache. attributes ( at: 0 , effectiveRange: nil )
172+ }
173+
136174 func addAttribute( _ key: NSAttributedString . Key , value: Any ) {
137175 attributedText = attributedText? . stringByAddingAttribute ( key, value: value)
138- placeholder = placeholder . stringByAddingAttribute ( key, value: value)
176+ cache = cache . stringByAddingAttribute ( key, value: value)
139177 }
140178
141179 func removeAttribute( _ key: NSAttributedString . Key ) {
142180 attributedText = attributedText? . stringByRemovingAttribute ( key)
143- placeholder = placeholder . stringByRemovingAttribute ( key)
181+ cache = cache . stringByRemovingAttribute ( key)
144182 }
145183}
146184
@@ -151,14 +189,14 @@ extension UILabel {
151189 fileprivate func getAttribute< AttributeType> (
152190 _ key: NSAttributedString . Key
153191 ) -> AttributeType ? where AttributeType: Any {
154- return attributes [ key] as? AttributeType
192+ return ( attributes ?? cachedAttributes ) [ key] as? AttributeType
155193 }
156194
157195 /// Get `OptionSet` attribute for the given key (if any).
158196 fileprivate func getAttribute< AttributeType> (
159197 _ key: NSAttributedString . Key
160198 ) -> AttributeType ? where AttributeType: OptionSet {
161- if let attribute = attributes [ key] as? AttributeType . RawValue {
199+ if let attribute = ( attributes ?? cachedAttributes ) [ key] as? AttributeType . RawValue {
162200 return . init( rawValue: attribute)
163201 } else {
164202 return nil
0 commit comments