Nested NSAttributedString (s) - swift

I am producing a PDF Invoice from my Swift app. Because not all address patched will be the same depth I am using carriage returns to layout the text. Address, Date, Invoice Number, Subject line and narrative, for example. it all works really well, however, I want to format the Subject line in bold text. I have looked all around this subject and drawn a blank. In essence my question is can you nest NSAttributed Strings, so that I can format the subjectline in Bold without having to create another NSAttributed String which would mean loosing my layout discipline?
This is my code
let textFont = UIFont.systemFont(ofSize: 10.0, weight: .regular)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .natural
paragraphStyle.lineBreakMode = .byWordWrapping
let textAttributes = [NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.font: textFont,
NSAttributedString.Key.kern : -0.15] as [NSAttributedString.Key : Any]
let attributedText = NSAttributedString(string: "\(billAddress)\r\r\r\("Invoice number: \ .
(invoiceNumber)")\r\("Date: \(mydate)")\r\r\r\("To")\r\r\(subjectline)\r\r\(narrative)",
attributes:
textAttributes as [NSAttributedString.Key : Any])
Any help/ideas/solution would be welcome

You can use NSMutableAttributedString, and append different parts of string using .append(_:) method. (https://developer.apple.com/documentation/foundation/nsmutableattributedstring/1417879-append)
Also you can take a look at https://github.com/Rightpoint/BonMot

let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .natural
paragraphStyle.lineBreakMode = .byWordWrapping
let defaultAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.font: UIFont.systemFont(ofSize: 10.0, weight: .regular),
NSAttributedString.Key.kern : -0.15]
let boldAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.font: UIFont.systemFont(ofSize: 10.0, weight: .bold),
NSAttributedString.Key.kern : -0.15]
let attributedText = NSMutableAttributedString()
attributedText.append(NSAttributedString(string: (billAddress), attributes: defaultAttributes))
attributedText.append(NSAttributedString(string: "\r\r"))
attributedText.append(NSAttributedString(string: "Invoice number: \(invoiceNumber)", attributes: defaultAttributes))
attributedText.append(NSAttributedString(string: "\r"))
attributedText.append(NSAttributedString(string: "Date: \(mydate)", attributes: defaultAttributes))
attributedText.append(NSAttributedString(string: "\r\r"))
attributedText.append(NSAttributedString(string: "To", attributes: defaultAttributes))
attributedText.append(NSAttributedString(string: "\r\r"))
attributedText.append(NSAttributedString(string: subjectline, attributes: boldAttributes))
attributedText.append(NSAttributedString(string: "\r"))
attributedText.append(NSAttributedString(string: narrative, attributes: defaultAttributes))
let textRect = CGRect(x: 40, y: textTop, width: pageRect.width - 240,
height: pageRect.height - textTop - pageRect.height / 2 )
attributedText.draw(in: textRect)

Related

iOS change the color of links for uilabel when using NSAttributedString

I'm using a uilabel and adding a link to by using NSAttributedString
let attributedText = NSMutableAttributedString(string: "http://www.google.com")
attributedText.addAttributes([NSAttributedString.Key.link: "https://www.google.com"], range: NSRange(location: 0, length: 21))
attributedText.addAttributes([NSAttributedString.Key.foregroundColor: UIColor.red], range: NSRange(location: 0, length: 21))
label.attributedText = attributedText
label.tintColor = UIColor.red
How can get the link to be another color other than the other default blue link color provided by the UIKit framework.
Note: I do not want to use UITextView or UIWebView
let attributedText = "http://www.google.com"
let multipleAttributes: [NSAttributedString.Key : Any] = [
NSAttributedString.Key.foregroundColor: UIColor.red,
NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue ]
let attributeString = NSAttributedString(string: attributedText, attributes: multipleAttributes)
// set attributed text on a UILabel
label.attributedText = attributeString

Why are NSAttributedString's attributes influencing other AttributedStrings?

In this case I'm setting a UILabel's attributedText with a combined NSAttributedString with different attributes for each line, and for some reason some AttributedString attributes influence the other AttributedStrings, but I thought their attributed are bound to the range of that particular AttributedString. I'm basically expecting that the NSAttributedString automatically gets its range set up when I append it to the NSMutableAttributedString.
Am I wrong?
In this case the shadows applies to the other appended AttributedStrings, in another case it is the bold font that overrides all fonts to come in the next AttributedStrings. Strange.
Here is some sample code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var textLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
textLabel.numberOfLines = 0
textLabel.lineBreakMode = .byWordWrapping
let myShadow = NSShadow()
myShadow.shadowBlurRadius = 3
myShadow.shadowOffset = CGSize(width: 3, height: 3)
myShadow.shadowColor = UIColor.gray
let attributedText = NSMutableAttributedString(string: "")
let fatString = NSMutableAttributedString(string: "Bold", attributes: [NSAttributedString.Key.foregroundColor : UIColor.green,
NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 16),
NSAttributedString.Key.backgroundColor : UIColor.lightGray,
NSAttributedString.Key.shadow : myShadow])
let normalString = NSAttributedString(string: "\n\nNormal", attributes: [NSAttributedString.Key.foregroundColor : UIColor.white,
NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)])
let italicString = NSAttributedString(string: "\n\nItalic", attributes: [NSAttributedString.Key.foregroundColor : UIColor.yellow,
NSAttributedString.Key.font : UIFont.italicSystemFont(ofSize: 16)])
let otherFontString = NSAttributedString(string: "\n\nBold + Italic", attributes: [NSAttributedString.Key.foregroundColor : UIColor.gray,
NSAttributedString.Key.font : UIFont(descriptor: UIFontDescriptor().withSymbolicTraits([.traitBold, .traitItalic])!, size: 16)])
let normalString2 = NSAttributedString(string: "\n\nUnderlined", attributes: [NSAttributedString.Key.underlineStyle : NSUnderlineStyle.single.rawValue,
NSAttributedString.Key.foregroundColor : UIColor.red,
NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)])
attributedText.append(fatString)
attributedText.append(normalString)
attributedText.append(italicString)
attributedText.append(otherFontString)
attributedText.append(normalString2)
textLabel.attributedText = attributedText
}
}

