Autoshrink fontsize of UILabeltext siwft - swift

I have a multiline UILabel text which expands dynamically according to the text length. I have setup proper auto layout in storyboard and grows bigger if the text is lengthy.
But i want to reduce the font size to 24 if its 2-line and to 20 if its 3-line text.
If text is single line then, font size should be 34.
I tried minimum font scale = 20/34 = .58
but no use. Please suggest a solution

You could use a switch statement to determine the font size when a certain number of lines have been after changing the label's text.
Using the actualNumberOfLines() algorithm from https://stackoverflow.com/a/60993649/12783209 allows you to redeclare the font for this label whenever necessary.
func changeFontSizeIfNeeded(on label: inout UILabel){
switch label.actualNumberOfLines{
case 2:
label.font = label.font.withSize(24)
case 3:
label.font = label.font.withSize(20)
default:
//Change this to be the default font size
label.font = label.font.withSize(30)
}
}
And add this extension to your project:
extension UILabel{
func actualNumberOfLines()-> Int {
// You have to call layoutIfNeeded() if you are using autoLayout
self.layoutIfNeeded()
let myText = self.text! as NSString
let rect = CGSize(width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude)
let labelSize = myText.boundingRect(with: rect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: self.font as Any], context: nil)
return Int(ceil(CGFloat(labelSize.height) / self.font.lineHeight))
}
}

Related

Swift label not updating frame due change of font size

currently, I am creating a table view that contains an imageView as main UI item inside the cell and on top op that there is a label. Now I'm facing an issue where for long text, the frame of the label is not accurate, and for a long text it is taking a lot of space and it seems like there is a line break but there isn't, this is the issue:
Label issue
This are the properties of the label
Label properties
This is my ViewController and the constraints that I'm using
View Controller on Storyboard
Additional Details
For the Cell, I'm not adding extra code, I just have the IBOutlets and nothing more.
On the CellForRowAtIndexPath, I'm not doing additional code more than setting the image and setting the text.
Thanks
Try to change Content hugging Priority of Label
The font you've selected - Noto Nastaliq Urdu Bold - has very specific type layout properties.
Compare it with the default system font.
Using this code:
let fontDefault = UIFont.systemFont(ofSize: 17.0, weight: .bold)
guard let fontNotoBld = UIFont(name: "NotoNastaliqUrdu-Bold", size: 17) else {
fatalError("Could not create font!")
}
print("fontDefault.lineHeight:\t",fontDefault.lineHeight)
print("fontNotoBld.lineHeight:\t",fontNotoBld.lineHeight)
print()
print("fontDefault.ascender:\t",fontDefault.ascender)
print("fontNotoBld.ascender:\t",fontNotoBld.ascender)
print()
print("fontDefault.descender:\t",fontDefault.descender)
print("fontNotoBld.descender:\t",fontNotoBld.descender)
print()
print("fontDefault.xHeight:\t",fontDefault.xHeight)
print("fontNotoBld.xHeight:\t",fontNotoBld.xHeight)
print()
print("fontDefault.capHeight:\t",fontDefault.capHeight)
print("fontNotoBld.capHeight:\t",fontNotoBld.capHeight)
print()
we get this output in the debug console:
fontDefault.lineHeight: 20.287109375
fontNotoBld.lineHeight: 42.5
fontDefault.ascender: 16.1865234375
fontNotoBld.ascender: 32.368
fontDefault.descender: -4.1005859375
fontNotoBld.descender: -10.132000000000001
fontDefault.xHeight: 9.13916015625
fontNotoBld.xHeight: 9.112
fontDefault.capHeight: 11.97802734375
fontNotoBld.capHeight: 12.138000000000002
I don't know exactly why, but my assumption is that Noto Nastaliq Urdu supports many more language characters which often have very different shapes and heights.
You can either use a more standard font, or design your UI to accommodate the bigger spacing.
Edit
You could try using attributed text with line height settings. However, it will affect the top spacing of the text to a point that it may not suffice.
Here's an example with 3 labels:
top label uses default settings
second label changes maximumLineHeight to 17
third label changes maximumLineHeight to 26
let titleText = "Label with enough text to cause word wrapping onto multiple lines."
guard let fontNotoBold = UIFont(name: "NotoNastaliqUrdu-Bold", size: 17) else {
fatalError("Could not create font!")
}
// without paragraphStyle attribute
var attrString = NSMutableAttributedString(string: titleText)
attrString.addAttribute(NSAttributedString.Key.font, value: fontNotoBold, range:NSMakeRange(0, attrString.length))
label1.attributedText = attrString
// with paragraphStyle max line height: 17
let ps2 = NSMutableParagraphStyle()
ps2.maximumLineHeight = 17
attrString = NSMutableAttributedString(string: titleText)
attrString.addAttribute(NSAttributedString.Key.font, value: fontNotoBold, range:NSMakeRange(0, attrString.length))
attrString.addAttribute(NSAttributedString.Key.paragraphStyle, value:ps2, range:NSMakeRange(0, attrString.length))
label2.attributedText = attrString
// with paragraphStyle max line height: 26
let ps3 = NSMutableParagraphStyle()
ps3.maximumLineHeight = 26
attrString = NSMutableAttributedString(string: titleText)
attrString.addAttribute(NSAttributedString.Key.font, value: fontNotoBold, range:NSMakeRange(0, attrString.length))
attrString.addAttribute(NSAttributedString.Key.paragraphStyle, value:ps3, range:NSMakeRange(0, attrString.length))
label3.attributedText = attrString
Here's the result:
With a little more experimentation, such as adding a newline ("\n") at the beginning, you might be able to get it to a satisfactory appearance.

