Skip to content

Basic implementations of NSAttributedString #334

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 6 commits into from
Apr 28, 2016
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
1 change: 1 addition & 0 deletions CoreFoundation/Base.subproj/SwiftRuntime/CoreFoundation.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@

#include <CoreFoundation/CFURLPriv.h>
#include <CoreFoundation/CFURLComponents.h>
#include <CoreFoundation/CFRunArray.h>

#include <CoreFoundation/ForSwiftFoundationOnly.h>

Expand Down
14 changes: 9 additions & 5 deletions Foundation.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
294E3C1D1CC5E19300E4F44C /* TestNSAttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 294E3C1C1CC5E19300E4F44C /* TestNSAttributedString.swift */; };
2EBE67A51C77BF0E006583D5 /* TestNSDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EBE67A31C77BF05006583D5 /* TestNSDateFormatter.swift */; };
528776141BF2629700CB0090 /* FoundationErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 522C253A1BF16E1600804FC6 /* FoundationErrors.swift */; };
528776191BF27D9500CB0090 /* Test.plist in Resources */ = {isa = PBXBuildFile; fileRef = 528776181BF27D9500CB0090 /* Test.plist */; };
Expand Down Expand Up @@ -66,9 +67,9 @@
5B5D89761BBDADD300234F36 /* libicucore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B5D89751BBDADD300234F36 /* libicucore.dylib */; };
5B5D89781BBDADDB00234F36 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B5D89771BBDADDB00234F36 /* libz.dylib */; };
5B6228BB1C179041009587FE /* CFRunArray.c in Sources */ = {isa = PBXBuildFile; fileRef = 5B6228BA1C179041009587FE /* CFRunArray.c */; };
5B6228BD1C179049009587FE /* CFRunArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 5B6228BC1C179049009587FE /* CFRunArray.h */; };
5B6228BD1C179049009587FE /* CFRunArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 5B6228BC1C179049009587FE /* CFRunArray.h */; settings = {ATTRIBUTES = (Private, ); }; };
5B6228BF1C179052009587FE /* CFAttributedString.c in Sources */ = {isa = PBXBuildFile; fileRef = 5B6228BE1C179052009587FE /* CFAttributedString.c */; };
5B6228C11C17905B009587FE /* CFAttributedString.h in Headers */ = {isa = PBXBuildFile; fileRef = 5B6228C01C17905B009587FE /* CFAttributedString.h */; };
5B6228C11C17905B009587FE /* CFAttributedString.h in Headers */ = {isa = PBXBuildFile; fileRef = 5B6228C01C17905B009587FE /* CFAttributedString.h */; settings = {ATTRIBUTES = (Public, ); }; };
5B7C8A721BEA7FCE00C5B690 /* CFBase.c in Sources */ = {isa = PBXBuildFile; fileRef = 5B5D895D1BBDABBF00234F36 /* CFBase.c */; };
5B7C8A731BEA7FCE00C5B690 /* CFFileUtilities.c in Sources */ = {isa = PBXBuildFile; fileRef = 5B5D89851BBDB18D00234F36 /* CFFileUtilities.c */; };
5B7C8A741BEA7FCE00C5B690 /* CFPlatform.c in Sources */ = {isa = PBXBuildFile; fileRef = 5B5D897B1BBDAE0800234F36 /* CFPlatform.c */; };
Expand Down Expand Up @@ -253,9 +254,9 @@
61E0117F1C1B5990000037DD /* CFRunLoop.c in Sources */ = {isa = PBXBuildFile; fileRef = 5B5D88D81BBC9AD800234F36 /* CFRunLoop.c */; };
61E011811C1B5998000037DD /* CFMessagePort.c in Sources */ = {isa = PBXBuildFile; fileRef = 5B5D88DC1BBC9AEC00234F36 /* CFMessagePort.c */; };
61E011821C1B599A000037DD /* CFMachPort.c in Sources */ = {isa = PBXBuildFile; fileRef = 5B5D88D01BBC9AAC00234F36 /* CFMachPort.c */; };
AE35A1861CBAC85E0042DB84 /* SwiftFoundation.h in Headers */ = {isa = PBXBuildFile; fileRef = AE35A1851CBAC85E0042DB84 /* SwiftFoundation.h */; settings = {ATTRIBUTES = (Public, ); }; };
7900433B1CACD33E00ECCBF1 /* TestNSCompoundPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 790043391CACD33E00ECCBF1 /* TestNSCompoundPredicate.swift */; };
7900433C1CACD33E00ECCBF1 /* TestNSPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7900433A1CACD33E00ECCBF1 /* TestNSPredicate.swift */; };
AE35A1861CBAC85E0042DB84 /* SwiftFoundation.h in Headers */ = {isa = PBXBuildFile; fileRef = AE35A1851CBAC85E0042DB84 /* SwiftFoundation.h */; settings = {ATTRIBUTES = (Public, ); }; };
CE19A88C1C23AA2300B4CB6A /* NSStringTestData.txt in Resources */ = {isa = PBXBuildFile; fileRef = CE19A88B1C23AA2300B4CB6A /* NSStringTestData.txt */; };
D31302011C30CEA900295652 /* NSConcreteValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D31302001C30CEA900295652 /* NSConcreteValue.swift */; };
D370696E1C394FBF00295652 /* NSKeyedUnarchiver-RangeTest.plist in Resources */ = {isa = PBXBuildFile; fileRef = D370696D1C394FBF00295652 /* NSKeyedUnarchiver-RangeTest.plist */; };
Expand Down Expand Up @@ -385,6 +386,7 @@

