Skip to content

NSAttributedString: Align methods with Darwin. #1281

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 73 additions & 43 deletions Foundation/NSAttributedString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,22 @@

import CoreFoundation

public struct NSAttributedStringKey : RawRepresentable, Equatable, Hashable {
public let rawValue: String

public init(_ rawValue: String) {
self.rawValue = rawValue
}

public init(rawValue: String) {
self.rawValue = rawValue
}

public var hashValue: Int {
return rawValue.hashValue
}
}

open class NSAttributedString: NSObject, NSCopying, NSMutableCopying, NSSecureCoding {

private let _cfinfo = _CFInfo(typeID: CFAttributedStringGetTypeID())
Expand Down Expand Up @@ -42,73 +58,87 @@ open class NSAttributedString: NSObject, NSCopying, NSMutableCopying, NSSecureCo
open func mutableCopy(with zone: NSZone? = nil) -> Any {
NSUnimplemented()
}


/// The character contents of the receiver as an NSString object.
open var string: String {
return _string._swiftObject
}

open func attributes(at location: Int, effectiveRange range: NSRangePointer) -> [String : Any] {

/// Returns the attributes for the character at a given index.
open func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [NSAttributedStringKey : Any] {
let rangeInfo = RangeInfo(
rangePointer: range,
shouldFetchLongestEffectiveRange: false,
longestEffectiveRangeSearchRange: nil)
return _attributes(at: location, rangeInfo: rangeInfo)
}

/// The length of the receiver’s string object.
open var length: Int {
return CFAttributedStringGetLength(_cfObject)
}

open func attribute(_ attrName: String, at location: Int, effectiveRange range: NSRangePointer?) -> Any? {

/// 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.
open func attribute(_ attrName: NSAttributedStringKey, at location: Int, effectiveRange range: NSRangePointer?) -> Any? {
let rangeInfo = RangeInfo(
rangePointer: range,
shouldFetchLongestEffectiveRange: false,
longestEffectiveRangeSearchRange: nil)
return _attribute(attrName, atIndex: location, rangeInfo: rangeInfo)
}


/// Returns an NSAttributedString object consisting of the characters and attributes within a given range in the receiver.
open func attributedSubstring(from range: NSRange) -> NSAttributedString { NSUnimplemented() }

open func attributes(at location: Int, longestEffectiveRange range: NSRangePointer?, in rangeLimit: NSRange) -> [String : Any] {

/// Returns the attributes for the character at a given index, and by reference the range over which the attributes apply.
open func attributes(at location: Int, longestEffectiveRange range: NSRangePointer?, in rangeLimit: NSRange) -> [NSAttributedStringKey : Any] {
let rangeInfo = RangeInfo(
rangePointer: range,
shouldFetchLongestEffectiveRange: true,
longestEffectiveRangeSearchRange: rangeLimit)
return _attributes(at: location, rangeInfo: rangeInfo)
}

open func attribute(_ attrName: String, at location: Int, longestEffectiveRange range: NSRangePointer?, in rangeLimit: NSRange) -> Any? {

/// 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.
open func attribute(_ attrName: NSAttributedStringKey, at location: Int, longestEffectiveRange range: NSRangePointer?, in rangeLimit: NSRange) -> Any? {
let rangeInfo = RangeInfo(
rangePointer: range,
shouldFetchLongestEffectiveRange: true,
longestEffectiveRangeSearchRange: rangeLimit)
return _attribute(attrName, atIndex: location, rangeInfo: rangeInfo)
}


/// Returns a Boolean value that indicates whether the receiver is equal to another given attributed string.
open func isEqual(to other: NSAttributedString) -> Bool { NSUnimplemented() }

public init(string str: String) {
_string = str._nsObject

/// Returns an NSAttributedString object initialized with the characters of a given string and no attribute information.
public init(string: String) {
_string = string._nsObject
_attributeArray = CFRunArrayCreate(kCFAllocatorDefault)

super.init()
addAttributesToAttributeArray(attrs: nil)
}

public init(string str: String, attributes attrs: [String : Any]?) {
_string = str._nsObject

/// Returns an NSAttributedString object initialized with a given string and attributes.
public init(string: String, attributes attrs: [NSAttributedStringKey : Any]? = nil) {
_string = string._nsObject
_attributeArray = CFRunArrayCreate(kCFAllocatorDefault)

super.init()
addAttributesToAttributeArray(attrs: attrs)
}

public init(NSAttributedString attrStr: NSAttributedString) { NSUnimplemented() }

open func enumerateAttributes(in enumerationRange: NSRange, options opts: NSAttributedString.EnumerationOptions = [], using block: ([String : Any], NSRange, UnsafeMutablePointer<ObjCBool>) -> Swift.Void) {
/// Returns an NSAttributedString object initialized with the characters and attributes of another given attributed string.
public init(attributedString: NSAttributedString) {
NSUnimplemented()
}

/// Executes the block for each attribute in the range.
open func enumerateAttributes(in enumerationRange: NSRange, options opts: NSAttributedString.EnumerationOptions = [], using block: ([NSAttributedStringKey : Any], NSRange, UnsafeMutablePointer<ObjCBool>) -> Swift.Void) {
_enumerate(in: enumerationRange, reversed: opts.contains(.reverse)) { currentIndex, stop in
var attributesEffectiveRange = NSRange(location: NSNotFound, length: 0)
let attributesInRange: [String : Any]
let attributesInRange: [NSAttributedStringKey : Any]
if opts.contains(.longestEffectiveRangeNotRequired) {
attributesInRange = attributes(at: currentIndex, effectiveRange: &attributesEffectiveRange)
} else {
Expand All @@ -122,8 +152,9 @@ open class NSAttributedString: NSObject, NSCopying, NSMutableCopying, NSSecureCo
return attributesEffectiveRange
}
}

open func enumerateAttribute(_ attrName: String, in enumerationRange: NSRange, options opts: NSAttributedString.EnumerationOptions = [], using block: (Any?, NSRange, UnsafeMutablePointer<ObjCBool>) -> Swift.Void) {

/// Executes the block for the specified attribute run in the specified range.
open func enumerateAttribute(_ attrName: NSAttributedStringKey, in enumerationRange: NSRange, options opts: NSAttributedString.EnumerationOptions = [], using block: (Any?, NSRange, UnsafeMutablePointer<ObjCBool>) -> Swift.Void) {
_enumerate(in: enumerationRange, reversed: opts.contains(.reverse)) { currentIndex, stop in
var attributeEffectiveRange = NSRange(location: NSNotFound, length: 0)
let attributeInRange: Any?
Expand Down Expand Up @@ -183,9 +214,9 @@ private extension NSAttributedString {
let longestEffectiveRangeSearchRange: NSRange?
}

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

// Convert the value to [String : AnyObject]
let dictionary = unsafeBitCast(value, to: NSDictionary.self)
var results = [String : Any]()
var results = [NSAttributedStringKey : Any]()
for (key, value) in dictionary {
guard let stringKey = (key as? NSString)?._swiftObject else {
continue
}
results[stringKey] = value
results[NSAttributedStringKey(stringKey)] = value
}

// Update effective range and return the results
Expand All @@ -211,15 +242,15 @@ private extension NSAttributedString {
}
}

func _attribute(_ attrName: String, atIndex location: Int, rangeInfo: RangeInfo) -> Any? {
func _attribute(_ attrName: NSAttributedStringKey, atIndex location: Int, rangeInfo: RangeInfo) -> Any? {
var cfRange = CFRange()
return withUnsafeMutablePointer(to: &cfRange) { (cfRangePointer: UnsafeMutablePointer<CFRange>) -> AnyObject? in
// Get attribute value using CoreFoundation function
let attribute: AnyObject?
if rangeInfo.shouldFetchLongestEffectiveRange, let searchRange = rangeInfo.longestEffectiveRangeSearchRange {
attribute = CFAttributedStringGetAttributeAndLongestEffectiveRange(_cfObject, location, attrName._cfObject, CFRange(searchRange), cfRangePointer)
attribute = CFAttributedStringGetAttributeAndLongestEffectiveRange(_cfObject, location, attrName.rawValue._cfObject, CFRange(searchRange), cfRangePointer)
} else {
attribute = CFAttributedStringGetAttribute(_cfObject, location, attrName._cfObject, cfRangePointer)
attribute = CFAttributedStringGetAttribute(_cfObject, location, attrName.rawValue._cfObject, cfRangePointer)
}

// Update effective range and return the result
Expand All @@ -241,18 +272,17 @@ private extension NSAttributedString {
}
}

func addAttributesToAttributeArray(attrs: [String : Any]?) {
func addAttributesToAttributeArray(attrs: [NSAttributedStringKey : Any]?) {
guard _string.length > 0 else {
return
}

let range = CFRange(location: 0, length: _string.length)
var attributes: [String : Any] = [:]
if let attrs = attrs {
CFRunArrayInsert(_attributeArray, range, attrs._cfObject)
} else {
let emptyAttrs = [String : AnyObject]()
CFRunArrayInsert(_attributeArray, range, emptyAttrs._cfObject)
attrs.forEach { attributes[$0.rawValue] = $1 }
}
CFRunArrayInsert(_attributeArray, range, attributes._cfObject)
}
}

Expand All @@ -277,19 +307,19 @@ extension NSAttributedString {
open class NSMutableAttributedString : NSAttributedString {

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

open var mutableString: NSMutableString {
return _string as! NSMutableString
}
open func addAttribute(_ name: String, value: Any, range: NSRange) {
CFAttributedStringSetAttribute(_cfMutableObject, CFRange(range), name._cfObject, _SwiftValue.store(value))

open func addAttribute(_ name: NSAttributedStringKey, value: Any, range: NSRange) {
CFAttributedStringSetAttribute(_cfMutableObject, CFRange(range), name.rawValue._cfObject, _SwiftValue.store(value))
}

open func addAttributes(_ attrs: [NSAttributedStringKey : Any], range: NSRange) { NSUnimplemented() }

open func addAttributes(_ attrs: [String : Any], range: NSRange) { NSUnimplemented() }

open func removeAttribute(_ name: String, range: NSRange) { NSUnimplemented() }
open func removeAttribute(_ name: NSAttributedStringKey, range: NSRange) { NSUnimplemented() }

open func replaceCharacters(in range: NSRange, with attrString: NSAttributedString) { NSUnimplemented() }
open func insert(_ attrString: NSAttributedString, at loc: Int) { NSUnimplemented() }
Expand Down
31 changes: 15 additions & 16 deletions TestFoundation/TestNSAttributedString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
#endif



class TestNSAttributedString : XCTestCase {

static var allTests: [(String, (TestNSAttributedString) -> () throws -> Void)] {
Expand All @@ -30,7 +29,7 @@ class TestNSAttributedString : XCTestCase {
("test_enumerateAttributes", test_enumerateAttributes),
]
}

func test_initWithString() {
let string = "Lorem 😀 ipsum dolor sit amet, consectetur adipiscing elit. ⌘ Phasellus consectetur et sem vitae consectetur. Nam venenatis lectus a laoreet blandit. ಠ_ರೃ"
let attrString = NSAttributedString(string: string)
Expand All @@ -43,40 +42,40 @@ class TestNSAttributedString : XCTestCase {
XCTAssertEqual(range.length, string.utf16.count)
XCTAssertEqual(attrs.count, 0)

let attribute = attrString.attribute("invalid", at: 0, effectiveRange: &range)
let attribute = attrString.attribute(NSAttributedStringKey("invalid"), at: 0, effectiveRange: &range)
XCTAssertNil(attribute)
XCTAssertEqual(range.location, 0)
XCTAssertEqual(range.length, string.utf16.count)
}

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

let attrString = NSAttributedString(string: string, attributes: attributes)
XCTAssertEqual(attrString.string, string)
XCTAssertEqual(attrString.length, string.utf16.count)

var range = NSRange()
let attrs = attrString.attributes(at: 0, effectiveRange: &range)
guard let value = attrs["attribute.placeholder.key"] as? String else {
guard let value = attrs[NSAttributedStringKey("attribute.placeholder.key")] as? String else {
XCTAssert(false, "attribute value not found")
return
}
XCTAssertEqual(range.location, 0)
XCTAssertEqual(range.length, attrString.length)
XCTAssertEqual(value, "attribute.placeholder.value")

let invalidAttribute = attrString.attribute("invalid", at: 0, effectiveRange: &range)
let invalidAttribute = attrString.attribute(NSAttributedStringKey("invalid"), at: 0, effectiveRange: &range)
XCTAssertNil(invalidAttribute)
XCTAssertEqual(range.location, 0)
XCTAssertEqual(range.length, string.utf16.count)

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

let attrKey = "attribute.placeholder.key"
let attrKey = NSAttributedStringKey("attribute.placeholder.key")
let attrValue = "attribute.placeholder.value" as NSString

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

let attrKey1 = "attribute.placeholder.key1"
let attrKey1 = NSAttributedStringKey("attribute.placeholder.key1")
let attrValue1 = "attribute.placeholder.value1"
let attrRange1 = NSRange(location: 0, length: 20)
let attrRange2 = NSRange(location: 18, length: 10)

let attrKey3 = "attribute.placeholder.key3"
let attrKey3 = NSAttributedStringKey("attribute.placeholder.key3")
let attrValue3 = "attribute.placeholder.value3"
let attrRange3 = NSRange(location: 40, length: 5)

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

let attrKey1 = "attribute.placeholder.key1"
let attrKey1 = NSAttributedStringKey("attribute.placeholder.key1")
let attrValue1 = "attribute.placeholder.value1"
let attrRange1 = NSRange(location: 0, length: 20)

let attrKey2 = "attribute.placeholder.key2"
let attrKey2 = NSAttributedStringKey("attribute.placeholder.key2")
let attrValue2 = "attribute.placeholder.value2"
let attrRange2 = NSRange(location: 18, length: 10)

let attrKey3 = "attribute.placeholder.key3"
let attrKey3 = NSAttributedStringKey("attribute.placeholder.key3")
let attrValue3 = "attribute.placeholder.value3"
let attrRange3 = NSRange(location: 40, length: 5)

Expand Down Expand Up @@ -235,9 +234,9 @@ fileprivate extension TestNSAttributedString {
}
}

fileprivate func describe(attrs: [String : Any]) -> String {
fileprivate func describe(attrs: [NSAttributedStringKey : Any]) -> String {
if attrs.count > 0 {
return "[" + attrs.map({ "\($0):\($1)" }).sorted(by: { $0 < $1 }).joined(separator: ",") + "]"
return "[" + attrs.map({ "\($0.rawValue):\($1)" }).sorted(by: { $0 < $1 }).joined(separator: ",") + "]"
} else {
return "[:]"
}
Expand Down