Attributed string cutting and displaying single word into two lines on UILabel

I'm creating attributed text using following attributes,
func attributedString(font: UIFont, contentColor: UIColor, alignment: NSTextAlignment) -> NSAttributedString {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 0.6
paragraphStyle.lineHeightMultiple = 0.8
paragraphStyle.alignment = alignment
paragraphStyle.lineBreakMode = .byWordWrapping
let lineSpacingAttribute: [NSAttributedStringKey: Any] = [NSAttributedStringKey.paragraphStyle: paragraphStyle, NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: contentColor]
let attributedString = NSAttributedString(string: self, attributes: lineSpacingAttribute)
return attributedString
}
I'm displaying this text on UILabel inside a custom tableViewCell. But, it is cutting a single word into two (cutting last letter of a word and displaying it on next line). I've set the numberOfLines to zero, and preferredMaxLayoutWidth for label. And I'm using a custom font.
This problem is happening on small screens only, iPhone SE and iPhone 5S simulators. But, on other simulators it is displaying correctly. Could you please help me to figure out what is wrong in this?
Thanks!
From Apple doc:
var preferredMaxLayoutWidth: CGFloat
This property affects the size of the label when layout constraints are applied to it. During layout, if the text extends beyond the width specified by this property, the additional text flows to one or more new lines, increasing the height of the label.
Depending on your text you can use the adjustsFontSizeToFitWidth property with the minimumScaleFactor property

tvOS: Anyway to display a subtitle outside of the AVPlayer?

