create an attributed string out of plain (Android formatted) text in swift for iOS
ios, nsattributedstring, nsmutableattributedstring, swift, xcode
Solution
Regarding your first method with `NSAttributedString`:
- The `\n` character in HTML is just ordinary white space. To get a line break you would have to replace it by `<br />` first.
- The font attributes can be controlled by a HTML `<span>`, see Parsing HTML into NSAttributedText - how to set font?.
This gives (now updated for Swift 2):
func convertText(inputText: String) -> NSAttributedString {
var html = inputText
// Replace newline character by HTML line break
while let range = html.rangeOfString("\n") {
html.replaceRange(range, with: "<br />")
}
// Embed in a <span> for font attributes:
html = "<span style=\"font-family: Helvetica; font-size:14pt;\">" + html + "</span>"
let data = html.dataUsingEncoding(NSUnicodeStringEncoding, allowLossyConversion: true)!
let attrStr = try? NSAttributedString(
data: data,
options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],
documentAttributes: nil)
return attrStr!
}
Regarding your second method:
There are two different `rangeOfString()` methods, one for (Swift) `String` and one for (Foundation) `NSString`. The `String` method returns a `Range<String.Index>` and the `NSString` method returns an `NSRange`.
Converting between these two is possible but complicated. The reason is that in a `String` each "extended grapheme cluster" counts as one character, whereas in `NSString` each UTF-16 unit is counted. An extended grapheme cluster can be one or more UTF-16 unit ("" is two UTF-16 units, "" is four).
The `addAttribute()` method accepts only an `NSRange`. The easiest method to solve this problem is to convert the Swift string to `NSString` and work with `NSRange` only. Then your method could look like this:
func convertText(inputText: String) -> NSAttributedString {
let attrString = NSMutableAttributedString(string: inputText)
let boldFont = UIFont(name: "Helvetica-Bold", size: 14.0)!
var r1 = (attrString.string as NSString).rangeOfString("<b>")
while r1.location != NSNotFound {
let r2 = (attrString.string as NSString).rangeOfString("</b>")
if r2.location != NSNotFound && r2.location > r1.location {
let r3 = NSMakeRange(r1.location + r1.length, r2.location - r1.location - r1.length)
attrString.addAttribute(NSFontAttributeName, value: boldFont, range: r3)
attrString.replaceCharactersInRange(r2, withString: "")
attrString.replaceCharactersInRange(r1, withString: "")
} else {
break
}
r1 = (attrString.string as NSString).rangeOfString("<b>")
}
return attrString
}
Problem
I am reading strings out of a Localizable.strings which contains something like that which is basically what you have in an strings.xml of an Android app ``` "testShort" = "A <b>short</b>\ntest with another<b>bold text</b>"; ``` The bold and and line feed are the only two formatting attributes I have in my texts. I am trying to develop a method like this for days now without success: ``` func ConvertText(inputText: String) -> NSAttributedString { // here comes the conversion to a representation with Helvetica 14pt and Helvetica-Bold 14pt including line feeds. } ``` My final goal is to display the text in an UITextView's attributedText. Being kinda new to Swift and iOS without knowing Objective-C I found its very difficult to do String manipulations as they are quite different and complex and all examples are in Objective-C. What makes it even harder is that most API methods are not available or different in Swift than in Objective-C... Here is what I tried so far for the method body with the help of a lot of other posts here: ``` var test = inputText.dataUsingEncoding(NSUnicodeStringEncoding, allowLossyConversion: true)! attrStr = NSAttributedString( data: test, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil, error: nil)! return attrStr ``` The main issues here are that `\n` isn't converted and the font is very small and different. Next I tried to manually bold a part of a text. It seem to work like that: ``` var newText = NSMutableAttributedString(string: inputText) newText.addAttribute(NSFontAttributeName, value: UIFont(name: "Helvetica-Bold", size: 14.0)!, range: NSRange(location:2,length:4)) ``` Now I tried to search for the attributes in the text, deleting them and use the addAttribute kinda like that ``` // Start of bold text var range = newText.rangeOfString("<b>")! // End of bold text var range2 = newText.rangeOfString("</b>")! // replacing attributes newText = newText.stringByReplacingCharactersInRange(range, withString: "") newText = newText.stringByReplacingCharactersInRange(range2, withString: "") // creating a range for bold => error "'String.Index' is not convertible to 'int'" // how to do such a thing var boldRange = NSMakeRange(range.startIndex, range2.endIndex -3) // setting bold newText.addAttribute(NSFontAttributeName, value: UIFont(name: "Helvetica-Bold", size: 14.0)!, range: boldRange) ``` This whole range thing is my main issue at the moment as its quite different to a simple position in the string. This issue is a great example for the lack of (or well hidden) documentation: The `addAttribute` wants an NSRange, the `rangeOfString` seems to deliver a generic Range according to an error message I get - but there is no info about it. The Search Documentation button in Xcode on `rangeOfString()` leads to NSString. Searching in there for `rangeOfString()`says it returns `NSRange`. Clicking on that leads to the info of a type alias for `_NSRange` which in turn has two NSUInteger properties named `location` and `length`. Where is the startIndex and endIndex property I see in XCode? Very confusing... Would be great if you can give me some snippets or hints where I'm wrong here or even the method body as I'm still hoping its not too difficult if you know iOS and Swift well. I'm aiming for iOS 7.1 support but if its way easier with iOS 8 only its fine as well.