is it possible to set the UILabel distance between the line? - iphone

Is it possible to set a UILabel's distance between the line, as i had a UILabel contain 3 lines, and linebreakmode is wordwrap?

If you're referring to "leading", which refers to the gap between lines of type - you cannot change this on a UILabel. That is inferred from the front of the label itself. Some people have tried to create categories to override the "leading" property of the UIFont for the label but it doesn't actually work when rendering.
If you really need to control the vertical spacing between lines of text then your best bet is to programmatically drop 1 UILabel per line of fixed width and control the vertical gap yourself.

Here is how you can set line spacing using interface builder and programatically as well.
From Interface Builder:
Programmatically:
SWift 4
Using label extension
extension UILabel {
// Pass value for any one of both parameters and see result
func setLineSpacing(lineSpacing: CGFloat = 0.0, lineHeightMultiple: CGFloat = 0.0) {
guard let labelText = self.text else { return }
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = lineSpacing
paragraphStyle.lineHeightMultiple = lineHeightMultiple
let attributedString:NSMutableAttributedString
if let labelattributedText = self.attributedText {
attributedString = NSMutableAttributedString(attributedString: labelattributedText)
} else {
attributedString = NSMutableAttributedString(string: labelText)
}
// Line spacing attribute
attributedString.addAttribute(NSAttributedStringKey.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
self.attributedText = attributedString
}
}
Now call extension function
let label = UILabel()
let stringValue = "is\nit\npossible\nto\nset\nthe\nUILabel\ndistance\nbetween\nthe\nline?"
// Pass value for any one argument - lineSpacing or lineHeightMultiple
label.setLineSpacing(lineSpacing: 2.0) . // try values 1.0 to 5.0
// or try lineHeightMultiple
//label.setLineSpacing(lineHeightMultiple = 2.0) // try values 0.5 to 2.0
Or using label instance (Just copy & execute this code to see result)
let label = UILabel()
let stringValue = "is\nit\npossible\nto\nset\nthe\nUILabel\ndistance\nbetween\nthe\nline?"
let attrString = NSMutableAttributedString(string: stringValue)
var style = NSMutableParagraphStyle()
style.lineSpacing = 24 // change line spacing between paragraph like 36 or 48
style.minimumLineHeight = 20 // change line spacing between each line like 30 or 40
// Line spacing attribute
attrString.addAttribute(NSAttributedStringKey.paragraphStyle, value: style, range: NSRange(location: 0, length: stringValue.characters.count))
// Character spacing attribute
attrString.addAttribute(NSAttributedStringKey.kern, value: 2, range: NSMakeRange(0, attrString.length))
label.attributedText = attrString

Related

How to check if a text went to a new line in a UILabel?

how can I check if a text in a UILabel went to a new line? I want to count the times when a text moved to a new line in order to get the distance between this line and the top of the UILabel. An easier way would be to get the number of the line which contains a certain word, but I couldn't find an answer for that. Do you know how to achieve it?
EDIT:
What I want to do is to add UITextFields into a label. I want the UITextFields to cover specific words, but the text in the label will change so the UITextField has to be dynamic. I already works, but only with the first line since in the UITextField, I can tap into the .firstBaselineAnchor property.
It works with this code here:
let labelView = UILabel(frame: .zero)
labelView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(labelView)
NSLayoutConstraint.activate([
labelView.leadingAnchor.constraint(equalTo: self.cardView.leadingAnchor, constant: 10),
labelView.trailingAnchor.constraint(equalTo: self.cardView.trailingAnchor, constant: -10),
labelView.topAnchor.constraint(equalTo: self.cardView.topAnchor, constant: 10)
])
self.labelView = labelView
let text = "This labelview ______ created to test how to replace the underscores with a textinput."
labelView.text = text
let font = UIFont.systemFont(ofSize: 30)
labelView.font = font
let distanceOfCharacterToStringStart = labelView.text?.characterPosition(character: "_", with: font)
labelView.textAlignment = .left
labelView.numberOfLines = 0
// getCharacterAtPosition(of: text, with: font, in: labelView.frame.width)
//MARK: - UITEXTFIELD
let textField = UITextField(frame: .zero)
textField.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(textField)
NSLayoutConstraint.activate([
textField.leadingAnchor.constraint(equalTo: labelView.leadingAnchor, constant: distanceOfCharacterToStringStart!.x - 0.5),
textField.trailingAnchor.constraint(equalTo: labelView.trailingAnchor),
textField.bottomAnchor.constraint(equalTo: labelView.firstBaselineAnchor, constant: 0 - labelView.font.descender),
textField.heightAnchor.constraint(equalToConstant: labelView.font.lineHeight)
])
self.labelView = labelView
textField.backgroundColor = UIColor.red
(I know that I still have to fix the trailing constraint of the UITextView.) It calculates the distance between the beginning of the string and the first underscore character by using this method:
extension StringProtocol {
func characterPosition(character: Character, with font: UIFont) -> CGPoint? {
guard let index = firstIndex(of: character) else {
print("\(character) is missed")
return nil
}
let string = String(self[..<index])
let size = string.size(withAttributes: [.font: font])
return CGPoint(x: size.width, y: 0)
}
}
But now I want to get it to work for the other lines in between the first and last baseline as well. So I thought I would probably have to calculate where the text goes into the next line, and then I could substring it and again calculate the distance of the first character of the string in this line and the underscore in this line (if it exists) by using a similar method as shown above. Here is what I started creating in order to do so:
func getCharacterAtPosition(of string: String, with font: UIFont, in labelsize: CGFloat) -> [Character : Int] {
var characterAtIndex: [Character : Int] = [:]
var indexCount = 0
for c in string {
let upperBound = String.Index.init(utf16Offset: indexCount, in: string)
let partOfFullString = String(string[..<upperBound])
let size = partOfFullString.size(withAttributes: [.font: font])
if size.width >= labelsize {
characterAtIndex = [c : indexCount]
indexCount = 0
break
} else {
indexCount += 1
}
}
return characterAtIndex
}
By counting the number of new lines and the distance of a character to the first character in each line, I could get the coordinates for my UITextView.

How to display multiple rows in macos menubar app?

Originally I thought adding new line should work. The problem is, the line height is too big. How can I make it more dense(closer to each other)?
There's the following example on git starting line 41. But apple docs say that custom view inside of NSStatusItem is deprecated. (not sure if it's deprecated for views inside of NSMenuItem)
let statusBar = NSStatusBar.system
statusBarItem = statusBar.statusItem(withLength: NSStatusItem.squareLength)
statusBarItem.button?.title = "123\n456"
UPD
What I got so far. It's not Y centred though. And statusItem?.button?.frame = CGRect(x: 0.0, y: -2.0, ... seems incorrect and shifts the leading icon to the bottom.
var comb = NSMutableAttributedString(attributedString: str1)
comb.append(br) // new line
comb.append(str2)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.maximumLineHeight = 9
comb.addAttribute(NSAttributedString.Key.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, comb.length))
statusItem?.button?.attributedTitle = comb
UPD (retarted approach with \n and baselineoffset)
var comb = NSMutableAttributedString(attributedString: NSAttributedString(string: "\n")) // this
comb.append(top)
comb.append(br)
comb.append(bottom)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.maximumLineHeight = 9
comb.addAttribute(NSAttributedString.Key.baselineOffset, value: 3.0, range:NSMakeRange(0, comb.length))
As matt noted, use attributeTitle and NSMutableParagraph
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.maximumLineHeight = 9
paragraphStyle.alignment = .left
Columns formatting can be achieved by using tabs, spaces, or other separators
NSAttributedString(string: "\n10 \u{2022} 220 \u{2022} 4999", attributes: textAttr) // this is a row
statusItem?.button?.attributedTitle = rows
And to shift text from the bottom:
rows.addAttribute(NSAttributedString.Key.baselineOffset, value: 2.0, range:NSMakeRange(0, rows.length))

