I have a UITextView and TextKit with an UITextKit-Style (NSStrikethroughStyleAttributeName):
This is my code:
@IBOutlet weak var textView: UITextView!
var dict = [String: AnyObject]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let newFont = UIFont(name:"HelveticaNeue", size: textView.font!.pointSize)
self.textView.font = newFont
dict[NSFontAttributeName] = newFont
let selectedRange: NSRange = NSMakeRange(12,6)
self.makeStrikeThrough(selectedRange)
}
func makeStrikeThrough(selectedRange: NSRange) {
dict[NSStrikethroughStyleAttributeName] = 2
self.textView.textStorage.beginEditing()
self.textView.textStorage.setAttributes(dict, range: selectedRange)
self.textView.textStorage.endEditing()
}
Now i must have a way to detect this Font-Attribute. Is there any way to get the Info:
In the selectedRange: NSRange 12, 6 i use the Attribute NSStrikethroughStyleAttributeName with the property 2, perhaps as an Array-Entry???
Any idea is welcome!
You can make use of repeated use of the attributesAtIndex(location:effectiveRange:)
method of NSAttributedString
to encode the range over the full attributed string into a list of sub-ranges which each hold a set of attributes and values to these.
Declaration:
func attributesAtIndex(location: Int, effectiveRange range: NSRangePointer) -> [String : AnyObject]
Description:
Returns the attributes for the character at a given index.
Return value:
The attributes for the character at
index
.
More specifically, use attributesAtIndex(...)
to create an extension to NSAttributedString
that returns and array of tuples, with tuples defined as
[String: AnyObject]
array, corresponding to attributes and their respective values (both variations included below)Ranges in the attributed string which are attributed by several attributes will naturally return an inner tuple array of several elements, whereas ranges that are not attributed at all will return an empty inner tuple array.
The extension(s, two alternatives) as follows:
/* let 2nd tuple be an array of tuples itself */
extension NSAttributedString {
func getAttributes() -> [(NSRange, [(String, AnyObject)])] {
var attributesOverRanges : [(NSRange, [(String, AnyObject)])] = []
var rng = NSRange()
var idx = 0
while idx < self.length {
let foo = self.attributesAtIndex(idx, effectiveRange: &rng)
var attributes : [(String, AnyObject)] = []
for (k, v) in foo { attributes.append(k, v) }
attributesOverRanges.append((rng, attributes))
idx = max(idx + 1, rng.toRange()?.endIndex ?? 0)
}
return attributesOverRanges
}
}
/* or, let 2nd tuple be a [String: AnyObject] dictionary */
extension NSAttributedString {
func getAttributes() -> [(NSRange, [String: AnyObject])] {
var attributesOverRanges : [(NSRange, [String: AnyObject])] = []
var rng = NSRange()
var idx = 0
while idx < self.length {
let foo = self.attributesAtIndex(idx, effectiveRange: &rng)
attributesOverRanges.append((rng, foo))
idx = max(idx + 1, rng.toRange()?.endIndex ?? 0)
}
return attributesOverRanges
}
}
Example usage:
/* Example setup */
let fooString = "foo foo foo foo foo foo foo"
var fooAttrString = NSMutableAttributedString(string: fooString)
let selectedRange: NSRange = NSMakeRange(12,6)
// attr1: strikethrough over range (12,6) (12..<18)
var myRange = NSRange(location: 12, length: 6)
let strikeThroughAttr = [ NSStrikethroughStyleAttributeName: 2 ]
fooAttrString.addAttributes(strikeThroughAttr, range: myRange)
// attr2: font over range (16,8) (16..<24)
myRange = NSRange(location: 16, length: 8)
let fontAttr = [ NSFontAttributeName: UIFont(name:"HelveticaNeue", size: 20)! ]
fooAttrString.addAttributes(fontAttr, range: myRange)
/* Example usage: extension */
let attributesOverRanges = fooAttrString.getAttributes()
for (rng, attributes) in attributesOverRanges {
print("Attributes over range \(rng):")
attributes.forEach { print("\t\($0.0) = \($0.1)") }
}
/* Attributes over range (0,12):
Attributes over range (12,4):
NSStrikethrough = 2
Attributes over range (16,2):
NSFont = <UICTFont: 0x7fcfd860f0b0> font-family: "Helvetica Neue"; font-weight: normal; font-style: normal; font-size: 20.00pt
NSStrikethrough = 2
Attributes over range (18,6):
NSFont = <UICTFont: 0x7fcfd860f0b0> font-family: "Helvetica Neue"; font-weight: normal; font-style: normal; font-size: 20.00pt
Attributes over range (24,3): */
UITextView
instance, specifically property textStorage
Now, the textStorage
property of UITextView
is of type NSTextStorage
, which is a (semi concrete) subclass of NSMutableAttributedString
, which itself is a subclass of NSAttributedString
. Hence, the extension getAttributes()
above will be accessible and work just as well on NSTextStorage
instances, e.g. textView.textStorage
in your question.
Hence, using the same extensions as above, we set up a similar example but for an UITextView
with an attributed textStorage
property.
/* Example setup: UITextView:s 'textStorage' (type NSTextStorage) */
let fooString = "foo foo foo foo foo foo foo"
// attr1: strikethrough over range (12,6) (12..<18)
let strikeThroughRng = NSRange(location: 12, length: 6)
let strikeThroughAttr = [ NSStrikethroughStyleAttributeName: 2 ]
// attr2: font over range (16,8) (16..<24)
let fontRng = NSRange(location: 16, length: 8)
let fontAttr = [ NSFontAttributeName: UIFont(name:"HelveticaNeue", size: 20)! ]
// create text view and set attributes
let textView = UITextView()
textView.text = fooString
textView.textStorage.beginEditing()
textView.textStorage.addAttributes(strikeThroughAttr, range: strikeThroughRng)
textView.textStorage.addAttributes(fontAttr, range: fontRng)
textView.textStorage.endEditing()
Example usage, extension:
/* Example usage: extension (uses first version above) */
let attributesOverRanges = textView.textStorage.getAttributes()
for (rng, attributes) in attributesOverRanges {
print("Attributes over range \(rng):")
attributes.forEach { print("\t\($0.0) = \($0.1)") }
}
/* Attributes over range (0,12):
NSOriginalFont = <UICTFont: 0x7ff610d88e20> font-family: "Helvetica"; font-weight: normal; font-style: normal; font-size: 12.00pt
NSFont = <UICTFont: 0x7ff610d88e20> font-family: "Helvetica"; font-weight: normal; font-style: normal; font-size: 12.00pt
Attributes over range (12,4):
NSOriginalFont = <UICTFont: 0x7ff610d88e20> font-family: "Helvetica"; font-weight: normal; font-style: normal; font-size: 12.00pt
NSFont = <UICTFont: 0x7ff610d88e20> font-family: "Helvetica"; font-weight: normal; font-style: normal; font-size: 12.00pt
NSStrikethrough = 2
Attributes over range (16,2):
NSFont = <UICTFont: 0x7ff610d80820> font-family: "Helvetica Neue"; font-weight: normal; font-style: normal; font-size: 20.00pt
NSStrikethrough = 2
Attributes over range (18,6):
NSFont = <UICTFont: 0x7ff610d80820> font-family: "Helvetica Neue"; font-weight: normal; font-style: normal; font-size: 20.00pt
Attributes over range (24,3):
NSOriginalFont = <UICTFont: 0x7ff610d88e20> font-family: "Helvetica"; font-weight: normal; font-style: normal; font-size: 12.00pt
NSFont = <UICTFont: 0x7ff610d88e20> font-family: "Helvetica"; font-weight: normal; font-style: normal; font-size: 12.00pt */
As expected, we see the same results as in the NSAttributedString
example above, with the difference that the textView.textStorage
contains some default attributes (NSFont
, NSOriginalFont
).
If you'd like you could also write an extension to search an attributed string for a specific attribute and value, making use of the attribute(attrName:atIndex:effectiveRange:)
method of NSAttributedString
Declaration:
func attribute(attrName: String, atIndex location: Int, effectiveRange range: NSRangePointer) -> AnyObject?
Description:
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.
Return value:
The value for the attribute named
attributeName
of the character atindex
, ornil
if there is no such attribute.
More specifically, creating an extension that
NSFontAttributeName
) for a given value (e.g. 2
), and returns the range (NSRange
) for the first occurrence of such an attributed portion in the given attributed string, or nil
, if none can be found.NSAttributedString
extension as follows
/* find the range of (the first occurence of) a given
attribute 'attrName' for a given value 'forValue'. */
extension NSAttributedString {
func findRangeOfAttribute(attrName: String, forValue value: AnyObject) -> NSRange? {
var rng = NSRange()
/* Is attribute (with given value) in range 0...X ? */
if let val = self.attribute(attrName, atIndex: 0, effectiveRange: &rng) where val.isEqual(value) { return rng }
/* If not, is attribute (with given value) anywhere in range X+1..<end? */
else if
let from = rng.toRange()?.endIndex where from < self.length - 1,
let val = self.attribute(attrName, atIndex: from, effectiveRange: &rng) where val.isEqual(value) { return rng }
/* if none of the above, return nil */
return nil
}
}
Example usage:
/* Example */
let fooString = "foo foo foo foo foo foo foo"
var fooAttrString = NSMutableAttributedString(string: fooString)
let selectedRange: NSRange = NSMakeRange(12,6)
let myRange = NSRange(location: 12, length: 6)
let attr = [ NSStrikethroughStyleAttributeName: 2 ]
fooAttrString.addAttributes(attr, range: myRange)
/* Example usage: extension */
if let rngOfFirstStrikethrough = fooAttrString.findRangeOfAttribute(NSStrikethroughStyleAttributeName, forValue: 2) {
print(rngOfStrikethrough) // (12,6)
}