Skip to content

Commit 0a8281b

Browse files
committed
0.2.0
1 parent 6e4a78a commit 0a8281b

6 files changed

Lines changed: 94 additions & 32 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
# CHANGELOG
22

33

4+
* Feature/Empty_Label_Support/0.2.0
5+
6+
+ Added `oldText` and `newText` parameters to `onTextChange` observer
7+
+ Renamed `placeholder` to `cache`
8+
+ Apply cached attributes (if any) in case text have just changed from empty on text change
9+
+ More testbed cleanup
10+
411
* Feature/Empty_Label_Support/0.1.9
512

613
+ Cleaned up testbed views

UILabel_Typography_Extensions/Typography/UILabel+Observer.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import UIKit
3030
extension UILabel {
3131

3232
typealias TextObserver = Observer<UILabel, String?>
33+
typealias TextChangeAction = (_ oldValue: String?, _ newValue: String?) -> Void
3334

3435
fileprivate struct Keys {
3536
static var observer: UInt8 = 0
@@ -44,27 +45,27 @@ extension UILabel {
4445
}
4546
}
4647

47-
func onTextChange(onChange completion: @escaping () -> Void) {
48+
func onTextChange(onChange completion: @escaping TextChangeAction) {
4849
guard observer == nil else {
4950
return
5051
}
5152

5253
observer = TextObserver(
5354
for: self,
5455
keyPath: \.text,
55-
onChange: { text in completion() }
56+
onChange: { oldText, newText in completion(oldText ?? nil, newText ?? nil) }
5657
)
5758
}
5859
}
5960

6061

6162
class Observer<ObjectType: NSObject, ValueType>: NSObject {
6263

63-
typealias OnChangeAction = (_ value: ValueType?) -> Void
64-
let onChange: OnChangeAction
64+
typealias ChangeAction = (_ oldValue: ValueType?, _ newValue: ValueType?) -> Void
65+
let onChange: ChangeAction
6566
private var observer: NSKeyValueObservation?
6667

67-
init(for object: ObjectType, keyPath: KeyPath<ObjectType, ValueType>, onChange: @escaping OnChangeAction) {
68+
init(for object: ObjectType, keyPath: KeyPath<ObjectType, ValueType>, onChange: @escaping ChangeAction) {
6869
self.onChange = onChange
6970
super.init()
7071
observe(object, keyPath: keyPath)
@@ -75,7 +76,7 @@ class Observer<ObjectType: NSObject, ValueType>: NSObject {
7576
keyPath,
7677
options: [.new, .old],
7778
changeHandler: { [weak self] _, change in
78-
self?.onChange(change.newValue ?? nil)
79+
self?.onChange(change.oldValue, change.newValue)
7980
}
8081
)
8182
}

UILabel_Typography_Extensions/Typography/UILabel+Typograpy.swift

Lines changed: 59 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,24 @@
2525
//
2626

2727
import 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+
108141
fileprivate 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

UILabel_Typography_Extensions/View/AttributesViewController.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ class AttributesViewController: UIViewController {
1919
.vertical(spacing: 10)
2020
.views(
2121

22+
UILabel()
23+
.with {
24+
$0.textColor = .label
25+
$0.font = .preferredFont(forTextStyle: .headline)
26+
$0.underline = .double
27+
$0.showGrid = true
28+
}
29+
.with(text: "Underline"),
30+
2231
UILabel()
2332
.with(text: "Default")
2433
.with {
@@ -80,7 +89,6 @@ class AttributesViewController: UIViewController {
8089
},
8190

8291
UILabel()
83-
.with(text: "🔠Letter Spacing / Alignment / Line Break Mode")
8492
.with {
8593
$0.textColor = .systemGray
8694
$0.font = .preferredFont(forTextStyle: .body)
@@ -95,7 +103,8 @@ class AttributesViewController: UIViewController {
95103
.withImages
96104
.with {
97105
self.label_4 = $0
98-
},
106+
}
107+
.with(text: "🔠Letter Spacing / Alignment / Line Break Mode"),
99108

100109
UIView
101110
.spacer

UILabel_Typography_Extensions/View/EmptyViewController.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,27 @@ class EmptyViewController: UIViewController {
2222
$0.numberOfLines = 0
2323
$0.showGrid = true
2424
$0.lineHeight = CGFloat(87)
25-
$0.underline = .double
25+
$0.underline = .single
2626
$0.textAlignment = .center
2727

2828
// Set text after.
2929
$0.text = loremIpsum
3030

3131
}.inspect
3232

33-
var models: [(text: String, backgroundColor: UIColor)] = [
33+
var models: [(text: String?, backgroundColor: UIColor)] = [
3434
("Lorem ipsum dolor sit amet.", UIColor.systemBlue.withAlphaComponent(0.1)),
3535
("Consectetur adipiscing elit.", UIColor.systemGreen.withAlphaComponent(0.1)),
3636
("", UIColor.clear),
37+
(nil, UIColor.clear),
3738
("Sed do eiusmod tempor incididunt ut labore.", UIColor.systemOrange.withAlphaComponent(0.1)),
39+
("Et dolore magna aliqua.", UIColor.systemIndigo.withAlphaComponent(0.1)),
40+
("Et dolore magna aliqua.", UIColor.systemIndigo.withAlphaComponent(0.1)),
41+
("Et dolore magna aliqua.", UIColor.systemIndigo.withAlphaComponent(0.1)),
42+
("Et dolore magna aliqua.", UIColor.systemIndigo.withAlphaComponent(0.1)),
43+
("", UIColor.clear),
44+
("", UIColor.clear),
45+
("Et dolore magna aliqua.", UIColor.systemIndigo.withAlphaComponent(0.1)),
3846
("Et dolore magna aliqua.", UIColor.systemIndigo.withAlphaComponent(0.1))
3947
]
4048

UILabel_Typography_Extensions/View/LoremIpsumViewController.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ class LoremIpsumViewController: UIViewController {
1717
.views(
1818

1919
UILabel()
20-
.with(text: loremIpsum)
2120
.with {
2221
$0.textColor = .label
2322
$0.font = .preferredFont(forTextStyle: .headline)

0 commit comments

Comments
 (0)