So the scenario is that there is a view where the user can enable/disable subtitles in an app I'm helping to develop.
On that view there is a sample text saying "This is what captions look like", and at the moment it's just a basic, unstyled UILabel. Ideally I would like it to be styled in a similar manner to how the user has customized their captions in the System Settings.
Is this possible in any way? I've envisioned two possible method:
Create an AVPlayer instance and a .vtt file with the text, load it into the view and pause the player. I'm not sure this is possible with a sample video (and it would somehow have to be transparent as there is an image behind the sample sub text).
Somehow get all the styling (font, size, background color, etc) the user has set for their subtitle and create an attributed string to match that
Method 2 seems like the most feasible way, but I don't know if we have access to those settings in code.
So I figured it out! It basically makes use a combination of the Media Accessibility API, which allows you to get the values the user has chosen for their captions/subtitle settings, Attributed Strings, and a subclass UILabel (although this could maybe be substituted with a UITextView as that will allow you to set it's UIEdgeInsets natively)
So, first, the subclass is to allow the UILabel to be inset. This is because captions can have a background color AND a text highlight color and without the inset, the text highlight is all you see. So the function the subclass is simple:
class InsetUILabel: UILabel {
override func drawTextInRect(rect: CGRect) {
let inset: CGFloat = 15
let insets: UIEdgeInsets = UIEdgeInsets(top: inset, left: inset/2, bottom: inset, right: inset/2)
super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets))
}
}
And for generating the actual label. This uses a label called textSample, but you can obviously make it a little more general.
import MediaAccessibility
func styleLabel(sampleText: String) {
let domain = MACaptionAppearanceDomain.User
// Background styling
let backgroundColor = UIColor(CGColor: MACaptionAppearanceCopyWindowColor(domain, nil).takeRetainedValue())
let backgroundOpacity = MACaptionAppearanceGetWindowOpacity(domain, nil)
textSample.layer.backgroundColor = backgroundColor.colorWithAlphaComponent(backgroundOpacity).CGColor
textSample.layer.cornerRadius = MACaptionAppearanceGetWindowRoundedCornerRadius(domain, nil)
// Text styling
var textAttributes = [String:AnyObject]()
let fontDescriptor = MACaptionAppearanceCopyFontDescriptorForStyle(domain, nil, MACaptionAppearanceFontStyle.Default).takeRetainedValue()
let fontName = CTFontDescriptorCopyAttribute(fontDescriptor, "NSFontNameAttribute") as! String
let fontColor = UIColor(CGColor: MACaptionAppearanceCopyForegroundColor(domain, nil).takeRetainedValue())
let fontOpacity = MACaptionAppearanceGetForegroundOpacity(domain, nil)
let textEdgeStyle = MACaptionAppearanceGetTextEdgeStyle(domain, nil)
let textHighlightColor = UIColor(CGColor: MACaptionAppearanceCopyBackgroundColor(domain, nil).takeRetainedValue())
let textHighlightOpacity = MACaptionAppearanceGetBackgroundOpacity(domain, nil)
let textEdgeShadow = NSShadow()
textEdgeShadow.shadowColor = UIColor.blackColor()
let shortShadowOffset: CGFloat = 1.5
let shadowOffset: CGFloat = 3.5
switch(textEdgeStyle) {
case .None:
textEdgeShadow.shadowColor = UIColor.clearColor()
case .DropShadow:
textEdgeShadow.shadowOffset = CGSize(width: -shortShadowOffset, height: shortShadowOffset)
textEdgeShadow.shadowBlurRadius = 6
case .Raised:
textEdgeShadow.shadowOffset = CGSize(width: 0, height: shadowOffset)
textEdgeShadow.shadowBlurRadius = 5
case .Depressed:
textEdgeShadow.shadowOffset = CGSize(width: 0, height: -shadowOffset)
textEdgeShadow.shadowBlurRadius = 5
case .Uniform:
textEdgeShadow.shadowColor = UIColor.clearColor()
textAttributes[NSStrokeColorAttributeName] = UIColor.blackColor()
textAttributes[NSStrokeWidthAttributeName] = -2.0
default:
break
}
textAttributes[NSFontAttributeName] = UIFont(name: fontName, size: (textSample.font?.pointSize)!)
textAttributes[NSForegroundColorAttributeName] = fontColor.colorWithAlphaComponent(fontOpacity)
textAttributes[NSShadowAttributeName] = textEdgeShadow
textAttributes[NSBackgroundColorAttributeName] = textHighlightColor.colorWithAlphaComponent(textHighlightOpacity)
textSample.attributedText = NSAttributedString(string: sampleText, attributes: textAttributes)
}
Now the text highlight section makes use of shadows, with values I think look pretty good, but you might want to tweak them a tiny bit. Hope this helps!

Swift: Programmatically make UILabel bold without changing its size?