How to create random string with multiple formats?

I need to create a random string with format which can convert any string(including parenthesis() to another color on swift: for example:
Hey (Hey) : First part 'Hey' is fine, but I want to change : (Hey) to a different color
same goes if I choose another string
Hi (What's Up) ....
And tried the following
let label = UILabel(frame: CGRect(origin: .zero, size: CGSize(width: 200, height: 50)))
let color = UIColor(white: 0.2, alpha: 1)
let attributedTextCustom = NSMutableAttributedString(string: "(\(String())", attributes: [.font: UIFont(name:"AvenirNext-Medium", size: 16)!, .foregroundColor: color]))
attributedTextCustom.append(NSAttributedString(string: " (\(String())", attributes: [.font: UIFont(name: "AvenirNext-Regular", size: 12)!, .foregroundColor: UIColor.lightGray]))
label.attributedText = attributedTextCustom
Something like this is the behavior I am looking for (just for demostration...):
You can use a regex "\\((.*?)\\)" to find the range of the word between the parentheses and add the color attribute to a NSMutableAttributedString:
let label = UILabel(frame: CGRect(origin: .zero, size: CGSize(width: 200, height: 50)))
let sentence = "Hello (Playground)"
let mutableAttr = NSMutableAttributedString(string: sentence, attributes: [.font: UIFont(name:"AvenirNext-Medium", size: 16)!, .foregroundColor: UIColor.black])
if let range = sentence.range(of: "\\((.*?)\\)", options: .regularExpression) {
let color = UIColor(white: 0.2, alpha: 1)
let attributes: [NSAttributedString.Key: Any] = [.font: UIFont(name:"AvenirNext-Medium", size: 16)!, .foregroundColor: color]
mutableAttr.addAttributes(attributes, range: NSRange(range, in: sentence))
label.attributedText = mutableAttr
}

Can't change Paragraph attributes and String Attributes at the same time

I can get either the NSMutableParagraphStyle line spacing working OR the NSMutableAttributedString to be the correct font,color and size, but I can't get them both at the same time.
let style = NSMutableParagraphStyle()
style.lineSpacing = 5
let attributes = [NSParagraphStyleAttributeName : style]
aboutScrollable.attributedText = NSAttributedString(string: aboutScrollable.text, attributes:attributes)
let myString:NSString = aboutScrollable.text as NSString
var myMutableString = NSMutableAttributedString()
myMutableString = NSMutableAttributedString(string: myString as String, attributes: [NSFontAttributeName:UIFont(name: "Geomanist-regular", size: 12.0)!])
myMutableString.addAttribute(NSForegroundColorAttributeName, value: UIColor(red: 30/255, green: 30/255, blue: 30/255, alpha: 1), range: NSRange(location: 0,length: aboutScrollable.text.characters.count))
// set label Attribute
aboutScrollable.attributedText = myMutableString; NSAttributedString(string: aboutScrollable.text, attributes:attributes)

UITextView frame doesn't care about the attributes of its content

I have a UITextView with an attributedText (containing font & lineSpacing to put lines closer).
I use the code below to determine the frame of the textView.
The problem is the frame.height is too much big, and doesn't seem to care about lineSpacing. It looks like the frame of the textView without specific lineSpacing.
Does anyone have an idea to fix this fail? Did I miss something?
//Attributes of textView
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = -1.25
let textViewText = Data().text
let attributedText = NSMutableAttributedString(string: textViewText, attributes: [NSFontAttributeName: UIFont(name: "AvenirNext-Medium", size: 14)!])
attributedText.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: NSRange(location: 0, length: attributedText.string.characters.count))
textView.attributedText = attributedText
...
//Determining the frame of textView in other method
let textViewText = Data().text
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = -1.25
let rect = NSString(string: textViewText).boundingRect(with: CGSize(width: view.bounds.size.width, height: view.bounds.size.height),
options: NSStringDrawingOptions.usesFontLeading.union(NSStringDrawingOptions.usesLineFragmentOrigin),
attributes: [NSFontAttributeName: UIFont(name: "AvenirNext-Medium", size: 14)!, NSParagraphStyleAttributeName: paragraphStyle],
context: nil)