Created
June 14, 2015 02:23
-
-
Save erica/be6c8d7435aad40fda7d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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