I have a UILabel created programmatically. I would like to make the text of the label bold without specifying font size. So far I have only found:
UIFont.boldSystemFont(ofSize: CGFloat)
This is what I have exactly:
let titleLabel = UILabel()
let fontSize: CGFloat = 26
titleLabel.font = UIFont.boldSystemFont(ofSize: titleLabelFontSize)
But this way I am also setting the size. I would like to avoid that. Is there a way?
If there is no way, what would be a good workaround in Swift?
Thank you!
Why not just:
titleLabel.font = UIFont.boldSystemFont(ofSize: titleLabel.font.pointSize)
To just make the Font bold without altering the font size you could create an extension like this (which is based off the answer here:
extension UIFont {
func withTraits(traits:UIFontDescriptorSymbolicTraits...) -> UIFont {
let descriptor = self.fontDescriptor()
.fontDescriptorWithSymbolicTraits(UIFontDescriptorSymbolicTraits(traits))
return UIFont(descriptor: descriptor, size: 0)
}
func bold() -> UIFont {
return withTraits(.TraitBold)
}
}
So that way you could use it like this:
let titleLabel = UILabel()
titleLabel.font = titleLabel.font.bold() //no need to include size!
Update for Swift 4 syntax:
extension UIFont {
func withTraits(traits:UIFontDescriptorSymbolicTraits...) -> UIFont {
let descriptor = self.fontDescriptor
.withSymbolicTraits(UIFontDescriptorSymbolicTraits(traits))
return UIFont(descriptor: descriptor!, size: 0)
}
func bold() -> UIFont {
return withTraits(traits: .traitBold)
}
}

Underline text in a UITextView

How can I underline text in a UITextView. I understand that I would need to create a subclass of UITextView, but what would go under drawRect:?
Thanks.
Try to use NSAttributedString as follows and set in UITextView. This works for iOS6.
NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:#"Some String"];
[attString addAttribute:(NSString*)kCTUnderlineStyleAttributeName
value:[NSNumber numberWithInt:kCTUnderlineStyleSingle]
range:(NSRange){0,[attString length]}];
For more info on NSAttributedString check this How do you use NSAttributedString?
For eg:-
textView.attributedText = attString;
From apple documentation on UITextView,
In iOS 6 and later, this class supports multiple text styles through
use of the attributedText property. (Styled text is not supported in
earlier versions of iOS.) Setting a value for this property causes the
text view to use the style information provided in the attributed
string. You can still use the font, textColor, and textAlignment
properties to set style attributes, but those properties apply to all
of the text in the text view.
attributedText:
The styled text displayed by the text view.
#property(nonatomic,copy) NSAttributedString *attributedText
Discussion: This property is nil by default. Assigning a new value to this property also replaces the value of the text property with the same string data, albeit without any formatting information. In addition, assigning a new a value updates the values in the font, textColor, and textAlignment properties so that they reflect the style information starting at location 0 in the attributed string.
If you want to avoid having to include CoreText, you can utilize an attributed string with this attribute:
#{NSUnderlineStyleAttributeName: #(NSUnderlineStyleSingle)}
If this is static text, you can underline it in Interface Builder. Make sure to make the text 'Attributed' first by selecting 'Attributed' in the drop down menu:
textViewMessage.linkTextAttributes = #{NSForegroundColorAttributeName: [UIColor blueColor], NSUnderlineStyleAttributeName: [NSNumber numberWithInt:NSUnderlineStyleSingle]};
If you want to format your text (with underlined words, links, colored words...) I suggest you to use FTCoreText
-(IBAction)underline:(id)sender
{
NSDictionary *underlineAttribute = #{NSUnderlineStyleAttributeName: #(NSUnderlineStyleSingle)};
texts.attributedText = [[NSAttributedString alloc] initWithString:texts.text
attributes:underlineAttribute];
}
You can't use "kCTUnderlineStyleAttributeName" or "kCTUnderlineStyleSingle"
Now you must do it like this:
NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:#"Text"];
[attString addAttribute:NSUnderlineStyleAttributeName
value:#(NSUnderlineStyleSingle)
range:(NSRange){0,[attString length]}];
If you are using iOS 6 then you can use the attributedText attribute of UITextView. Apply underline formatting to the text. You can also set the typingAttributes property to ensure the text that the user types has a specific set of formatting if you wish.
I recommend you to use CoreText. A Basic tutorial is here on raywenderlich.
I recommend you to use MFUnderlinedTextView, it will be helpful.
This is how I did it using Swift 5:
let attributedString = NSMutableAttributedString(string: myTextView.text ?? "")
myTextView.linkTextAttributes = [NSAttributedString.Key(rawValue: NSAttributedString.Key.underlineStyle.rawValue): NSUnderlineStyle.single.rawValue] as [NSAttributedString.Key: Any]?
myTextView.attributedText = attributedString
Swift 5.
As my UITextView if for inserting text, I created an extension function as bellow.
extension UITextView {
func underlined() {
let border = CALayer()
let width = CGFloat(1.0)
border.borderColor = UIColor.lightGray.cgColor
border.frame = CGRect(x: 0, y: self.frame.size.height - 5, width: self.frame.size.width, height: 1)
border.borderWidth = width
self.layer.addSublayer(border)
self.layer.masksToBounds = true
let style = NSMutableParagraphStyle()
style.lineSpacing = 15
let attributes = [NSAttributedString.Key.paragraphStyle : style, NSAttributedString.Key.foregroundColor : UIColor.darkGray, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 13)]
self.attributedText = NSAttributedString(string: self.text, attributes: attributes)
}
}
The border is drawling the line and the style is adding the spacing between the lines.
Usage in your UIView custom layout:
override func layoutSubviews() {
super.layoutSubviews()
self.dateAndTimeInput.underlined()
}
Image with the result
let someString = NSMutableAttributedString(string: "Your String", attributes: [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 20), NSAttributedString.Key.underlineStyle : NSUnderlineStyle.single.rawValue])
someStringTextView.attributedText = titleAT
U can just give your string a bunch of attributes like bold, underlined etc.
To underline a text, you have to go where you can select copy, cut , delete options there are now more options like B/I/U( bold, italic, underline). Choose this option and this is it. And to unable it, choose underline option again.