/* Begin PBXFileReference section */
22B9C1E01C165D7A00DECFF9 /* TestNSDate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSDate.swift; sourceTree = "<group>"; };
294E3C1C1CC5E19300E4F44C /* TestNSAttributedString.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSAttributedString.swift; sourceTree = "<group>"; };
2EBE67A31C77BF05006583D5 /* TestNSDateFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSDateFormatter.swift; sourceTree = "<group>"; };
400E22641C1A4E58007C5933 /* TestNSProcessInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSProcessInfo.swift; sourceTree = "<group>"; };
4AE109261C17CCBF007367B5 /* TestNSIndexPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSIndexPath.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1210,6 +1212,7 @@
5B40F9F11C125187000E72E3 /* TestNSXMLParser.swift */,
5B6F17961C48631C00935030 /* TestUtils.swift */,
E19E17DB1C2225930023AF4D /* TestNSXMLDocument.swift */,
294E3C1C1CC5E19300E4F44C /* TestNSAttributedString.swift */,
);
name = Tests;
sourceTree = "<group>";
Expand Down Expand Up @@ -1517,7 +1520,6 @@
5B7C8ACC1BEA80FC00C5B690 /* CFDateFormatter.h in Headers */,
5B7C8AD41BEA80FC00C5B690 /* CFXMLParser.h in Headers */,
5B7C8AD11BEA80FC00C5B690 /* CFTimeZone.h in Headers */,
5B6228C11C17905B009587FE /* CFAttributedString.h in Headers */,
5B7C8AC31BEA80FC00C5B690 /* CFBag.h in Headers */,
5B7C8AE01BEA80FC00C5B690 /* CFStringEncodingExt.h in Headers */,
5B7C8AE11BEA80FC00C5B690 /* CFURL.h in Headers */,
Expand Down Expand Up @@ -1552,12 +1554,12 @@
5B7C8AD21BEA80FC00C5B690 /* CFPropertyList.h in Headers */,
5B7C8AE51BEA81AC00C5B690 /* ForFoundationOnly.h in Headers */,
5B7C8AD51BEA80FC00C5B690 /* CFBundle.h in Headers */,
5B6228C11C17905B009587FE /* CFAttributedString.h in Headers */,
5B7C8AE21BEA80FC00C5B690 /* CFURLAccess.h in Headers */,
5B7C8ACA1BEA80FC00C5B690 /* CFError.h in Headers */,
5B7C8AE91BEA81AC00C5B690 /* CFLocaleInternal.h in Headers */,
5B7C8AE61BEA81AC00C5B690 /* ForSwiftFoundationOnly.h in Headers */,
5B7C8AC61BEA80FC00C5B690 /* CFData.h in Headers */,
5B6228BD1C179049009587FE /* CFRunArray.h in Headers */,
5B7C8AC01BEA807A00C5B690 /* CFUUID.h in Headers */,
5B7C8AD31BEA80FC00C5B690 /* CFXMLNode.h in Headers */,
5B7C8AC21BEA80FC00C5B690 /* CFArray.h in Headers */,
Expand Down Expand Up @@ -1587,6 +1589,7 @@
5B7C8AF71BEA81AC00C5B690 /* CFStringEncodingConverterExt.h in Headers */,
5B7C8AE31BEA81AC00C5B690 /* CFLogUtilities.h in Headers */,
5B7C8AD01BEA80FC00C5B690 /* CFNumber.h in Headers */,
5B6228BD1C179049009587FE /* CFRunArray.h in Headers */,
5B7C8ACE1BEA80FC00C5B690 /* CFNumberFormatter.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -1981,6 +1984,7 @@
5B13B3321C582D4C00651CE2 /* TestNSIndexSet.swift in Sources */,
5B13B3511C582D4C00651CE2 /* TestNSByteCountFormatter.swift in Sources */,
5B13B3501C582D4C00651CE2 /* TestUtils.swift in Sources */,
294E3C1D1CC5E19300E4F44C /* TestNSAttributedString.swift in Sources */,
5B13B3431C582D4C00651CE2 /* TestNSScanner.swift in Sources */,
5B13B3401C582D4C00651CE2 /* TestNSRange.swift in Sources */,
5B13B3371C582D4C00651CE2 /* TestNSNotificationCenter.swift in Sources */,
Expand Down
96 changes: 90 additions & 6 deletions Foundation/NSAttributedString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//

