Skip to content

Commit 89b13d6

Browse files
authored
Fix typing attributes and add more tests (#84)
1 parent 949dc75 commit 89b13d6

File tree

10 files changed

+502
-317
lines changed

10 files changed

+502
-317
lines changed

Examples/Examples/ViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class ViewController: MessageViewController, UITableViewDataSource, UITableViewD
2626
borderColor = .lightGray
2727

2828
messageView.textViewInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 16)
29-
messageView.font = UIFont.preferredFont(forTextStyle: .body)
29+
messageView.font = UIFont.systemFont(ofSize: 18)
3030

3131
messageView.setButton(title: "Add", for: .normal, position: .left)
3232
messageView.addButton(target: self, action: #selector(onLeftButton), position: .left)

Examples/Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ SPEC CHECKSUMS:
1313

1414
PODFILE CHECKSUM: 74208e93c3daf191e681c80fcdf956f1603f9016
1515

16-
COCOAPODS: 1.5.2
16+
COCOAPODS: 1.5.3

Examples/Pods/Manifest.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Examples/Pods/Pods.xcodeproj/project.pbxproj

Lines changed: 236 additions & 209 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

MessageViewController.xcodeproj/project.pbxproj

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
290482271FED90340053978C /* UITextView+Prefixes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2904821E1FED90340053978C /* UITextView+Prefixes.swift */; };
1818
29792B141FFAE7FC007A0C57 /* MessageTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29792B121FFAE7FC007A0C57 /* MessageTextView.swift */; };
1919
29792B151FFAE7FC007A0C57 /* MessageAutocompleteController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29792B131FFAE7FC007A0C57 /* MessageAutocompleteController.swift */; };
20+
29C8A1FB2187223300AEA4E0 /* MessageTextViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C8A1FA2187223300AEA4E0 /* MessageTextViewTests.swift */; };
21+
29C8A1FD2187D39200AEA4E0 /* DictionaryString+NSAttributedStringKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C8A1FC2187D39200AEA4E0 /* DictionaryString+NSAttributedStringKey.swift */; };
2022
29C8F9A9208BDAC60075931C /* ExpandedHitTestButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C8F9A8208BDAC60075931C /* ExpandedHitTestButton.swift */; };
21-
29CC293B1FF4266D006B6DE7 /* MessageViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29CC293A1FF4266D006B6DE7 /* MessageViewControllerTests.swift */; };
2223
29CC293D1FF4266D006B6DE7 /* MessageViewController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2904820B1FED90070053978C /* MessageViewController.framework */; };
2324
29CC29441FF4267F006B6DE7 /* String+WordAtRangeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29CC29431FF4267F006B6DE7 /* String+WordAtRangeTests.swift */; };
2425
29CC29471FF42687006B6DE7 /* String+WordAtRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29CC29451FF42687006B6DE7 /* String+WordAtRange.swift */; };
@@ -51,9 +52,10 @@
5152
2904821E1FED90340053978C /* UITextView+Prefixes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITextView+Prefixes.swift"; sourceTree = "<group>"; };
5253
29792B121FFAE7FC007A0C57 /* MessageTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageTextView.swift; sourceTree = "<group>"; };
5354
29792B131FFAE7FC007A0C57 /* MessageAutocompleteController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageAutocompleteController.swift; sourceTree = "<group>"; };
55+
29C8A1FA2187223300AEA4E0 /* MessageTextViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageTextViewTests.swift; sourceTree = "<group>"; };
56+
29C8A1FC2187D39200AEA4E0 /* DictionaryString+NSAttributedStringKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DictionaryString+NSAttributedStringKey.swift"; sourceTree = "<group>"; };
5457
29C8F9A8208BDAC60075931C /* ExpandedHitTestButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandedHitTestButton.swift; sourceTree = "<group>"; };
5558
29CC29381FF4266D006B6DE7 /* MessageViewControllerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MessageViewControllerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
56-
29CC293A1FF4266D006B6DE7 /* MessageViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageViewControllerTests.swift; sourceTree = "<group>"; };
5759
29CC293C1FF4266D006B6DE7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
5860
29CC29431FF4267F006B6DE7 /* String+WordAtRangeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+WordAtRangeTests.swift"; sourceTree = "<group>"; };
5961
29CC29451FF42687006B6DE7 /* String+WordAtRange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+WordAtRange.swift"; sourceTree = "<group>"; };
@@ -102,20 +104,21 @@
102104
2904820D1FED90070053978C /* MessageViewController */ = {
103105
isa = PBXGroup;
104106
children = (
107+
29C8A1FC2187D39200AEA4E0 /* DictionaryString+NSAttributedStringKey.swift */,
108+
29C8F9A8208BDAC60075931C /* ExpandedHitTestButton.swift */,
105109
2904820F1FED90070053978C /* Info.plist */,
106110
29792B131FFAE7FC007A0C57 /* MessageAutocompleteController.swift */,
107111
29792B121FFAE7FC007A0C57 /* MessageTextView.swift */,
108-
29C8F9A8208BDAC60075931C /* ExpandedHitTestButton.swift */,
109112
290482181FED90340053978C /* MessageView.swift */,
110113
2904820E1FED90070053978C /* MessageViewController.h */,
111114
290482171FED90340053978C /* MessageViewController.swift */,
112115
290482191FED90340053978C /* MessageViewController+MessageViewDelegate.swift */,
113116
2904821C1FED90340053978C /* MessageViewDelegate.swift */,
117+
38199E102022792600ADFE76 /* NSAttributedString+ReplaceRange.swift */,
114118
29CC29451FF42687006B6DE7 /* String+WordAtRange.swift */,
115119
2904821D1FED90340053978C /* UIButton+BottomHeightOffset.swift */,
116120
290482161FED90340053978C /* UIScrollView+StopScrolling.swift */,
117121
2904821E1FED90340053978C /* UITextView+Prefixes.swift */,
118-
38199E102022792600ADFE76 /* NSAttributedString+ReplaceRange.swift */,
119122
29CC29461FF42687006B6DE7 /* UIView+iOS11.swift */,
120123
);
121124
path = MessageViewController;
@@ -125,9 +128,9 @@
125128
isa = PBXGroup;
126129
children = (
127130
29CC293C1FF4266D006B6DE7 /* Info.plist */,
128-
29CC293A1FF4266D006B6DE7 /* MessageViewControllerTests.swift */,
129-
29CC29431FF4267F006B6DE7 /* String+WordAtRangeTests.swift */,
131+
29C8A1FA2187223300AEA4E0 /* MessageTextViewTests.swift */,
130132
38D26FB02023D01900B2B7B5 /* NSAttributedString+HighlightingTests.swift */,
133+
29CC29431FF4267F006B6DE7 /* String+WordAtRangeTests.swift */,
131134
);
132135
path = MessageViewControllerTests;
133136
sourceTree = "<group>";
@@ -247,6 +250,7 @@
247250
29792B141FFAE7FC007A0C57 /* MessageTextView.swift in Sources */,
248251
290482211FED90340053978C /* MessageView.swift in Sources */,
249252
290482261FED90340053978C /* UIButton+BottomHeightOffset.swift in Sources */,
253+
29C8A1FD2187D39200AEA4E0 /* DictionaryString+NSAttributedStringKey.swift in Sources */,
250254
290482251FED90340053978C /* MessageViewDelegate.swift in Sources */,
251255
290482201FED90340053978C /* MessageViewController.swift in Sources */,
252256
29CC29481FF42687006B6DE7 /* UIView+iOS11.swift in Sources */,
@@ -263,8 +267,8 @@
263267
isa = PBXSourcesBuildPhase;
264268
buildActionMask = 2147483647;
265269
files = (
266-
29CC293B1FF4266D006B6DE7 /* MessageViewControllerTests.swift in Sources */,
267270
38D26FB12023D01900B2B7B5 /* NSAttributedString+HighlightingTests.swift in Sources */,
271+
29C8A1FB2187223300AEA4E0 /* MessageTextViewTests.swift in Sources */,
268272
29CC29441FF4267F006B6DE7 /* String+WordAtRangeTests.swift in Sources */,
269273
29CC29491FF81F1F006B6DE7 /* String+WordAtRange.swift in Sources */,
270274
);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// DictionaryString+NSAttributedStringKey.swift
3+
// MessageViewController
4+
//
5+
// Created by Ryan Nystrom on 10/29/18.
6+
// Copyright © 2018 Ryan Nystrom. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
extension Dictionary where Key == String {
12+
13+
var attributed: [NSAttributedStringKey: Any] {
14+
var map = [NSAttributedStringKey: Any]()
15+
forEach { map[NSAttributedStringKey($0)] = $1 }
16+
return map
17+
}
18+
19+
}

MessageViewController/MessageAutocompleteController.swift

Lines changed: 5 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -31,37 +31,14 @@ public final class MessageAutocompleteController: MessageTextViewListener {
3131
public private(set) var selection: Selection?
3232

3333
/// Adds an additional space after the autocompleted text when true. Default value is `TRUE`
34-
open var appendSpaceOnCompletion = true
34+
public var appendSpaceOnCompletion = true
3535

3636
/// The text attributes applied to highlighted substrings for each prefix
3737
private var autocompleteTextAttributes: [String: [NSAttributedStringKey: Any]] = [:]
3838

3939
/// A key used for referencing which substrings were autocompletes
4040
private let NSAttributedAutocompleteKey = NSAttributedStringKey.init("com.messageviewcontroller.autocompletekey")
4141

42-
private var defaultTextAttributes: [NSAttributedStringKey: Any] {
43-
return [
44-
.font: textView.defaultFont,
45-
.foregroundColor: textView.defaultTextColor,
46-
]
47-
}
48-
49-
/// A reference to `defaultTextAttributes` that adds the NSAttributedAutocompleteKey
50-
private var typingTextAttributes: [NSAttributedStringKey: Any] {
51-
var attributes = defaultTextAttributes
52-
attributes[.paragraphStyle] = paragraphStyle
53-
attributes[NSAttributedAutocompleteKey] = false
54-
return attributes
55-
}
56-
57-
/// The NSAttributedStringKey.paragraphStyle value applied to attributed strings
58-
private let paragraphStyle: NSMutableParagraphStyle = {
59-
let style = NSMutableParagraphStyle()
60-
style.paragraphSpacingBefore = 2
61-
style.lineHeightMultiple = 1
62-
return style
63-
}()
64-
6542
internal var registeredPrefixes = Set<String>()
6643
internal let border = CALayer()
6744
internal var keyboardHeight: CGFloat = 0
@@ -80,8 +57,6 @@ public final class MessageAutocompleteController: MessageTextViewListener {
8057
name: .UIKeyboardWillChangeFrame,
8158
object: nil
8259
)
83-
84-
preserveTypingAttributes(for: textView)
8560
}
8661

8762
// MARK: Public API
@@ -123,8 +98,6 @@ public final class MessageAutocompleteController: MessageTextViewListener {
12398
location: selectedLocation,
12499
length: 0
125100
)
126-
127-
preserveTypingAttributes(for: textView)
128101
}
129102

