I am DTCoreText to transform HTML to attributed text. Because I want to set the font up front, not afterwards as that would override all bold, italic etcetera tags I want to set the document attributes in the constructor. This constructors wants me to give a AutoreleasingUnsafeMutablePointer that more or less seems to be a NSDictionary? with & up front. Sort of. Only it doesn't let me set it in any way. I've tried .memory, tried to cast the dictionary in any possible way and it just doesn't accept any data.
let font = UIFont.systemFontOfSize(12)
let data = info.desc?.dataUsingEncoding(NSUTF8StringEncoding)
let attributes: NSMutableDictionary? = NSMutableDictionary()
attributes!.setObject(font, forKey: NSFontAttributeName)
var attributeRef: AutoreleasingUnsafeMutablePointer<NSDictionary?> = AutoreleasingUnsafeMutablePointer.null()
NSMutableAttributedString(HTMLData: data, documentAttributes: nil)
//attributeRef = *attributeDict
let attributedString = NSMutableAttributedString(HTMLData: data, documentAttributes:attributeRef)
let paragraphStyle: NSMutableParagraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakMode = NSLineBreakMode.ByWordWrapping;
let range = NSMakeRange(0, attributedString.length)
attributedString.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: range)
lblMessage.attributedText = attributedString
You should not be using DTCoreText at this point; iOS now has native calls for this. Just say var dict = NSDictionary?() and pass &dict. Here's example code:
let s = "<html><body><h1>Howdy</h1><p>Hello</p></body></html>"
let d = s.dataUsingEncoding(NSUTF16StringEncoding, allowLossyConversion: false)
var dict = NSDictionary?()
let att = NSAttributedString(data: d!, options: [
NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType
], documentAttributes: &dict, error: nil)
println(att!)
println(dict!)
You'll see that this works perfectly well. Here is dict:
BottomMargin = 72;
Converted = "-1";
DocumentType = NSHTML;
LeftMargin = 90;
PaperMargin = "UIEdgeInsets: {72, 90, 72, 90}";
PaperSize = "NSSize: {612, 792}";
RightMargin = 90;
TopMargin = 72;
UTI = "public.html";
However, I usually pass nil because nothing is coming back in the second dictionary that I really care about.
To update fonts in generated HTML use the code similar to the following:
Swift 5
extension String {
/// Convert HTML to NSAttributedString
func convertHtml() -> NSAttributedString {
guard let data = data(using: .utf8) else { return NSAttributedString() }
if let attributedString = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
let string = NSMutableAttributedString(attributedString: attributedString)
// Apply text color
string.addAttributes([.foregroundColor: UIColor.text], range: NSRange(location: 0, length: attributedString.length))
// Update fonts
let regularFont = UIFont(name: Fonts.Regular, size: 13)! // DEFAULT FONT (REGUALR)
let boldFont = UIFont(name: Fonts.Bold, size: 13)! // BOLD FONT
/// add other fonts if you have them
string.enumerateAttribute(.font, in: NSMakeRange(0, attributedString.length), options: NSAttributedString.EnumerationOptions(rawValue: 0), using: { (value, range, stop) -> Void in
/// Update to our font
// Bold font
if let oldFont = value as? UIFont, oldFont.fontName.lowercased().contains("bold") {
string.removeAttribute(.font, range: range)
string.addAttribute(.font, value: boldFont, range: range)
}
// Default font
else {
string.addAttribute(.font, value: regularFont, range: range)
}
})
return string
}
return NSAttributedString()
}
}
Related
I have some text:
New Content - Published Today | 10 min read
I'd like to apply styles to everything after and including the pipe, so | 10 min read
I have tried the below but it has only styles the pipe itself.
func makeAttributedText(using baseString: String?) -> NSMutableAttributedString? {
guard let baseString = baseString else { return nil }
let attributedString = NSMutableAttributedString(string: baseString, attributes: nil)
let timeToReadRange = (attributedString.string as NSString).range(of: "|")
attributedString.setAttributes([NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 18)], range: timeToReadRange)
return attributedString
}
Rather than getting the range of a single character get the index of the character and create a range from that index to the end of the string.
func makeAttributedText(using baseString: String?) -> NSMutableAttributedString? {
guard let baseString = baseString else { return nil }
let attributedString = NSMutableAttributedString(string: baseString, attributes: nil)
guard let timeToReadIndex = baseString.firstIndex(of: "|") else { return attributedString }
let timeToReadRange = NSRange(timeToReadIndex..., in: baseString)
attributedString.setAttributes([NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 18)], range: timeToReadRange)
return attributedString
}
Note:
Swift has dedicated methods to convert Range<String.Index> to NSRange. There's no reason for the bridge cast to NSString
I want to highlight and unhighlight the searched text in UITableView.
For Highlighting the searched text, i have tried below code in CellForRow and Highlighting searched text works fine.
let range = (self.modelClass.users[indexPath.row].name as NSString).range(of: self.searchBar.text!, options: .caseInsensitive)
let attributedString = NSMutableAttributedString(string: self.modelClass.users[indexPath.row].name)
attributedString.addAttributes([NSAttributedString.Key.backgroundColor: Colors.highlightedColor, NSAttributedString.Key.font: cell.lblName.font], range: range)
cell.lblName.attributedText = attributedString
EDIT 1
if searchBar.text != "" {
let range = (self.modelClass.users[indexPath.row].name as NSString).range(of: self.searchBar.text!, options: .caseInsensitive)
let attributedString = NSMutableAttributedString(string: self.modelClass.users[indexPath.row].name)
attributedString.addAttributes([NSAttributedString.Key.backgroundColor: Colors.highlightedColor, NSAttributedString.Key.font: cell.lblName.font], range: range)
cell.lblName.attributedText = attributedString
}
else {
cell.lblName.text = self.modelClass.users[indexPath.row].name
}
EDIT 2
Actually me 1st code is working fine. First i assign normal text to lblName.text and then after again i set attributedText to lblName.attributedText. When i comment the normal text assign, it works fine.
But when i clear searched data and load my default array but the highlighted color wasn't clear in lblName UILabel.
Please guide me how can i unhighlight the UILabel text?
in cellForRowAt, You also need to pass an else condition for the default label view.
if self.searchBar.text != "" {
let range = (self.modelClass.users[indexPath.row].name as NSString).range(of:
self.searchBar.text!, options: .caseInsensitive)
let attributedString = NSMutableAttributedString(string:
self.modelClass.users[indexPath.row].name)
attributedString.addAttributes([NSAttributedString.Key.backgroundColor:
Colors.highlightedColor, NSAttributedString.Key.font: cell.lblName.font], range: range)
cell.lblName.attributedText = attributedString
}
else {
let attributeString: NSMutableAttributedString = NSMutableAttributedString(string: self.modelClass.users[indexPath.row].name as NSString)
attributeString.removeAttribute(NSAttributedString.Key.backgroundColor, range: NSMakeRange(0, attributeString.length))
cell.lblName.attributedText = attributeString
}
Reload TableView when searchBar text changes.
I wrote an extension for UILabel to manage this
extension UILabel {
func stringWithSearchBarString(_ string: String) {
let attributedString = NSMutableAttributedString(string: text ?? "")
if string.count == 0 {
attributedText = attributedString
return
}
let dotRanges: [NSRange]
do {
let regex = try NSRegularExpression(pattern: string.lowercased(), options: [])
dotRanges = regex.matches(in: string.lowercased(), options: [], range: NSMakeRange(0, string.count)).map {$0.range}
} catch {
dotRanges = []
}
let rangeColor = WXColors.mainAppColor.color
for dotRange in dotRanges {
attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: rangeColor, range: dotRange)
}
attributedText = attributedString
}
}
USAGE:
cell.textLabel.stringWithSearchBarString(searcher.text)
Before, I changed specific strings to NSTextAttachment that include image to display custom emoticon.
String to NSTextAttachment code
{
guard
let original = self.attributedText
else { return }
let pattern = "\\[img src=(\\w+)\\]"
do{
let regex = try NSRegularExpression(pattern: pattern, options: [])
let matches = regex.matches(in: original.string, options : [], range : NSMakeRange(0, original.string.characters.count))
let attributeString = NSMutableAttributedString(attributedString: original)
for match in matches.reversed(){
let emoticonString = attributeString.attributedSubstring(from: match.rangeAt(1)).string
if let emoticonAndroid = Emoticon(rawValue: emoticonString),
let image = UIImage(named : "\(emoticonAndroid.convertFromAndroid().rawValue)_000"){
image.accessibilityIdentifier = emoticonAndroid.rawValue
let attributedImage = NSTextAttachment()
attributedImage.image = image
attributedImage.bounds = CGRect(x: 0, y: -8, width: 25, height: 25)
attributeString.beginEditing()
attributeString.replaceCharacters(in: match.rangeAt(0), with: NSAttributedString(attachment: attributedImage))
attributeString.endEditing()
}
}
self.attributedText = attributeString
}catch{
return
}
}
but, I need to replace NSTextAttachment to string to send message.
I used NSMutableAttributedString.replaceCharacters(in:with:) method. but, It can work with only one emoticon image.
one emoticon
two emoticons or more
how can I fix it?
NSTextAttachment to String code
{
if let original = self.attributedText{
let attributeString = NSMutableAttributedString(attributedString: original)
original.enumerateAttribute(NSAttachmentAttributeName, in: NSMakeRange(0, original.length), options: [], using: { attribute, range, _ in
if let attachment = attribute as? NSTextAttachment,
let image = attachment.image{
let str = "[img src=\(image.accessibilityIdentifier!)]"
attributeString.beginEditing()
attributeString.(in: range, with: str)
attributeString.endEditing()
}
})
self.attributedText = attributeString
return attributeString.string
}else{
return nil
}
}
Umm.. I solved this problem.
First : Count number of NSTextAttachment
var count = 0
self.attributedText.enumerateAttribute(NSAttachmentAttributeName, in : NSMakeRange(0, self.attributedText.length), options: [], using: { attribute, range, _ in
if let attachment = attribute as? NSTextAttachment,
let image = attachment.image{
count = count + 1
}
})
return count
Second : Replace NSTextAttachment with String and calculate the changed range. <- Repeat
for i in 0..<self.countOfNSTextAttachment(){
let attributedString = NSMutableAttributedString(attributedString: self.attributedText)
var count = 0
attributedString.enumerateAttribute(NSAttachmentAttributeName, in : NSMakeRange(0, attributedString.length), options: [], using: { attribute, range, _ in
if let attachment = attribute as? NSTextAttachment,
let image = attachment.image{
let str = "[img src=\(image.accessibilityIdentifier!)]"
if count == 0{
attributedString.beginEditing()
attributedString.replaceCharacters(in: range, with: NSAttributedString(string : str))
attributedString.endEditing()
self.attributedText = attributedString
}else{
return
}
count = count + 1
}
})
}
return self.attributedText.string
Result : result
Perfect!!
I'm trying to apply formatting on the selected range of the textview. The problem is when the selected text format applied, the rest of text reset its format.
Here is my code:
if let text = textView.text {
if let textRange = textView.selectedTextRange {
if let selectedText = textView.text(in: textRange) {
let range = (text as NSString).range(of: selectedText)
let attributedString = NSMutableAttributedString(string:text)
attributedString.addAttribute(NSFontAttributeName, value: UIFont.systemFont(ofSize: 18) , range: range)
self.textView.attributedText = attributedString
}
}
}
let range = textView.selectedRange
let string = NSMutableAttributedString(attributedString:
textView.attributedText)
let attributes = [NSForegroundColorAttributeName: UIColor.redColor()]
string.addAttributes(attributes, range: textView.selectedRange)
textView.attributedText = string
textView.selectedRange = range
I have a UITextView and I have a string of HTML I give it like so...
func applyHTML() {
textView.attributedText = htmlString()
textView.linkTextAttributes = [NSForegroundColorAttributeName: .blue,
NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue,
NSFontAttributeName:font]
}
func htmlString() -> NSAttributedString? {
if let htmlData = text.dataUsingEncoding(NSUnicodeStringEncoding) {
do {
let attributedString = try NSMutableAttributedString(data: htmlData,
options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],
documentAttributes: nil)
return attributedString
}
catch { return nil }
}
return nil
}
This works fine, I get blue hyperlinks with black text... what I want to do now is style the font for all and colour of the non-hyperlink text... problem is when I do that, by adding this....
let length = attributedString.length
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .Center
attributedString.setAttributes([NSForegroundColorAttributeName:.white,NSFontAttributeName: myFont,NSParagraphStyleAttributeName: paragraphStyle], range: NSRange(location: 0, length: length))
it removes the hyperlinks... I want them to work in tandem. What am I doing wrong?
For anyone who visits, #Larme was right
attributedString.setAttributes([NSForegroundColorAttributeName:.white,NSFontAttributeName: myFont,NSParagraphStyleAttributeName: paragraphStyle], range: NSRange(location: 0, length: length))
should be
attributedString.addAttributes([NSForegroundColorAttributeName:.white,NSFontAttributeName: myFont,NSParagraphStyleAttributeName: paragraphStyle], range: NSRange(location: 0, length: length))