Swift 3 - UITextView total line counter - swift

Good day all hope your well.
This possibly seems like a repeat question but nothing is working for me.
I have looked at multiple instances of this question and I have tried many different things and it doesn't solve my problem.
I have a textview that is constrained to 11 Lines and then becomes scrollable..... I want to get the total line count of the text content not the display size on the screen all the solutions just provide me with 11 lines but the true line count is +- 30 lines.Even if I show 5 Lines of text it returns 11
How can i find out the line count of all the text collectivly.
Any help would be much appreciated

Use this extension for solve your problem!
extension String {
func lines(font : UIFont, width : CGFloat) -> Int {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude);
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil);
return Int(boundingBox.height/font.lineHeight);
}
}

Here what I'm doing
extension String {
func lines() -> CGFloat {
let font = //your font
let width = //constrained width to which text can expand
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
return boundingBox.height/font.lineHeight
}
}
This will give you the number of Lines that are actually a string is taking.
Hope it helps!!

Related

Getting UILabel Height by giving String, UIFont and Number of lines

I have found several good answers about how to calculate the height of UILabel with a given text and the UIFont:
extension String {
func heightWithConstrainedWidth(width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [NSAttributedStringKey.font: font], context: nil)
return boundingBox.height
}
}
However, I want to go a little bit further and calculate the height with a given text, font and number of lines. Is that even possible? So, if the height is supposed to be in 2 lines, I would like the height for 2 lines.
Thank you.
In Swift 5:
Rob pointed me in the right direction and I saw that the number of lines can be achieved by using the UIFont.lineHeight.
Solution for getting the right height with a given text, width and max number of lines.
func height(width: CGFloat, font: UIFont, maxLines: CGFloat = 0) -> CGFloat {
//Calculating height
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect,
options: [.usesLineFragmentOrigin, .usesFontLeading],
attributes: [NSAttributedStringKey.font: font], context: nil)
let height = ceil(boundingBox.height)
if maxLines > 0 {
let lines = height / font.lineHeight
if lines >= maxLines {
return (boundingBox.height / lines) * maxLines
}
}
return height
}

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.

NSAttributedString boundingRect returns wrong height

I calculate the height of an NSAttributedString like this.
let maxSize = NSSize(width: w, height: CGFloat.greatestFiniteMagnitude)
let rect = boundingRect(with: maxSize, options: [.usesFontLeading, .usesLineFragmentOrigin])
let height = rect.integral.size.height
I tried about every "hack" that was mentioned on SO, yet the string height gets more inaccurate the smaller the width gets (the calculated height is larger than the actual height).
According to other posts the following things cause problems with size calculation:
Whitespace
No foreground color
No background color
Widths below 300 don't work (?)
I have found that none of these suggestions make any difference. The attributed string is a concatenation of lines, each having a foregroundColor and backgroundColor. The string also has the following paragraph style.
let pstyle = NSMutableParagraphStyle()
pstyle.lineSpacing = 6
pstyle.lineBreakMode = .byWordWrapping
and a userFixedPitchFont of size 11.
Why does the height error get larger the smaller the width is?
PS: I imagine it has something to do with lineBreak since the error gets larger if more lines are word wrapped.
I found that this solution works. Note that if you don't set the lineFragmentPadding to 0, it produces the same (wrong) results as boundingRect.
extension NSAttributedString {
func sizeFittingWidth(_ w: CGFloat) -> CGSize {
let textStorage = NSTextStorage(attributedString: self)
let size = CGSize(width: w, height: CGFloat.greatestFiniteMagnitude)
let boundingRect = CGRect(origin: .zero, size: size)
let textContainer = NSTextContainer(size: size)
textContainer.lineFragmentPadding = 0
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
layoutManager.glyphRange(forBoundingRect: boundingRect, in: textContainer)
let rect = layoutManager.usedRect(for: textContainer)
return rect.integral.size
}
}

Extra blank space when NSTextField is in editing mode