import CoreFoundation

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

private let _cfinfo = _CFInfo(typeID: CFAttributedStringGetTypeID())
private let _string: NSString
Copy link
Contributor Author

@eyeplum eyeplum Apr 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to use private let _string: String here, but encountered EXC_BAD_ACCESS at CFString function calls like CFStringGetLength ( inside the CF_SWIFT_FUNCDISPATCHV macro ).
I couldn't figure out why, but found out that with a NSString, everything seemed to be fine.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think https://bugs.swift.org/browse/SR-1129 could be related?

Copy link
Contributor Author

@eyeplum eyeplum Apr 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SR-1129 looks like a compile time error, what I encountered is a runtime error.
So, maybe they are not related.

My guess is that this is a memory related issue.

private let _attributeArray: CFRunArrayRef

public required init?(coder aDecoder: NSCoder) {
NSUnimplemented()
}
Expand Down Expand Up @@ -38,26 +43,105 @@ public class NSAttributedString : NSObject, NSCopying, NSMutableCopying, NSSecur
NSUnimplemented()
}

public var string: String { NSUnimplemented() }
public func attributesAtIndex(_ location: Int, effectiveRange range: NSRangePointer) -> [String : AnyObject] { NSUnimplemented() }
public var string: String {
return _string._swiftObject
}

public func attributesAtIndex(_ location: Int, effectiveRange range: NSRangePointer) -> [String : AnyObject] {
var cfRange = CFRange()
return withUnsafeMutablePointer(&cfRange) { (rangePointer: UnsafeMutablePointer<CFRange>) -> [String : AnyObject] in
// Get attributes value using CoreFoundation function
let value = CFAttributedStringGetAttributes(_cfObject, location, rangePointer)

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

// Update effective range
let hasAttrs = results.count > 0
range.pointee.location = hasAttrs ? rangePointer.pointee.location : NSNotFound
range.pointee.length = hasAttrs ? rangePointer.pointee.length : 0

return results
}
}

public var length: Int { NSUnimplemented() }
public func attribute(_ attrName: String, atIndex location: Int, effectiveRange range: NSRangePointer) -> AnyObject? { NSUnimplemented() }
public var length: Int {
return CFAttributedStringGetLength(_cfObject)
}

public func attribute(_ attrName: String, atIndex location: Int, effectiveRange range: NSRangePointer) -> AnyObject? {
var cfRange = CFRange()
return withUnsafeMutablePointer(&cfRange) { (rangePointer: UnsafeMutablePointer<CFRange>) -> AnyObject? in
// Get attribute value using CoreFoundation function
let attribute = CFAttributedStringGetAttribute(_cfObject, location, attrName._cfObject, rangePointer)

// Update effective range and return the result
if let attribute = attribute {
range.pointee.location = rangePointer.pointee.location
range.pointee.length = rangePointer.pointee.length
return attribute
} else {
range.pointee.location = NSNotFound
range.pointee.length = 0
return nil
}
}
}

public func attributedSubstringFromRange(_ range: NSRange) -> NSAttributedString { NSUnimplemented() }

public func attributesAtIndex(_ location: Int, longestEffectiveRange range: NSRangePointer, inRange rangeLimit: NSRange) -> [String : AnyObject] { NSUnimplemented() }
public func attribute(_ attrName: String, atIndex location: Int, longestEffectiveRange range: NSRangePointer, inRange rangeLimit: NSRange) -> AnyObject? { NSUnimplemented() }

