NSAttributedString superscript styling - ios

I want to superscript all the instances of ® character in a block of text (legal disclaimer, naturally ;)) and the default way NSAttributedString is not very good.
If I just let the character be and only use unmodified NSString, it is rendered the same size as a capital letter and is placed approximately at the baseline. If I add the superscript attribute to NSAttributedString as follows:
[attrStr setAttributes:#{(NSString *)kCTSuperscriptAttributeName : #1} range:NSMakeRange(locationOfReg, 1)];
The character is lifted off the baseline, its size is unchanged, but the line spacing is now affected because the raised character would otherwise intrude on the line above.
To illustrate:
I created this image in Photoshop where the desired position was achieved by reducing the size of the character and shifting the baseline. I know how to change the font size in iOS, but changing the baseline seems trickier. Any suggestions on how to achieve this?
Edit: I suppose I could use the superscript attribute as a way to shift the baseline up. Now it would be great to figure out a way to get the current font size and subsequently reduce it to allow the same method to be used on blocks of text of different size.

The following code seems to do the trick:
UIFont *fnt = [UIFont fontWithName:#"Helvetica" size:20.0];
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:#"GGG®GGG"
attributes:#{NSFontAttributeName: [fnt fontWithSize:20]}];
[attributedString setAttributes:#{NSFontAttributeName : [fnt fontWithSize:10]
, NSBaselineOffsetAttributeName : #10} range:NSMakeRange(3, 1)];

Swift version:
let fnt = UIFont(name:"Helvetica", size:20.0)
let attributedString = NSMutableAttributedString(string:"GGG®GGG", attributes:[NSFontAttributeName : fnt!])
attributedString.setAttributes([NSFontAttributeName : fnt!.fontWithSize(10), NSBaselineOffsetAttributeName: 10], range: NSRange(location: 3, length: 1))

Swift 5
let fnt = UIFont(name:"Helvetica", size:20.0)
let attributedString = NSMutableAttributedString(string:"2.099", attributes:[NSAttributedString.Key.font : fnt!])
attributedString.setAttributes([NSAttributedString.Key.font : fnt!.withSize(10), NSAttributedString.Key.baselineOffset: 10], range: NSRange(location: 4, length: 1))

Swift 4.2
In my example I want to subscript one instance of infinity symbol so my label's title will look like this:
let font = UIFont(name: "Helvetica", size: 14.0)
let attributedString = NSMutableAttributedString(string: "Solids(ΔE∞)•G7®", attributes: [NSAttributedStringKey.font : font!])
attributedString.setAttributes([NSAttributedStringKey.baselineOffset: -5], range: NSRange(location: 9, length: 1))
solidsLbl.attributedText = attributedString

Related

Why does changing the text of a UILabel not reset text attributes on iOS 13?

On iOS 12, if one changes the text of a UILabel it resets the text attributes. On iOS 13 however, text attributes such as color, typeface, letter spacing, et cetera are kept when the text is changed. What has changed?
An example:
label.text = "Hello world"
let attributedString = NSMutableAttributedString(string: label.text ?? " ")
attributedString.addAttributes([.foregroundColor: UIColor.red], range: NSRange(location: 0, length: attributedString.length))
label.attributedText = attributedString
label.text = "What's up world" // Text is red on iOS 13, default black on iOS 12.
Seems like from iOS 13, if you set and attribute to the entire text, it will persist! If you don't apply the attribute on the entire range of the text, it behaves like before.
You have some options to get around it:
Not applying it on the entire range (Happens most of the times):
attributedString.addAttributes([
.foregroundColor: UIColor.red,
.backgroundColor: UIColor.green
], range: NSRange(location: 0, length: 3))
Perform a version check (Maybe with a little extension)
#available(iOS 13.0, *)
extension UILabel {
func setTextWithoutAttributes(_ text: String) {
// Get rid of the holding attributes instance as Asperi mentioned or in another way you like
self.attributedText = nil
// Set the text
self.text = text
}
}
You did not reset attributedText, but documentation says - if set, the label ignores the properties above (see below for UILabel.h interface, in obj-c it is more correctly visible):
#property(null_resettable, nonatomic,strong) UIColor *textColor UI_APPEARANCE_SELECTOR; // default is labelColor
...
// the underlying attributed string drawn by the label, if set, the label ignores the properties above.
#property(nullable, nonatomic,copy) NSAttributedString *attributedText API_AVAILABLE(ios(6.0)); // default is nil
so behaves as specified (before it might be a bug, that now is fixed)
The solution of your case should be
label.attributedText = attributedString
...
label.attributedText = nil // << reset to default !!
label.text = "What's up world"

I want my text strike through extend on whitespace

I use NSAttributeString to set strike through on my text, I want it extend on whitespace.
I already set the correct range, but the strike through only cover the text characters. The strike through is ignore the whitespace unless I add some non-empty text before and after it.
How can I make the strike through extend on whitespace without extra text?
I have come across the same problem today, and neither existing answers gave me a solution I felt was as straightforward as it should be. After some research, I found a more concise solution using Unicode characters.
Padding each side of your text with one or more non-breaking Unicode space characters (U+00A0) extends the line as desired:
let attributedText = NSAttributedString(
string: "\u{00A0}\(someText)\u{00A0}",
attributes: [.strikethroughStyle: NSUnderlineStyle.single.rawValue]
)
I haven't found any solution but with your last option means adding first and last character as . with space you can try one thing. Either set the NSForegroundColorAttributeName of that first and last character to your background color of label or set the NSFontAttributeName with UIFont.systemFont(ofSize: 0.1). So it will be goes like this. You haven't specify your answer language so i'm posting answer in latest Swift 3.
let attributedText = NSMutableAttributedString(string: self.lbl.text!)
attributedText.addAttributes([NSStrikethroughStyleAttributeName: 2], range: NSMakeRange(0, self.lbl.text!.characters.count))
self.lbl.attributedText = attributedText
Before using NSForegroundColorAttributeName & NSFontAttributeName
Now you can use either NSForegroundColorAttributeName or NSFontAttributeName to hide first and last dot(.) character.
let attributedText = NSMutableAttributedString(string: self.lbl.text!)
attributedText.addAttributes([NSStrikethroughStyleAttributeName: 2], range: NSMakeRange(0, self.lbl.text!.characters.count))
attributedText.addAttributes([NSForegroundColorAttributeName: UIColor.white], range: NSMakeRange(0, 1))
attributedText.addAttributes([NSForegroundColorAttributeName: UIColor.white], range: NSMakeRange(self.lbl.text!.characters.count - 1, 1))
//Or either Set NSFontAttributeName instead of NSForegroundColorAttributeName
//attributedText.addAttributes([NSFontAttributeName: UIFont.systemFont(ofSize: 0.1)], range: NSMakeRange(0, 1))
//attributedText.addAttributes([NSFontAttributeName: UIFont.systemFont(ofSize: 0.1)], range: NSMakeRange(self.lbl.text!.characters.count - 1, 1))
self.lbl.attributedText = attributedText
After using NSForegroundColorAttributeName or NSFontAttributeName
For people coming to this question who only want a horizontal line through whitespace, this is the way to do it (Swift 4):
let line = String(repeating: "\u{23AF}", count: 10)

Dynamically setting a storyboard-generated UILabel's attributed string with two different fonts (Swift)

I have a UILabel in my storyboard, and I have an #IBOutlet to it in my controller. In my viewDidLoad, I am setting its attributed text with two different font sizes.
let str1 = NSMutableAttributedString(string: "first", attributes: [NSFontAttributeName: UIFont.systemFontOfSize(15.0)])
let str2 = NSMutableAttributedString(string: "second", attributes: [NSFontAttributeName: UIFont.systemFontOfSize(10.0)])
str1.appendAttributedString(str2)
myLabel.attributedText = str1
Unfortunately, when I run the app, I can see the "firstsecond" string, but all in the same size (str1's 15-point font). Why is str2's 10-point font not being set?
Thanks in advance.
You have to use addAttribute(...) to apply multiple attributes to the same string.
let first = "first"
let second = "second"
let string = NSMutableAttributedString(string: first + second)
string.addAttribute(NSFontAttributeName, value: UIFont.systemFontOfSize(15), range: NSMakeRange(0, first.characters.count))
string.addAttribute(NSFontAttributeName, value: UIFont.systemFontOfSize(10), range: NSMakeRange(first.characters.count, second.characters.count))
myLabel.attributedText = string
The documentation for UILabel's attributedText property states:
assigning a new value updates the values in the font, textColor, and
other style-related properties so that they reflect the style
information starting at location 0 in the attributed string.
So whatever the style information is at location 0, that's what the label's going to be. That's why you're only seeing str1's 15 point font.

Change paragraph height (not line spacing) in a UILabel

Is it possible to limit the distance between paragraphs that were created using \n\n smaller in UILabels using attributed strings?
So for example, I would like this:
To look like this:
Would this involve replace \n\n with something else? Or is there a much simpler solution using NSAttributedString?
First at all: The usage of \n\n to create distances between two paragraphs is no good idea at all. \n has the semantic meaning of a new paragraph, so you have three paragraphs, where two are semantically meant. This is like a amateurish secretary deals with paragraph distances. You should replace them with a single \n.
However, you should not use font sizes to adjust line spacing or paragraph spacing. This highly relies on the shape of a font and its definition. Things break fast.
Add a paragraph style, because they are built for paragraph spacing. Set the line height or paragraph spacing properties.
The solution I outlined in my comment works. You can set the font size of the the empty line / paragraph spacing as something that pleases your eyes:
[myAttributedString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:5.0] range:NSMakeRange(6, 1)];
The following code finds all occurrences of \n\n and specify the second one to have a specific size:
unsigned long length = myAttributedString.length;
NSRange range = NSMakeRange(0, length);
NSRange found;
while (NSNotFound != (found =[myAttributedString.string rangeOfString:#"\n\n" options:0 range:range]).location) {
[myAttributedString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:8.0] range:NSMakeRange(found.location + 1, 1)];
range = NSMakeRange(found.location + 2, length - found.location - 2);
}
One thing I didn't mention in the question, which I had thought was obvious from the example is that the description is not within my control, it is generated by users. Therefore, the carriage return characters are added by them when they are creating the text.
So the solution I came up with is the following:
Firstly, I replace any \n\n characters with a single carriage return. This was inspired by amin-negm-awad's answer. \n\n is not a desirable way to generate a paragraph space.
I am doing this using the following piece of code:
func sanitize() -> String {
var output = NSMutableString(string: self)
var numberOfReplacements = 0
do {
let range = NSMakeRange(0, output.length)
numberOfReplacements = newString.replaceOccurrencesOfString("\n\n", withString: "\n", options: NSStringCompareOptions.CaseInsensitiveSearch, range: range)
} while (numberOfReplacements > 0)
return output as String
}
The next part is to apply a paragraph style with an attributed string. Here is an example function that is fairly flexible:
func textAttributesWithFont(font: UIFont, andColor color: UIColor,
lineSpacing: CGFloat = 0,
maximumLineHeight: CGFloat = 0,
textAlignment: NSTextAlignment = .Natural) -> [NSObject: AnyObject] {
var attributes = [NSFontAttributeName : font, NSForegroundColorAttributeName : color]
var paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = lineSpacing
paragraphStyle.alignment = textAlignment
paragraphStyle.maximumLineHeight = maximumLineHeight
paragraphStyle.paragraphSpacing = 4
attributes[NSParagraphStyleAttributeName] = paragraphStyle
return attributes
}
Finally the label is constructed using the attributes:
var label1 = UILabel()
let text1 = "This is a test that is supposed😓😛😠😑 to wrap with some paragaphs\n\nThis is a paragraph"
label1.attributedText = NSAttributedString(string:sanitizeComment(text1), attributes: attributes)
label1.numberOfLines = 0

how to set attributes in NSAttributedString in iOS?

I'm trying to set the infamous NSFontAttributeName property of an NSAttributedString in iOS but it just doesn't seem to work:
first off, none of the NS constants seem defined for iOS
I read somewhere that I could instead work around it by passing
the CoreText consts instead. Fine... but still, The attribute
expects an NSFont and I'm stuck with UIFont or CTFontRef, neither of
which seems to work:
this doesn't work:
CTFontRef ctFont = CTFontCreateWithName((CFStringRef)[UIFont boldSystemFontOfSize:16].fontName, [UIFont boldSystemFontOfSize:16].pointSize, NULL);
[myAttString addAttribute:(NSString*)kCTFontNameAttribute
value:(id)ctFont
range:NSMakeRange(0, myAttString.length-1)];
this doesn't work:
[myAttString addAttribute:(NSString*)kCTFontNameAttribute
value:[UIFont boldSystemFontOfSize:16]
range:NSMakeRange(0, myAttString.length-1)];
Is there anyway to make this work?
I found it!
basically, turns out the string constant for the dictionary key I should been using is kCTFontAttributeName
This whole thing is a show...
The NS constants and full attributedString support will be there. Not yet in iOS5 though.
The CoreText constants do work and CTFontRef is the way I use it as well. The first block of your code should work. Can you verify your other bits of code that the problem ain't elsewhere.
do this way:
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
let attributes = [NSParagraphStyleAttributeName : paragraphStyle,
NSFontAttributeName : UIFont.systemFont(ofSize: 24.0),
NSForegroundColorAttributeName : UIColor.blue,
]
let attrString = NSAttributedString(string: "Stop\nall Dance",
attributes: attributes)

Resources