Set NSAttributedString center vertically each line using baselineOffset

I want to set label's line height and I use minimumLineHeight and maximumLineHeight of NSMutableParagraphStyle
extension UILabel {
func setTextWithLineHeight(text: String?, lineHeight: CGFloat) {
if let text = text {
let style = NSMutableParagraphStyle()
style.maximumLineHeight = lineHeight
style.minimumLineHeight = lineHeight
let attributes: [NSAttributedString.Key: Any] = [
.paragraphStyle: style
.baselineOffset: (lineHeight - font.lineHeight) / 4 // added!!️️🤟
]
let attrString = NSAttributedString(string: text,
attributes: attributes)
self.attributedText = attrString
}
}
}
I add .baselineOffset attribute based on a answer NSAttributedString text always sticks to bottom with big lineHeight , because without it, text is sticks to bottom like this.
image
What I want is set text center vertically so using baselineOffset, I solved the problem. However I wonder why it set baseOffline as (attributes.lineHeight - font.lineHeight) / 4 not (attributes.lineHeight - font.lineHeight) / 2

How to get the estimated size of a rectangle required to draw NSMutableAttributedString?

I am trying to get the estimated size of the rectangle required to draw out a NSMutableAttributedString. The numbers that come not does not make any sense to me. I have a UIViewController with a UIlabel (txtField), with a UIlabel.numberOfLines = 3. I would like to estimate the height of this NSMutableAttributedString were i to set UIlabel.numberOfLines= 0.
With reference to the console reading, I do not understand why the estimated height of the rectangle required to draw the entire NSMutableAttributedString is less than that if it were constrained to just 3 lines?
var txtField: UILabel = {
let label = UILabel()
label.numberOfLines = 3
label.translatesAutoresizingMaskIntoConstraints = false
label.lineBreakMode = .byTruncatingTail
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
let content = "Pasture he invited mr company shyness. But when shot real her. Chamber her
observe visited removal six sending himself boy. At exquisite existence if an oh dependent excellent. Are gay head need down draw. Misery wonder enable mutual get set oppose the uneasy. End why melancholy estimating her had indulgence middletons. Say ferrars demands besides her address. Blind going you merit few fancy their. "
let attributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14)]
let attributedString = NSMutableAttributedString.init(string: content, attributes: attributes)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 1.5
paragraphStyle.lineBreakMode = .byTruncatingTail
attributedString.addAttributes([.paragraphStyle : paragraphStyle], range: NSRange(location: 0, length: attributedString.length))
txtField.attributedText = attributedString
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let attText = txtField.attributedText{
let size = CGSize.init(width: txtField.frame.width - 20, height: 1000)
let estimatedFrame = attText.boundingRect(with: size, options: .usesLineFragmentOrigin, context: nil)
print("txtField.frame: \(txtField.frame)")
print("estimatedFrame: \(estimatedFrame)")
}
}
CONSOLE:
txtField.frame: (0.0, 0.0, 394.0, 53.333333333333336)
estimatedFrame: (0.0, 0.0, 367.28125, 16.70703125)
This is wrong:
paragraphStyle.lineBreakMode = .byTruncatingTail
Attributed string line breaking is different from label line breaking. Your attributed string needs to have a line break mode that wraps. Otherwise you are measuring the height of just one line.