public func isEqualToAttributedString(_ other: NSAttributedString) -> Bool { NSUnimplemented() }

public init(string str: String) { NSUnimplemented() }
public init(string str: String, attributes attrs: [String : AnyObject]?) { NSUnimplemented() }
public init(string str: String) {
_string = str._nsObject
_attributeArray = CFRunArrayCreate(kCFAllocatorDefault)

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

public init(string str: String, attributes attrs: [String : AnyObject]?) {
_string = str._nsObject
_attributeArray = CFRunArrayCreate(kCFAllocatorDefault)

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

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

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

let range = CFRange(location: 0, length: _string.length)
if let attrs = attrs {
CFRunArrayInsert(_attributeArray, range, attrs._cfObject)
} else {
let emptyAttrs = [String : AnyObject]()
CFRunArrayInsert(_attributeArray, range, emptyAttrs._cfObject)
}
}

public func enumerateAttributesInRange(_ enumerationRange: NSRange, options opts: NSAttributedStringEnumerationOptions, usingBlock block: ([String : AnyObject], NSRange, UnsafeMutablePointer<ObjCBool>) -> Void) { NSUnimplemented() }
public func enumerateAttribute(_ attrName: String, inRange enumerationRange: NSRange, options opts: NSAttributedStringEnumerationOptions, usingBlock block: (AnyObject?, NSRange, UnsafeMutablePointer<ObjCBool>) -> Void) { NSUnimplemented() }
}

extension NSAttributedString: _CFBridgable {
internal var _cfObject: CFAttributedString { return unsafeBitCast(self, to: CFAttributedString.self) }
}

public struct NSAttributedStringEnumerationOptions : OptionSet {
public let rawValue : UInt
public init(rawValue: UInt) { self.rawValue = rawValue }
Expand Down
82 changes: 82 additions & 0 deletions TestFoundation/TestNSAttributedString.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//



#if DEPLOYMENT_RUNTIME_OBJC || os(Linux)
import Foundation
import XCTest
#else
import SwiftFoundation
import SwiftXCTest
#endif



class TestNSAttributedString : XCTestCase {

static var allTests: [(String, TestNSAttributedString -> () throws -> Void)] {
return [
("test_initWithString", test_initWithString),
("test_initWithStringAndAttributes", test_initWithStringAndAttributes)
]
}

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)
XCTAssertEqual(attrString.string, string)
XCTAssertEqual(attrString.length, string.utf16Count)

var range = NSRange()
let attrs = attrString.attributesAtIndex(0, effectiveRange: &range)
Copy link
Contributor Author

@eyeplum eyeplum Apr 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that this will cause a compile error with the current swift toolchain:

let attrs = attrString.attributesAtIndex(0, effectiveRange: nil)

But the darwin counterpart can actually accept nil as a valid argument.
Not sure if a change is needed in the function signature.

XCTAssertEqual(range.location, NSNotFound)
XCTAssertEqual(range.length, 0)
XCTAssertEqual(attrs.count, 0)

let attribute = attrString.attribute("invalid", atIndex: 0, effectiveRange: &range)
XCTAssertNil(attribute)
XCTAssertEqual(range.location, NSNotFound)
XCTAssertEqual(range.length, 0)
}

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 attrString = NSAttributedString(string: string, attributes: attributes)
XCTAssertEqual(attrString.string, string)
XCTAssertEqual(attrString.length, string.utf16Count)

var range = NSRange()
let attrs = attrString.attributesAtIndex(0, effectiveRange: &range)
guard let value = attrs["attribute.placeholder.key"] as? NSString 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", atIndex: 0, effectiveRange: &range)
XCTAssertNil(invalidAttribute)
XCTAssertEqual(range.location, NSNotFound)
XCTAssertEqual(range.length, 0)

let attribute = attrString.attribute("attribute.placeholder.key", atIndex: 0, effectiveRange: &range)
XCTAssertEqual(range.location, 0)
XCTAssertEqual(range.length, attrString.length)
guard let validAttribute = attribute as? NSString else {
XCTAssert(false, "attribuet not found")
return
}
XCTAssertEqual(validAttribute, "attribute.placeholder.value")
}

}
1 change: 1 addition & 0 deletions TestFoundation/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,5 @@ XCTMain([
testCase(TestNSUserDefaults.allTests),
testCase(TestNSXMLParser.allTests),
testCase(TestNSXMLDocument.allTests),
testCase(TestNSAttributedString.allTests),
])