130103
internal func cancel() {
@@ -180,15 +153,14 @@ public final class MessageAutocompleteController: MessageTextViewListener {
180153
public func registerAutocomplete(prefix: String, attributes: [NSAttributedStringKey: Any]) {
181154
registeredPrefixes.insert(prefix)
182155
autocompleteTextAttributes[prefix] = attributes
183-
autocompleteTextAttributes[prefix]?[.paragraphStyle] = paragraphStyle
184156
}
185157

186158
// MARK: Private API
187159

188160
private func insertAutocomplete(_ autocomplete: String, at selection: Selection, for range: NSRange, keepPrefix: Bool) {
189-
let defaultTypingTextAttributes = typingTextAttributes
161+
let defaultTypingTextAttributes = textView.typingAttributes.attributed
190162

191-
var attrs = defaultTextAttributes
163+
var attrs = defaultTypingTextAttributes
192164
attrs[NSAttributedAutocompleteKey] = true
193165

194166
if let autoAttrs = autocompleteTextAttributes[selection.prefix] {
@@ -232,25 +204,14 @@ public final class MessageAutocompleteController: MessageTextViewListener {
232204
else { return }
233205
keyboardHeight = keyboardFrame.height
234206
}
235-
236-
/// Ensures new text typed is not styled
237-
///
238-
/// - Parameter textView: The `UITextView` to apply `typingTextAttributes` to
239-
internal func preserveTypingAttributes(for textView: UITextView) {
240-
var typingAttributes = [String: Any]()
241-
typingTextAttributes.forEach { typingAttributes[$0.key.rawValue] = $0.value }
242-
textView.typingAttributes = typingAttributes
243-
}
244207

245208
// MARK: MessageTextViewListener
246209

247210
public func didChangeSelection(textView: MessageTextView) {
248211
check()
249212
}
250213

251-
public func didChange(textView: MessageTextView) {
252-
preserveTypingAttributes(for: textView)
253-
}
214+
public func didChange(textView: MessageTextView) {}
254215

255216
public func willChangeRange(textView: MessageTextView, to range: NSRange) {
256217

@@ -272,11 +233,9 @@ public final class MessageAutocompleteController: MessageTextViewListener {
272233
// Only delete the first found range
273234
defer { stop.pointee = true }
274235

275-
let emptyString = NSAttributedString(string: "", attributes: typingTextAttributes)
236+
let emptyString = NSAttributedString(string: "", attributes: textView.typingAttributes.attributed)
276237
textView.attributedText = textView.attributedText.replacingCharacters(in: range, with: emptyString)
277238
textView.selectedRange = NSRange(location: range.location, length: 0)
278-
self.textView.textViewDidChange(textView)
279-
self.preserveTypingAttributes(for: textView)
280239
})
281240
}
282241
}

MessageViewController/MessageTextView.swift

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,48 @@ public protocol MessageTextViewListener: class {
1515

1616
open class MessageTextView: UITextView, UITextViewDelegate {
1717

18-
private let placeholderLabel = UILabel()
19-
20-
private var listeners: NSHashTable<AnyObject> = NSHashTable.weakObjects()
18+
internal let placeholderLabel = UILabel()
19+
internal var listeners: NSHashTable<AnyObject> = NSHashTable.weakObjects()
2120

2221
open override var delegate: UITextViewDelegate? {
2322
get { return self }
2423
set {}
2524
}
2625

27-
open var defaultFont: UIFont? = UIFont.preferredFont(forTextStyle: .body)
26+
open var defaultFont = UIFont.preferredFont(forTextStyle: .body) {
27+
didSet {
28+
defaultTextAttributes[NSAttributedStringKey.font.rawValue] = defaultFont
29+
}
30+
}
2831

29-
open var defaultTextColor: UIColor? = .black
32+
open var defaultTextColor = UIColor.black {
33+
didSet {
34+
defaultTextAttributes[NSAttributedStringKey.foregroundColor.rawValue] = defaultTextColor
35+
}
36+
}
37+
38+
internal var defaultTextAttributes: [String: Any] = {
39+
let style = NSMutableParagraphStyle()
40+
style.paragraphSpacingBefore = 2
41+
style.lineHeightMultiple = 1
42+
return [NSAttributedStringKey.paragraphStyle.rawValue: style]
43+
}() {
44+
didSet {
45+
typingAttributes = defaultTextAttributes
46+
}
47+
}
3048

3149
open override var font: UIFont? {
3250
didSet {
33-
defaultFont = font
51+
defaultFont = font ?? .preferredFont(forTextStyle: .body)
3452
placeholderLabel.font = font
3553
placeholderLayoutDidChange()
3654
}
3755
}
3856

3957
open override var textColor: UIColor? {
4058
didSet {
41-
defaultTextColor = textColor
59+
defaultTextColor = textColor ?? .black
4260
}
4361
}
4462

@@ -49,15 +67,14 @@ open class MessageTextView: UITextView, UITextViewDelegate {
4967
}
5068
}
5169

52-
open override var text: String! {
53-
didSet {
54-
updatePlaceholderVisibility()
55-
}
56-
}
57-
5870
open override var attributedText: NSAttributedString! {
59-
didSet {
60-
updatePlaceholderVisibility()
71+
get { return super.attributedText }
72+
set {
73+
let didChange = super.attributedText != newValue
74+
super.attributedText = newValue
75+
if didChange {
76+
textViewDidChange(self)
77+
}
6178
}
6279
}
6380

@@ -112,12 +129,14 @@ open class MessageTextView: UITextView, UITextViewDelegate {
112129
placeholderLabel.font = font
113130
placeholderLabel.textColor = textColor
114131
placeholderLabel.textAlignment = textAlignment
115-
116132
addSubview(placeholderLabel)
117133
updatePlaceholderVisibility()
134+
135+
defaultTextAttributes[NSAttributedStringKey.font.rawValue] = defaultFont
136+
defaultTextAttributes[NSAttributedStringKey.foregroundColor.rawValue] = defaultTextColor
118137
}
119138

120-
private func enumerateListeners(block: (MessageTextViewListener) -> Void) {
139+
internal func enumerateListeners(block: (MessageTextViewListener) -> Void) {
121140
for listener in listeners.objectEnumerator() {
122141
guard let listener = listener as? MessageTextViewListener else { continue }
123142
block(listener)
@@ -136,6 +155,7 @@ open class MessageTextView: UITextView, UITextViewDelegate {
136155
// MARK: UITextViewDelegate
137156

138157
public func textViewDidChange(_ textView: UITextView) {
158+
typingAttributes = defaultTextAttributes
139159
updatePlaceholderVisibility()
140160
enumerateListeners { $0.didChange(textView: self) }
141161
}

0 commit comments

Comments
 (0)