Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
import UIKit
public struct SmartString : CustomStringConvertible, Equatable {
internal var textStorage = NSTextStorage()
internal let layoutMgr = NSLayoutManager()
internal let container = NSTextContainer(size: CGSizeMake(.max, .max))
internal var glyphs = [NSRange]()
public var description : String {return string}
public var isEmpty : Bool {return string.isEmpty}
public var length : Int {return glyphs.count}
mutating internal func updateGlyphRanges() {
glyphs = [NSRange](); if isEmpty {return}
var range = NSMakeRange(0, 0); var bounds = CGRectZero
for index in 0..<layoutMgr.numberOfGlyphs {
let testBounds = layoutMgr.boundingRectForGlyphRange(
NSMakeRange(index, 1), inTextContainer: container)
if !CGRectEqualToRect(bounds, testBounds) {
if range.length != 0 {glyphs.append(range)}
range.location = index; range.length = 1
bounds = testBounds
} else {
range.length += 1
}
}
if glyphs.last!.location != range.location {
glyphs.append(range)
}
}
public var string : String {
didSet {
textStorage.setAttributedString(NSAttributedString(string: string))
layoutMgr.textStorage = textStorage // bug workaround
updateGlyphRanges()
}
}
public init (_ string : String) {
self.string = string
textStorage.setAttributedString(NSAttributedString(string: string))
layoutMgr.textStorage = textStorage
layoutMgr.addTextContainer(container)
updateGlyphRanges()
}
public subscript (i: Int) -> String {
get {return textStorage.attributedSubstringFromRange(glyphs[i]).string}
set {
textStorage.replaceCharactersInRange(glyphs[i], withString: newValue)
string = textStorage.string
}
}
public subscript (aRange: Range<Int>) -> String {
get {
var s = ""
for index in aRange {s += self[index]}
return s
}
set {
for index in aRange.reverse() {self[index] = ""}
let index = glyphs[aRange.startIndex].location
textStorage.insertAttributedString(NSAttributedString(string:newValue), atIndex: index)
string = textStorage.string
}
}
static public func test() {
var x = SmartString("hello")
assert(x.string == "hello", "basic assignment failed")
assert(x.length == 5, "basic length test failed")
assert(x == "hello", "compare against string failed (1)")
assert("hello" == x, "compare against string failed (2)")
// string values can be updated
x.string = "bye"
assert(x.string == "bye", "assign test failed")
// items indexed by int
assert(x[2] == "e", "single index subscript failed")
// items assigned by int
x[2] = "👯👨‍👨‍👧‍👦"
assert(x.string == "by👯👨‍👨‍👧‍👦", "single index subscript assignment failed")
// length handles composed sequences
x.string.characters.count // normal count is 7. composed count is 4
assert(x.length == 4, "calculated length of composed string failed")
// items indexed by range
assert(x[1...2] == "y👯", "range index test failed")
// items assigned by range
x[1...2] = "XXXX"
assert(x[1...4] == "XXXX", "range index test failed")
}
}
public func == (lhs: SmartString, rhs: SmartString) -> Bool {return lhs.string == rhs.string}
public func == (lhs: SmartString, rhs: String) -> Bool {return lhs.string == rhs}
public func == (lhs: String, rhs: SmartString) -> Bool {return lhs == rhs.string}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.