SKLabelNode Border and Bounds Issue

I am attempting to create text that has an outline. I am currently using SKLabelNode with NSAttributedString, which you can now do in SpriteKit as of iOS 11. The problem is, if the stroke width is too thick, then the outline gets cut off by what appears to be the bounding rectangle of the SKLabelNode. Please see below for the image and code.
extension SKLabelNode {
func addStroke(_ strokeColor:UIColor) {
let font = UIFont(name: self.fontName!, size: self.fontSize)
let attributes:[NSAttributedStringKey:Any] = [.strokeColor: strokeColor, .strokeWidth: 20.0, .font: font!]
let attributedString = NSMutableAttributedString(string: " \(self.text!) ", attributes: attributes)
let label1 = SKLabelNode()
label1.horizontalAlignmentMode = self.horizontalAlignmentMode
label1.text = self.text
label1.zPosition = -1
label1.attributedText = attributedString
self.addChild(label1)
}
}
I looked at expanding the frame of the SKLabelNode serving as the border text, but that is a get-only property. I tried to add leading/trailing spaces, but they appear to be automatically trimmed. Using a negative value for strokeWidth works but creates an inner stroke, I'd prefer to have an outer stroke.
Any ideas? Thanks in advance for the help!
Mike
You shouldn't need to create a separate node for the stroke.
Use negative width values to only render the stroke without fill.
Use .foregroundColor to fill.
You should first check to see if an attributed string is already present to ensure you do not clobber it.
Here is the code:
extension SKLabelNode {
func addStroke(color:UIColor, width: CGFloat) {
guard let labelText = self.text else { return }
let font = UIFont(name: self.fontName!, size: self.fontSize)
let attributedString:NSMutableAttributedString
if let labelAttributedText = self.attributedText {
attributedString = NSMutableAttributedString(attributedString: labelAttributedText)
} else {
attributedString = NSMutableAttributedString(string: labelText)
}
let attributes:[NSAttributedStringKey:Any] = [.strokeColor: color, .strokeWidth: -width, .font: font!, .foregroundColor: self.fontColor!]
attributedString.addAttributes(attributes, range: NSMakeRange(0, attributedString.length))
self.attributedText = attributedString
}
}