I made an NSTextField subclass which adjusts its width with its content length. The idea (of overriding intrinsicContentSize) is from this question.
override var intrinsicContentSize: NSSize {
if isEditing {
if let fieldEditor =
self.window?.fieldEditor(false, for: self) as?
NSTextView
{
let rect = fieldEditor.layoutManager!.usedRect(
for: fieldEditor.textContainer!
)
let size = rect.size
return size
}
}
let size = self.cell!.cellSize
return size
}
However, there's an extra blank area after the last character. If I set the size.width manually (size.width -= 3.5, for example), the text will offset back and forth (horizontally) during editing.
I don't see this quirk in macOS's Finder when renaming its sidebar items. How to get rid of the extra space without making the text "jumping"?
Update 1:
I added a demo on GitHub.
Update 2:
Tried setting NSTextView's textContainerInset to a size of 0, 0, which doesn't solve the problem.
Update 3:
Updated the repo with #Михаил Масло 's answer. The text still jiggles during editing. The original implementation can be viewed by checking out the initial commit.
You can calculate size of the string with defined font directly try this (I've used your code in TableTextField.swift):
class TableTextField: NSTextField {
...
override var intrinsicContentSize: NSSize {
return stringValue.size(withConstraintedHeight: 1000, font: fieldEditor.font!)
}
...
}
extension String {
func size(withConstraintedHeight height: CGFloat, font: NSFont) -> CGSize {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin,
attributes: [NSAttributedStringKey.font: font], context: nil)
let size = boundingBox.size
return CGSize(width: size.width + 0.5, height: size.height)
}
}
I didn't tend to make it safe so probably you can make it better and exclude force-unwrap and following errors

How to get the height of a UILabel in Swift?

I am a beginner in Swift and I am trying to get the height of a label.
The label has multiple lines of text. I want to know the total height it occupies on the screen.
Swift 4 with extension
extension UILabel{
public var requiredHeight: CGFloat {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: frame.width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.attributedText = attributedText
label.sizeToFit()
return label.frame.height
}
}
it's simple, just call
label.bounds.size.height
Updated for Swift 3
func estimatedHeightOfLabel(text: String) -> CGFloat {
let size = CGSize(width: view.frame.width - 16, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let attributes = [NSFontAttributeName: UIFont.systemFont(ofSize: 10)]
let rectangleHeight = String(text).boundingRect(with: size, options: options, attributes: attributes, context: nil).height
return rectangleHeight
}
override func viewDidLoad() {
super.viewDidLoad()
guard let labelText = label1.text else { return }
let height = estimatedHeightOfLabel(text: labelText)
print(height)
}
Swift 5 ioS 13.2 tested 100%, best solution when the UILabel numberOfLines = 0
Note, result is rounded. Just remove ceil() if you don't want it.
If you want to get height -> give storyboard width of UILabel
If you want to get width -> give storyboard height of UILabel
let stringValue = ""//your label text
let width:CGFloat = 0//storybord width of UILabel
let height:CGFloat = 0//storyboard height of UILabel
let font = UIFont(name: "HelveticaNeue-Bold", size: 18)//font type and size
func getLableHeightRuntime() -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = stringValue.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
return ceil(boundingBox.height)
}
func getLabelWidthRuntime() -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = stringValue.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
return ceil(boundingBox.width)
}
#iajmeri43's answer Updated for Swift 5
func estimatedLabelHeight(text: String, width: CGFloat, font: UIFont) -> CGFloat {
let size = CGSize(width: width, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let attributes = [NSAttributedString.Key.font: font]
let rectangleHeight = String(text).boundingRect(with: size, options: options, attributes: attributes, context: nil).height
return rectangleHeight
}
To use it:
// 1. get the text from the label
guard let theLabelsText = myLabel.text else { return }
// 2. get the width of the view the label is in for example a cell
// Here I'm just stating that the cell is the same exact width of whatever the collection's width is which is usually based on the width of the view that collectionView is in
let widthOfCell = self.collectionView.frame.width
// 3. get the font that your using for the label. For this example the label's font is UIFont.systemFont(ofSize: 17)
let theLabelsFont = UIFont.systemFont(ofSize: 17)
// 4. Plug the 3 values from above into the function
let totalLabelHeight = estimatedLabelHeight(text: theLabelsText, width: widthOfCell, font: theLabelsFont)
// 5. Print out the label's height with decimal values eg. 95.46875
print(totalLabelHeight)
// 6. as #slashburn suggested in the comments, use the ceil() function to round out the totalLabelHeight
let ceilHeight = ceil(totalLabelHeight)
// 7. Print out the ceilHeight rounded off eg. 95.0
print(ceilHeight)