Create custom NSAttributedString.Key - ios

I'm trying to build a simple note app. At the moment, I'm focusing on the possibility to set the text with different text styles (e.g. body, headline, bold, italic, etc.). I used a NSAttributedString to set the different text styles. Now, I'd like to detect which style has been applied to the selected text.
I thought a good way to do it would have been to create a custom NSAttributedString.Key, so that I can assign it when setting the attributes (e.g. .textStyle: "headline", and read it when I need to detect it. I tried implementing it as an extension of NSAttributedString.Key but without success. What would be the correct way to do it?
Is there a better alternative?

You can simply create a TextStyle enumeration and set your cases "body, headline, bold, italic, etc" (You can assign any value to them if needed). Then you just need to create a new NSAttributedString key:
enum TextStyle {
case body, headline, bold, italic
}
extension NSAttributedString.Key {
static let textStyle: NSAttributedString.Key = .init("textStyle")
}
Playground Testing
let attributedString = NSMutableAttributedString(string: "Hello Playground")
attributedString.setAttributes([.textStyle: TextStyle.headline], range: NSRange(location: 0, length: 5))
attributedString.enumerateAttributes(in: NSRange(location: 0, length: attributedString.length), options: []) { attributes, range, stop in
print(attributes, range, stop )
print(attributedString.attributedSubstring(from: range))
}

Related

Change only fontsize of NSAttributedString

I have a NSAttributedString that was loaded from a RTF file, so it already holds several font-attributes for different ranges.
Now I want to adapt the font size to the screensize of the device, but when I add a whole new font attribute with a new size, the other fonts disappear.
Is there a way to change only the font size for the whole string?
If you only want to change the size of any given font found in the attributed string then you can do:
let newStr = someAttributedString.mutableCopy() as! NSMutableAttributedString
newStr.beginEditing()
newStr.enumerateAttribute(.font, in: NSRange(location: 0, length: newStr.string.utf16.count)) { (value, range, stop) in
if let oldFont = value as? UIFont {
let newFont = oldFont.withSize(20) // whatever size you need
newStr.addAttribute(.font, value: newFont, range: range)
}
}
newStr.endEditing()
print(newStr)
This will keep all other attributes in place.
If you want to replace all fonts in a given attributed string with a single font of a given size but keep all other attributes such as bold and italic, see:
NSAttributedString, change the font overall BUT keep all other attributes?

PDFPage.attributedString returns the wrong font

I'm reading an existing PDF using PDFKit. I get an attributedString for a page, but the fonts in the string don't match the fonts actually in the PDF:
The fonts in the PDF (according to several different apps) are:
CourierFinalDraft (TypeType Roman) Embedded Subset
CourierFinalDraft-Bold (TrueType Roman) Embedded Subset
CourierFinalDraft-Italic (TrueType Roman) Embedded Subset
My Swift code to get the font is:
guard let page = pdf.page(at: pageNo) else { return }
guard let content = page.attributedString else { return }
content.enumerateAttributes(in: range, options:[]) {(dict: [String:Any], range: NSRange, stop: UnsafeMutablePointer<ObjCBool>) -> Void in
let font = dict[NSFontAttributeName] as! UIFont
}
All of the text is returned in a single range. The font returned has the following:
font-family: "Times New Roman"
font-name: "TimesNewRomanPSMT"
So the Bold and Italic text are returned in the same range as the normal text, and I can't distinguish among them, which is what I'm trying to do. As well as the font name I also look at:
font.fontDescriptor.symbolicTraits.contains(.traitItalic)
but of course this is always False as all text is returned in the same range as if it is normal.
This is using the XCode simulator, in case that's relevant. The PDF does render correctly (including bold and italics) on Safari in the Simulator. Unfortunately I can't try it out on a real iPhone.
This question is a few years old, but I found that I had to make a mutable copy of the data in order to interact with it and then use the information available to modify the underlying font and re-display it.
let str = content.mutableCopy() as! NSMutableAttributedString
str.beginEditing()
str.enumerateAttribute(.font,
in: NSRange(location: 0, length: str.length),
options: []) { value, range, _ in
guard let value = value as? UIFont else {
return
}
let isBold = value.fontDescriptor.symbolicTraits.contains(.traitBold)
let font = // Your font logic here based on traits
str.removeAttribute(.font, range: range)
str.addAttribute(.font, value: font, range: range)
str.addAttribute(.paragraphStyle, value: style, range: range)
}
str.endEditing()
I have not gone over performance issues on this yet for larger pdfs, but it does not seem to take up too many resources for pdfs between 10-20 pages. For a higher number of pages, I would most likely create an actual paging mechanism and then adjust for each page on the fly rather than loop the entire pdf file.

iOS Attributed Text Setting Entire String on Editing End

Swift 3.0. I'm highlighting certain words in a UITextField using attributed text.
let myRegex = "2 highlight"
for match in myRegex.matches(in: textField.text!, options: [], range: NSRange(location: 0, length: textField.text!.utf16.count)) as [NSTextCheckingResult] {
attributedString.addAttribute(NSBackgroundColorAttributeName, value: UIColor.yellow, range: match.range)
}
textField.attributedText = attributedString
For some reason though, once I get over a certain character limit, once I leave the text field, the entire thing gets highlighted. There is no way to unhighlight it once this happens.
http://www.giphy.com/gifs/3o6ZthPnWEvD7v5wT6
I've got nothing in my code that does anything like this, why is the whole string being attributed once I leave the field?

iOS Swift Relative font size

I have a UITextView, and I want to change its font size.
however, I want it to change relatively since it pulls from a file that has multiple font sizes in it and I want it to change accordingly.
for example, I have a word in font size 36 and one in font size 12 and I want to scale them by 0.75% to 27 and 9 respectively.
If I try:
textview.font = UIFont(name: textview.font.fontName, size: 20)
it will only change the entire UITextView font size.
thanks!
You can use this extension:
extension NSAttributedString {
#warn_unused_result
func scaleBy(scale: CGFloat) -> NSAttributedString {
let scaledAttributedString = NSMutableAttributedString(attributedString: self)
scaledAttributedString.enumerateAttribute(NSFontAttributeName, inRange: NSRange(location: 0, length: scaledAttributedString.length), options: NSAttributedStringEnumerationOptions(rawValue: 0)) { (value, range, _) in
if let oldFont = value as? UIFont {
let newFont = oldFont.fontWithSize(oldFont.pointSize * scale)
scaledAttributedString.removeAttribute(NSFontAttributeName, range: range)
scaledAttributedString.addAttribute(NSFontAttributeName, value: newFont, range: range)
}
}
return scaledAttributedString
}
}
Then just call something like:
textField.attributedText = textField.attributedText!.scaleBy(0.5)
Example:
You would have to write (or find) a parser for the rich text format file that could extract the font size data for each text element (I think this would be the \fsN tags in most cases) and then use that number (multiplied by 0.75) to set the size of each word or phrase individually. You could use an attributed string if the differently sized words need to be recombined into a single string, but that wouldn't be necessary if each word or phrase was in a separate label.
Personally, I would disregard the font sizes of the source data and impose a layout within the app that looks nice, if that's an option.

Font attribute (NSFontAttributeName) not applied to attributed string

I'm trying to apply two attributes to a string, one for kerning (NSKernAttributeName) and one for the font (NSFontAttributeName). Although I've checked and rechecked 1000 times, I can only get the kerning attribute to be applied to the string. Here's my setup:
let runAtTitle = "RUN AT"
var attRunAt = NSMutableAttributedString(string: runAtTitle)
let font = UIFont(name: "HelveticaNeue-Bold", size: 76.0)!
let attrs = [NSFontAttributeName: font, NSKernAttributeName: -3.0]
attRunAt.addAttributes(attrs, range: NSMakeRange(0, attRunAt.length))
runAtLabel.attributedText = attRunAt
When I build the app, the kerning is applied to my string, but the font is not. It uses the default 12pt Helvetica. Please tell me I'm doing something wrong.
I just tried your code in Playground and it works fine. Most likely you are setting the text attribute of the label after setting the attributedText. That will revert the string to the normal label string with its attributes, or perhaps for some strange reason keep the kern attribute.
Here is what I get with your code:
and after adding
label.text = "RUN AT"

Resources