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

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

Related

How to apply Atrributed String different spacing between lines in swift

I want to apply spacing between first two lines in attributed string and third line should look like paragraph.
Expected output :
Expected output screenshot
Current Implemenation:
Current implementaion screenshot
Here is the code tried by me.
let myString = "Your account phone numbers are listed here.\nTo change or delete a phone number, tap on it.\nTo add a phone number, go to the top right-hand corner of your screen and tap on “Add”.";
let font = UIFont.systemFont(ofSize: 14)
let attributedString = NSMutableAttributedString(string: myString, attributes: [.font: font])
self.displayLabel.attributedText = attributedString
I created label and setting number of lines 0 so it will display multiline text.
In the label need to show space in the first two lines as shown in expected output screenshot.
How to apply spacing only to first two lines and third line should display as shown in expected output screenshot?
You seem to want to set the spacing between paragraphs. This is controlled by NSParagraphStyle.paragraphSpacing. Just set the .paragraphStyle attribute of the attributed string to an NSParagraphStyle:
let paraStyle = NSMutableParagraphStyle()
paraStyle.paragraphSpacing = 10 // or some other number
let attributedString = NSMutableAttributedString(string: myString,
attributes: [
.font: font,
.paragraphStyle: paraStyle
])

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

why (arabic with english) text refuses to align right?

This is the string I use:
CASE 1
var word1 = "عبد الله"
var word2 = "restaurant"
label.text = " \(word1) found your review on \(word2) useful."
Result:
CASE 2
var word1 = "عبد الله"
var word2 = "restaurant"
label.text = "note: \(word1) found your review on \(word2) useful."
Result:
Question
so, how do I make the first word to wrap right? if the first word is arabic, it gets wrapped to the left, but if the first word is english the situation is expected, so how make the word1 to show up when first word on the left?
I tried both
label.textAlignment = NSTextAlignment.Left
and
label.textAlignment = NSTextAlignment.Natural
without any luck.
Unicode has two marker characters (LTR: 0x200E, RTL:200F). These are invisible, but control the direction, I just need to add this \u{200E} to force the wrapping direction.
\u{200E} \(word1) found your review on \(word2) useful.
EDIT:
see full tutorial here, for more info.
UILabel as a subclass of UIView has a variable named semanticContentAttribute which you can set to .foreRightToLeft, it can also be set from the nib inspector through the Semantic pop-up menu in the attributes inspector.
Moreover, you can query effectiveUserInterfaceLayoutDirection property for debugging it's state.
See this for reference.
Now if you need both alignments in one label it will be tricky, either group two labels in a container UIView or see if you can set these values for portions of an NSMutableAttributedString which you can feed to a UILabel.
The textAlignment properties you are trying to set will give you the same effect that MS-Word does to paragraph alignment but wouldn't flip reading direction for language.
Happy coding!
Edit: This is an example of what I am suggesting with attributed strings although when changing the arabic setting to RightToLeft it puts it at the bottom of the string... Maybe the flags need to be combined differently?
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
let myMutableString = NSMutableAttributedString()
//right-to-left
let multipleAttributes: [String : AnyObject] = [
NSForegroundColorAttributeName: UIColor.orangeColor(),
NSBackgroundColorAttributeName: UIColor.blueColor(),
NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleDouble.rawValue,
NSWritingDirectionAttributeName : [NSWritingDirection.LeftToRight.rawValue ]
]
let myAttrString = NSAttributedString(string: "عبد الله", attributes: multipleAttributes)
myMutableString.appendAttributedString(myAttrString)
//some-text
let someText = NSAttributedString(string: " finds ", attributes: nil)
myMutableString.appendAttributedString(someText)
//left-to-right
let multipleAttributes2: [String : AnyObject] = [
NSForegroundColorAttributeName: UIColor.blueColor(),
NSBackgroundColorAttributeName: UIColor.yellowColor(),
NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleDouble.rawValue,
NSWritingDirectionAttributeName : [NSWritingDirection.LeftToRight.rawValue | NSTextWritingDirection.Embedding.rawValue]
]
let myAttrString2 = NSAttributedString(string: "restaurant", attributes: multipleAttributes2)
myMutableString.appendAttributedString(myAttrString2)
label.attributedText = myMutableString
self.view.addSubview(label)
label.sizeToFit()
label.center = self.view.center
}

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

Can't get UILabel's text to stay on multiple lines

I have a UILabel that should display text on multiple lines in case that it's too long to stay on a single line. This how I set its parameters in interface builder:
But even by doing so, the text still gets truncated:
This is how I set the text at runtime:
let text = "left button pressed 5 seconds ago, you may want to press another button now"
let attributedText = NSMutableAttributedString(string: text)
attributedText.addAttribute(NSFontAttributeName, value: UIFont.boldSystemFontOfSize(statusLabel.font.pointSize), range: (text as NSString).rangeOfString("left"))
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakMode = .ByWordWrapping
attributedText.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: NSMakeRange(0, attributedText.length))
statusLabel.attributedText = attributedText
Like you see I even tried to add a paragraph style attribute to force the text to stay on multiple lines, but it doesn't work.
Check that you're setting the auto layout constraints so you have the top, leading and trailing spaces defined, but don't hookup a vertical height, the label will adjust itself based on the content.
Edit:

Resources