Change paragraph height (not line spacing) in a UILabel - ios

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

Related

NSAttributedString how to use NSParagraphStyle to set line of text center alignment

I am trying to modify my attributed text in UILable to have one line centered.
So I am adding attributes to NSAttributedString with NSParagrahStyle as below
var centerParagraphAttributes : [NSAttributedString.Key : Any] {
let paragraph = NSMutableParagraphStyle()
paragraph.alignment = .center
return [
NSAttributedString.Key.paragraphStyle : paragraph,
NSAttributedString.Key.font : AppFonts.SFUITextBold.font(size: 14.0)
]
}
// center - or -
if let range = attributedText.string.range(of: "\n\r- or -\n\r") {
let nsrange = NSRange(range, in: attributedText.string)
attributedText.addAttributes(centerParagraphAttributes, range: nsrange)
}
But this code doesn't do anything and line is left aligned as previously.
Ok the above code works and aligns line of text. I only tried to set paragraph style on substring without "\n\r" special characters that start new paragraphs, and then after adding this to substring I've left empty space between "\n\r - or -\n\r" So it doesn't match range(of: "\n\r- or -\n\r")
More over it even works when using just leading "\n\r- or -" so no extra space at bottom of paragraph is needed

NSAttributedString text always sticks to bottom with big lineHeight

I'm trying to implement by-design labels coming from Sketch e.g. I need text styles with font size = 19 and line height = 50. So I ended up using NSAttributedString with NSMutableParagraphStyle but was stopped by problem with text being sticked to bottom of UILabel
I've already tried to use lineHeightMultiple and lineSpacing but those didn't give me the line height I wanted so I ended up using minimumLineHeight and maximumLineHeight equal the same
Here is my approach to make NSAttributedString
private static func makeAttributedString(
with attributes: TextAttributes,
text: String? = nil,
alignment: NSTextAlignment = .center
) -> NSAttributedString {
let font = UIFont(name: attributes.font.rawValue, size: attributes.fontSize)!
let paragraph = NSMutableParagraphStyle()
paragraph.alignment = alignment
paragraph.paragraphSpacing = attributes.paragraph
paragraph.minimumLineHeight = attributes.lineHeight // equal 50 in my case
paragraph.maximumLineHeight = attributes.lineHeight // equal 50 in my case
let attributes: [NSAttributedStringKey: Any] = [
NSAttributedStringKey.paragraphStyle: paragraph,
NSAttributedStringKey.foregroundColor: attributes.textColor,
NSAttributedStringKey.kern: attributes.kern,
NSAttributedStringKey.font: font
]
return NSAttributedString(string: text ?? "", attributes: attributes)
}
I expect result similar to design
but actually getting
Note: setting height constraint to 50 is not applicable because I also need multiline labels but there is the same bug with them
Seems like I've found some workaround myself, maybe it will help someone.
The method is about setting baselineOffset like this:
NSAttributedStringKey.baselineOffset: (attributes.lineHeight - font.lineHeight) / 4
Works like charm:
https://i.imgur.com/a2EOf5R.png

How to add indents to Multiple lines of UILabel?

While I found how to add indentations to first line (FirstLineHeadIndent) and to the rest of lines (HeadIndent), I cannot find how to add indents to only first two/three lines in order to achieve something like this:
PS: This is not a duplicate, because I'm not asking how to indent only first line, as one user suggested.
Using TextKit Framework in ios
CGRect checkBoxFrame = [self.textView convertRect:self.checkView.bounds fromView:self.checkView];
checkBoxFrame.origin.x -= self.textView.textContainerInset.left;
checkBoxFrame.origin.y -= self.textView.textContainerInset.top;
UIBezierPath *checkBoxPath = [UIBezierPath bezierPathWithOvalInRect:checkBoxFrame];
self.textView.textContainer.exclusionPaths = #[checkBoxPath];
It will exclude the image path inside content of TextView
You need to set your UILabel text as Attributed string in storyboard.
Then you can edit the indentation of each line, and you can also paste any text you've created with text editor and it will keep its indentation as well as other attributes.
You can of course manipulate these attributes programmatically, here is an example:
#IBOutlet weak var label: UILabel!
let text = "\tfirst line\n \tsecond line\nthird line\nforth line"
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.tabStops = [NSTextTab(textAlignment: NSTextAlignment.left, location: 15, options: [:])]
paragraphStyle.headIndent = 10
label.attributedText = NSAttributedString(string: text, attributes: [NSParagraphStyleAttributeName: paragraphStyle])
Here is an example of how to configure it:
Here is how to configure indentation:
Here is the example on the simulator:
You would need to use the stringattribute property of the UILabel.AttributedText.
its actually of type NSMutableAttributedString so I first casted
label.AttributedText to the mutable type then I could manipulate it.‡
To do this you would use the following:
var mutable = Control.AttributedText as NSMutableAttributedString;
UIStringAttributes uiString=new UIStringAttributes();
Then you need to set an indent on the first line (Do this the way you already know how to), and then you would set the headIndent of its paragraph style like below.
this is converted from objective-c so might not be perfect: ‡
NSMutableParagraphStyle paragraphStyle = new NSMutableParagraphStyle();
paragraphStyle.headIndent = 14;
NSDictionary attributes (){
StyleAttributeName = paragraphStyle;
};
mutable.AddAttribute(attributes);
Control.attributedText = mutable;
I believe something like this in combination with your 'FirstLineHeadIndent' code should do the trick.
‡ How to manipulate NSAttributedString
‡ Objective-C second line with indent

UITextView attributed Alignment and size

First assume math is correct.
let textViewText = NSMutableAttributedString(string: "34\n+ 10\n+ 32344\n= 23424")
Im using a Textview to display input from the user. To make it easier to read I'm trying to get the text format like this
34
+ 10
+ 32344
= 23424
The other issue I'm having is with wrapping. Is there a way to resize each line to fit on its line?
34
= 23424
4356356
Is your text dynamic or static? If static, then all you need to do is to put the correct amount of spacing between your numbers and plus signs and then right justify the text.
self.textView.attributedText = NSMutableAttributedString(string: "34\n+ 10\n+ 32344\n= 23424")
self.textView.textAlignment = NSTextAlignment.Right
Result:
You can accomplish this by using a right-aligned tab stop in your paragraph style, and separating your operators and values with a tab.
let string = "\t34\n+\t10\n+\t32344\n=\t23424"
let paragraph = NSMutableParagraphStyle()
paragraph.tabStops = [NSTextTab(textAlignment: .Right, location: 200, options: [:])]
let attributedString = NSAttributedString(string: string, attributes:[NSParagraphStyleAttributeName: paragraph])

NSAttributedString superscript styling

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

Resources