Skip to content

Commit d91a7af

Browse files
authored
Merge pull request #1281 from spevans/pr_nsattributed_string_fixes
2 parents f34b11f + cd25320 commit d91a7af

File tree

2 files changed

+88
-59
lines changed

2 files changed

+88
-59
lines changed

Foundation/NSAttributedString.swift

Lines changed: 73 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,22 @@
99

1010
import CoreFoundation
1111

12+
public struct NSAttributedStringKey : RawRepresentable, Equatable, Hashable {
13+
public let rawValue: String
14+
15+
public init(_ rawValue: String) {
16+
self.rawValue = rawValue
17+
}
18+
19+
public init(rawValue: String) {
20+
self.rawValue = rawValue
21+
}
22+
23+
public var hashValue: Int {
24+
return rawValue.hashValue
25+
}
26+
}
27+
1228
open class NSAttributedString: NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
1329

1430
private let _cfinfo = _CFInfo(typeID: CFAttributedStringGetTypeID())
@@ -42,73 +58,87 @@ open class NSAttributedString: NSObject, NSCopying, NSMutableCopying, NSSecureCo
4258
open func mutableCopy(with zone: NSZone? = nil) -> Any {
4359
NSUnimplemented()
4460
}
45-
61+
62+
/// The character contents of the receiver as an NSString object.
4663
open var string: String {
4764
return _string._swiftObject
4865
}
49-
50-
open func attributes(at location: Int, effectiveRange range: NSRangePointer) -> [String : Any] {
66+
67+
/// Returns the attributes for the character at a given index.
68+
open func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [NSAttributedStringKey : Any] {
5169
let rangeInfo = RangeInfo(
5270
rangePointer: range,
5371
shouldFetchLongestEffectiveRange: false,
5472
longestEffectiveRangeSearchRange: nil)
5573
return _attributes(at: location, rangeInfo: rangeInfo)
5674
}
5775

76+
/// The length of the receiver’s string object.
5877
open var length: Int {
5978
return CFAttributedStringGetLength(_cfObject)
6079
}
61-
62-
open func attribute(_ attrName: String, at location: Int, effectiveRange range: NSRangePointer?) -> Any? {
80+
81+
/// Returns the value for an attribute with a given name of the character at a given index, and by reference the range over which the attribute applies.
82+
open func attribute(_ attrName: NSAttributedStringKey, at location: Int, effectiveRange range: NSRangePointer?) -> Any? {
6383
let rangeInfo = RangeInfo(
6484
rangePointer: range,
6585
shouldFetchLongestEffectiveRange: false,
6686
longestEffectiveRangeSearchRange: nil)
6787
return _attribute(attrName, atIndex: location, rangeInfo: rangeInfo)
6888
}
69-
89+
90+
/// Returns an NSAttributedString object consisting of the characters and attributes within a given range in the receiver.
7091
open func attributedSubstring(from range: NSRange) -> NSAttributedString { NSUnimplemented() }
71-
72-
open func attributes(at location: Int, longestEffectiveRange range: NSRangePointer?, in rangeLimit: NSRange) -> [String : Any] {
92+
93+
/// Returns the attributes for the character at a given index, and by reference the range over which the attributes apply.
94+
open func attributes(at location: Int, longestEffectiveRange range: NSRangePointer?, in rangeLimit: NSRange) -> [NSAttributedStringKey : Any] {
7395
let rangeInfo = RangeInfo(
7496
rangePointer: range,
7597
shouldFetchLongestEffectiveRange: true,
7698
longestEffectiveRangeSearchRange: rangeLimit)
7799
return _attributes(at: location, rangeInfo: rangeInfo)
78100
}
79-
80-
open func attribute(_ attrName: String, at location: Int, longestEffectiveRange range: NSRangePointer?, in rangeLimit: NSRange) -> Any? {
101+
102+
/// Returns the value for the attribute with a given name of the character at a given index, and by reference the range over which the attribute applies.
103+
open func attribute(_ attrName: NSAttributedStringKey, at location: Int, longestEffectiveRange range: NSRangePointer?, in rangeLimit: NSRange) -> Any? {
81104
let rangeInfo = RangeInfo(
82105
rangePointer: range,
83106
shouldFetchLongestEffectiveRange: true,
84107
longestEffectiveRangeSearchRange: rangeLimit)
85108
return _attribute(attrName, atIndex: location, rangeInfo: rangeInfo)
86109
}
87-
110+
111+
/// Returns a Boolean value that indicates whether the receiver is equal to another given attributed string.
88112
open func isEqual(to other: NSAttributedString) -> Bool { NSUnimplemented() }
89-
90-
public init(string str: String) {
91-
_string = str._nsObject
113+
114+
/// Returns an NSAttributedString object initialized with the characters of a given string and no attribute information.
115+
public init(string: String) {
116+
_string = string._nsObject
92117
_attributeArray = CFRunArrayCreate(kCFAllocatorDefault)
93118

94119
super.init()
95120
addAttributesToAttributeArray(attrs: nil)
96121
}
97-
98-
public init(string str: String, attributes attrs: [String : Any]?) {
99-
_string = str._nsObject
122+
123+
/// Returns an NSAttributedString object initialized with a given string and attributes.
124+
public init(string: String, attributes attrs: [NSAttributedStringKey : Any]? = nil) {
125+
_string = string._nsObject
100126
_attributeArray = CFRunArrayCreate(kCFAllocatorDefault)
101-
127+
102128
super.init()
103129
addAttributesToAttributeArray(attrs: attrs)
104130
}
105-
106-
public init(NSAttributedString attrStr: NSAttributedString) { NSUnimplemented() }
107131

108-
open func enumerateAttributes(in enumerationRange: NSRange, options opts: NSAttributedString.EnumerationOptions = [], using block: ([String : Any], NSRange, UnsafeMutablePointer<ObjCBool>) -> Swift.Void) {
132+
/// Returns an NSAttributedString object initialized with the characters and attributes of another given attributed string.
133+
public init(attributedString: NSAttributedString) {
134+
NSUnimplemented()
135+
}
136+
137+
/// Executes the block for each attribute in the range.
138+
open func enumerateAttributes(in enumerationRange: NSRange, options opts: NSAttributedString.EnumerationOptions = [], using block: ([NSAttributedStringKey : Any], NSRange, UnsafeMutablePointer<ObjCBool>) -> Swift.Void) {
109139
_enumerate(in: enumerationRange, reversed: opts.contains(.reverse)) { currentIndex, stop in
110140
var attributesEffectiveRange = NSRange(location: NSNotFound, length: 0)
111-
let attributesInRange: [String : Any]
141+
let attributesInRange: [NSAttributedStringKey : Any]
112142
if opts.contains(.longestEffectiveRangeNotRequired) {
113143
attributesInRange = attributes(at: currentIndex, effectiveRange: &attributesEffectiveRange)
114144
} else {
@@ -122,8 +152,9 @@ open class NSAttributedString: NSObject, NSCopying, NSMutableCopying, NSSecureCo
122152
return attributesEffectiveRange
123153
}
124154
}
125-
126-
open func enumerateAttribute(_ attrName: String, in enumerationRange: NSRange, options opts: NSAttributedString.EnumerationOptions = [], using block: (Any?, NSRange, UnsafeMutablePointer<ObjCBool>) -> Swift.Void) {
155+
156+
/// Executes the block for the specified attribute run in the specified range.
157+
open func enumerateAttribute(_ attrName: NSAttributedStringKey, in enumerationRange: NSRange, options opts: NSAttributedString.EnumerationOptions = [], using block: (Any?, NSRange, UnsafeMutablePointer<ObjCBool>) -> Swift.Void) {
127158
_enumerate(in: enumerationRange, reversed: opts.contains(.reverse)) { currentIndex, stop in
128159
var attributeEffectiveRange = NSRange(location: NSNotFound, length: 0)
129160
let attributeInRange: Any?
@@ -183,9 +214,9 @@ private extension NSAttributedString {
183214
let longestEffectiveRangeSearchRange: NSRange?
184215
}
185216

186-
func _attributes(at location: Int, rangeInfo: RangeInfo) -> [String : Any] {
217+
func _attributes(at location: Int, rangeInfo: RangeInfo) -> [NSAttributedStringKey : Any] {
187218
var cfRange = CFRange()
188-
return withUnsafeMutablePointer(to: &cfRange) { (cfRangePointer: UnsafeMutablePointer<CFRange>) -> [String : Any] in
219+
return withUnsafeMutablePointer(to: &cfRange) { (cfRangePointer: UnsafeMutablePointer<CFRange>) -> [NSAttributedStringKey : Any] in
189220
// Get attributes value using CoreFoundation function
190221
let value: CFDictionary
191222
if rangeInfo.shouldFetchLongestEffectiveRange, let searchRange = rangeInfo.longestEffectiveRangeSearchRange {
@@ -196,12 +227,12 @@ private extension NSAttributedString {
196227

197228
// Convert the value to [String : AnyObject]
198229
let dictionary = unsafeBitCast(value, to: NSDictionary.self)
199-
var results = [String : Any]()
230+
var results = [NSAttributedStringKey : Any]()
200231
for (key, value) in dictionary {
201232
guard let stringKey = (key as? NSString)?._swiftObject else {
202233
continue
203234
}
204-
results[stringKey] = value
235+
results[NSAttributedStringKey(stringKey)] = value
205236
}
206237

207238
// Update effective range and return the results
@@ -211,15 +242,15 @@ private extension NSAttributedString {
211242
}
212243
}
213244

214-
func _attribute(_ attrName: String, atIndex location: Int, rangeInfo: RangeInfo) -> Any? {
245+
func _attribute(_ attrName: NSAttributedStringKey, atIndex location: Int, rangeInfo: RangeInfo) -> Any? {
215246
var cfRange = CFRange()
216247
return withUnsafeMutablePointer(to: &cfRange) { (cfRangePointer: UnsafeMutablePointer<CFRange>) -> AnyObject? in
217248
// Get attribute value using CoreFoundation function
218249
let attribute: AnyObject?
219250
if rangeInfo.shouldFetchLongestEffectiveRange, let searchRange = rangeInfo.longestEffectiveRangeSearchRange {
220-
attribute = CFAttributedStringGetAttributeAndLongestEffectiveRange(_cfObject, location, attrName._cfObject, CFRange(searchRange), cfRangePointer)
251+
attribute = CFAttributedStringGetAttributeAndLongestEffectiveRange(_cfObject, location, attrName.rawValue._cfObject, CFRange(searchRange), cfRangePointer)
221252
} else {
222-
attribute = CFAttributedStringGetAttribute(_cfObject, location, attrName._cfObject, cfRangePointer)
253+
attribute = CFAttributedStringGetAttribute(_cfObject, location, attrName.rawValue._cfObject, cfRangePointer)
223254
}
224255

225256
// Update effective range and return the result
@@ -241,18 +272,17 @@ private extension NSAttributedString {
241272
}
242273
}
243274

244-
func addAttributesToAttributeArray(attrs: [String : Any]?) {
275+
func addAttributesToAttributeArray(attrs: [NSAttributedStringKey : Any]?) {
245276
guard _string.length > 0 else {
246277
return
247278
}
248279

249280
let range = CFRange(location: 0, length: _string.length)
281+
var attributes: [String : Any] = [:]
250282
if let attrs = attrs {
251-
CFRunArrayInsert(_attributeArray, range, attrs._cfObject)
252-
} else {
253-
let emptyAttrs = [String : AnyObject]()
254-
CFRunArrayInsert(_attributeArray, range, emptyAttrs._cfObject)
283+
attrs.forEach { attributes[$0.rawValue] = $1 }
255284
}
285+
CFRunArrayInsert(_attributeArray, range, attributes._cfObject)
256286
}
257287
}
258288

@@ -277,19 +307,19 @@ extension NSAttributedString {
277307
open class NSMutableAttributedString : NSAttributedString {
278308

279309
open func replaceCharacters(in range: NSRange, with str: String) { NSUnimplemented() }
280-
open func setAttributes(_ attrs: [String : Any]?, range: NSRange) { NSUnimplemented() }
310+
open func setAttributes(_ attrs: [NSAttributedStringKey : Any]?, range: NSRange) { NSUnimplemented() }
281311

282312
open var mutableString: NSMutableString {
283313
return _string as! NSMutableString
284314
}
285-
286-
open func addAttribute(_ name: String, value: Any, range: NSRange) {
287-
CFAttributedStringSetAttribute(_cfMutableObject, CFRange(range), name._cfObject, _SwiftValue.store(value))
315+
316+
open func addAttribute(_ name: NSAttributedStringKey, value: Any, range: NSRange) {
317+
CFAttributedStringSetAttribute(_cfMutableObject, CFRange(range), name.rawValue._cfObject, _SwiftValue.store(value))
288318
}
319+
320+
open func addAttributes(_ attrs: [NSAttributedStringKey : Any], range: NSRange) { NSUnimplemented() }
289321

290-
open func addAttributes(_ attrs: [String : Any], range: NSRange) { NSUnimplemented() }
291-
292-
open func removeAttribute(_ name: String, range: NSRange) { NSUnimplemented() }
322+
open func removeAttribute(_ name: NSAttributedStringKey, range: NSRange) { NSUnimplemented() }
293323

294324
open func replaceCharacters(in range: NSRange, with attrString: NSAttributedString) { NSUnimplemented() }
295325
open func insert(_ attrString: NSAttributedString, at loc: Int) { NSUnimplemented() }

TestFoundation/TestNSAttributedString.swift

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
#endif
1919

2020

21-
2221
class TestNSAttributedString : XCTestCase {
2322

2423
static var allTests: [(String, (TestNSAttributedString) -> () throws -> Void)] {
@@ -30,7 +29,7 @@ class TestNSAttributedString : XCTestCase {
3029
("test_enumerateAttributes", test_enumerateAttributes),
3130
]
3231
}
33-
32+
3433
func test_initWithString() {
3534
let string = "Lorem 😀 ipsum dolor sit amet, consectetur adipiscing elit. ⌘ Phasellus consectetur et sem vitae consectetur. Nam venenatis lectus a laoreet blandit. ಠ_ರೃ"
3635
let attrString = NSAttributedString(string: string)
@@ -43,40 +42,40 @@ class TestNSAttributedString : XCTestCase {
4342
XCTAssertEqual(range.length, string.utf16.count)
4443
XCTAssertEqual(attrs.count, 0)
4544

46-
let attribute = attrString.attribute("invalid", at: 0, effectiveRange: &range)
45+
let attribute = attrString.attribute(NSAttributedStringKey("invalid"), at: 0, effectiveRange: &range)
4746
XCTAssertNil(attribute)
4847
XCTAssertEqual(range.location, 0)
4948
XCTAssertEqual(range.length, string.utf16.count)
5049
}
5150

5251
func test_initWithStringAndAttributes() {
5352
let string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur et sem vitae consectetur. Nam venenatis lectus a laoreet blandit."
54-
let attributes: [String : AnyObject] = ["attribute.placeholder.key" : "attribute.placeholder.value" as NSString]
53+
let attributes: [NSAttributedStringKey : AnyObject] = [NSAttributedStringKey("attribute.placeholder.key") : "attribute.placeholder.value" as NSString]
5554

5655
let attrString = NSAttributedString(string: string, attributes: attributes)
5756
XCTAssertEqual(attrString.string, string)
5857
XCTAssertEqual(attrString.length, string.utf16.count)
5958

6059
var range = NSRange()
6160
let attrs = attrString.attributes(at: 0, effectiveRange: &range)
62-
guard let value = attrs["attribute.placeholder.key"] as? String else {
61+
guard let value = attrs[NSAttributedStringKey("attribute.placeholder.key")] as? String else {
6362
XCTAssert(false, "attribute value not found")
6463
return
6564
}
6665
XCTAssertEqual(range.location, 0)
6766
XCTAssertEqual(range.length, attrString.length)
6867
XCTAssertEqual(value, "attribute.placeholder.value")
6968

70-
let invalidAttribute = attrString.attribute("invalid", at: 0, effectiveRange: &range)
69+
let invalidAttribute = attrString.attribute(NSAttributedStringKey("invalid"), at: 0, effectiveRange: &range)
7170
XCTAssertNil(invalidAttribute)
7271
XCTAssertEqual(range.location, 0)
7372
XCTAssertEqual(range.length, string.utf16.count)
7473

75-
let attribute = attrString.attribute("attribute.placeholder.key", at: 0, effectiveRange: &range)
74+
let attribute = attrString.attribute(NSAttributedStringKey("attribute.placeholder.key"), at: 0, effectiveRange: &range)
7675
XCTAssertEqual(range.location, 0)
7776
XCTAssertEqual(range.length, attrString.length)
7877
guard let validAttribute = attribute as? NSString else {
79-
XCTAssert(false, "attribuet not found")
78+
XCTAssert(false, "attribute not found")
8079
return
8180
}
8281
XCTAssertEqual(validAttribute, "attribute.placeholder.value")
@@ -85,7 +84,7 @@ class TestNSAttributedString : XCTestCase {
8584
func test_longestEffectiveRange() {
8685
let string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur et sem vitae consectetur. Nam venenatis lectus a laoreet blandit."
8786

88-
let attrKey = "attribute.placeholder.key"
87+
let attrKey = NSAttributedStringKey("attribute.placeholder.key")
8988
let attrValue = "attribute.placeholder.value" as NSString
9089

9190
let attrRange1 = NSRange(location: 0, length: 20)
@@ -110,12 +109,12 @@ class TestNSAttributedString : XCTestCase {
110109
func test_enumerateAttributeWithName() {
111110
let string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur et sem vitae consectetur. Nam venenatis lectus a laoreet blandit."
112111

113-
let attrKey1 = "attribute.placeholder.key1"
112+
let attrKey1 = NSAttributedStringKey("attribute.placeholder.key1")
114113
let attrValue1 = "attribute.placeholder.value1"
115114
let attrRange1 = NSRange(location: 0, length: 20)
116115
let attrRange2 = NSRange(location: 18, length: 10)
117116

118-
let attrKey3 = "attribute.placeholder.key3"
117+
let attrKey3 = NSAttributedStringKey("attribute.placeholder.key3")
119118
let attrValue3 = "attribute.placeholder.value3"
120119
let attrRange3 = NSRange(location: 40, length: 5)
121120

@@ -161,15 +160,15 @@ class TestNSAttributedString : XCTestCase {
161160
#else
162161
let string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur et sem vitae consectetur. Nam venenatis lectus a laoreet blandit."
163162

164-
let attrKey1 = "attribute.placeholder.key1"
163+
let attrKey1 = NSAttributedStringKey("attribute.placeholder.key1")
165164
let attrValue1 = "attribute.placeholder.value1"
166165
let attrRange1 = NSRange(location: 0, length: 20)
167166

168-
let attrKey2 = "attribute.placeholder.key2"
167+
let attrKey2 = NSAttributedStringKey("attribute.placeholder.key2")
169168
let attrValue2 = "attribute.placeholder.value2"
170169
let attrRange2 = NSRange(location: 18, length: 10)
171170

172-
let attrKey3 = "attribute.placeholder.key3"
171+
let attrKey3 = NSAttributedStringKey("attribute.placeholder.key3")
173172
let attrValue3 = "attribute.placeholder.value3"
174173
let attrRange3 = NSRange(location: 40, length: 5)
175174

@@ -235,9 +234,9 @@ fileprivate extension TestNSAttributedString {
235234
}
236235
}
237236

238-
fileprivate func describe(attrs: [String : Any]) -> String {
237+
fileprivate func describe(attrs: [NSAttributedStringKey : Any]) -> String {
239238
if attrs.count > 0 {
240-
return "[" + attrs.map({ "\($0):\($1)" }).sorted(by: { $0 < $1 }).joined(separator: ",") + "]"
239+
return "[" + attrs.map({ "\($0.rawValue):\($1)" }).sorted(by: { $0 < $1 }).joined(separator: ",") + "]"
241240
} else {
242241
return "[:]"
243242
}

0 commit comments

Comments
 (0)