I have a UILabel where I want to add space in the top and in the bottom.
With minimun height in constrainst I've modified it to:
EDIT: To do this I've used:
override func drawTextInRect(rect: CGRect) {
var insets: UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 10.0)
super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets))
}
But I've to find a different method because if I write more than two lines the problem is the same:
If you want to stick with UILabel, without subclassing it, Mundi has given you a clear solution.
If alternatively you would be willing to avoid wrapping the UILabel with a UIView, you could use UITextView to enable the use of UIEdgeInsets (padding) or subclass UILabel to support UIEdgeInsets.
Using a UITextView you would only need to provide the insets (OBJ-C):
textView.textContainerInset = UIEdgeInsetsMake(10, 0, 10, 0);
Alternative, if you subclass UILabel, an example to this approach would be overriding the drawTextInRect method
(OBJ-C)
- (void)drawTextInRect:(CGRect)uiLabelRect {
UIEdgeInsets myLabelInsets = {10, 0, 10, 0};
[super drawTextInRect:UIEdgeInsetsInsetRect(uiLabelRect, myLabelInsets)];
}
You could additionally provide your new subclassed UILabel with a insets variable for TOP, LEFT, BOTTOM and RIGHT.
An example code could be:
In .h (OBJ-C)
float topInset, leftInset,bottomInset, rightInset;
In .m (OBJ-C)
- (void)drawTextInRect:(CGRect)uiLabelRect {
[super drawTextInRect:UIEdgeInsetsInsetRect(uiLabelRect, UIEdgeInsetsMake(topInset,leftInset,bottomInset,rightInset))];
}
EDIT #1:
From what I have seen, it seems you have to override the intrinsicContentSize of the UILabel when subclassing it.
So you should override intrinsicContentSize like:
- (CGSize) intrinsicContentSize {
CGSize intrinsicSuperViewContentSize = [super intrinsicContentSize] ;
intrinsicSuperViewContentSize.height += topInset + bottomInset ;
intrinsicSuperViewContentSize.width += leftInset + rightInset ;
return intrinsicSuperViewContentSize ;
}
And add the following method to edit your insets, instead of editing them individually:
- (void) setContentEdgeInsets:(UIEdgeInsets)edgeInsets {
topInset = edgeInsets.top;
leftInset = edgeInsets.left;
rightInset = edgeInsets.right;
bottomInset = edgeInsets.bottom;
[self invalidateIntrinsicContentSize] ;
}
It will update the size of your UILabel to match the edge insets and cover multiline necessity you referred.
Edit #2
After searching a bit I have found this Gist with a IPInsetLabel. If none of those solutions work you could try it out.
Edit #3
There was similar question (duplicate) about this matter.
For a full list of available solutions, see this answer: UILabel text margin
I have tried with it on Swift 4.2, hopefully it work for you!
@IBDesignable class PaddingLabel: UILabel {
@IBInspectable var topInset: CGFloat = 5.0
@IBInspectable var bottomInset: CGFloat = 5.0
@IBInspectable var leftInset: CGFloat = 7.0
@IBInspectable var rightInset: CGFloat = 7.0
override func drawText(in rect: CGRect) {
let insets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
super.drawText(in: rect.inset(by: insets))
}
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(width: size.width + leftInset + rightInset,
height: size.height + topInset + bottomInset)
}
}
Or you can use CocoaPods here https://github.com/levantAJ/PaddingLabel
pod 'PaddingLabel', '1.1'
Swift 3
import UIKit
class PaddingLabel: UILabel {
@IBInspectable var topInset: CGFloat = 5.0
@IBInspectable var bottomInset: CGFloat = 5.0
@IBInspectable var leftInset: CGFloat = 5.0
@IBInspectable var rightInset: CGFloat = 5.0
override func drawText(in rect: CGRect) {
let insets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))
}
override var intrinsicContentSize: CGSize {
get {
var contentSize = super.intrinsicContentSize
contentSize.height += topInset + bottomInset
contentSize.width += leftInset + rightInset
return contentSize
}
}
}
SWIFT 4
Easy to use solution, available for all UILabel child in project.
Example:
let label = UILabel()
label.<Do something>
label.padding = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 0)
UILabel Extension
import UIKit
extension UILabel {
private struct AssociatedKeys {
static var padding = UIEdgeInsets()
}
public var padding: UIEdgeInsets? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.padding) as? UIEdgeInsets
}
set {
if let newValue = newValue {
objc_setAssociatedObject(self, &AssociatedKeys.padding, newValue as UIEdgeInsets?, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
override open func draw(_ rect: CGRect) {
if let insets = padding {
self.drawText(in: rect.inset(by: insets))
} else {
self.drawText(in: rect)
}
}
override open var intrinsicContentSize: CGSize {
guard let text = self.text else { return super.intrinsicContentSize }
var contentSize = super.intrinsicContentSize
var textWidth: CGFloat = frame.size.width
var insetsHeight: CGFloat = 0.0
var insetsWidth: CGFloat = 0.0
if let insets = padding {
insetsWidth += insets.left + insets.right
insetsHeight += insets.top + insets.bottom
textWidth -= insetsWidth
}
let newSize = text.boundingRect(with: CGSize(width: textWidth, height: CGFloat.greatestFiniteMagnitude),
options: NSStringDrawingOptions.usesLineFragmentOrigin,
attributes: [NSAttributedString.Key.font: self.font], context: nil)
contentSize.height = ceil(newSize.size.height) + insetsHeight
contentSize.width = ceil(newSize.size.width) + insetsWidth
return contentSize
}
}
Just use a UIView as a superview and define a fixed margin to the label with auto layout.
Just use a UIButton, its already built in. Turn off all the extra button features and you have a label that you can set edge instets on.
let button = UIButton()
button.contentEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
button.setTitle("title", for: .normal)
button.tintColor = .white // this will be the textColor
button.isUserInteractionEnabled = false
Without Storyboard:
class PaddingLabel: UILabel {
var topInset: CGFloat
var bottomInset: CGFloat
var leftInset: CGFloat
var rightInset: CGFloat
required init(withInsets top: CGFloat, _ bottom: CGFloat,_ left: CGFloat,_ right: CGFloat) {
self.topInset = top
self.bottomInset = bottom
self.leftInset = left
self.rightInset = right
super.init(frame: CGRect.zero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func drawText(in rect: CGRect) {
let insets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))
}
override var intrinsicContentSize: CGSize {
get {
var contentSize = super.intrinsicContentSize
contentSize.height += topInset + bottomInset
contentSize.width += leftInset + rightInset
return contentSize
}
}
}
Usage:
let label = PaddingLabel(8, 8, 16, 16)
label.font = .boldSystemFont(ofSize: 16)
label.text = "Hello World"
label.backgroundColor = .black
label.textColor = .white
label.textAlignment = .center
label.layer.cornerRadius = 8
label.clipsToBounds = true
label.sizeToFit()
view.addSubview(label)
Result:
Swift 3 Code with Implementation Example
class UIMarginLabel: UILabel {
var topInset: CGFloat = 0
var rightInset: CGFloat = 0
var bottomInset: CGFloat = 0
var leftInset: CGFloat = 0
override func drawText(in rect: CGRect) {
let insets: UIEdgeInsets = UIEdgeInsets(top: self.topInset, left: self.leftInset, bottom: self.bottomInset, right: self.rightInset)
self.setNeedsLayout()
return super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))
}
}
class LabelVC: UIViewController {
//Outlets
@IBOutlet weak var labelWithMargin: UIMarginLabel!
override func viewDidLoad() {
super.viewDidLoad()
//Label settings.
labelWithMargin.leftInset = 10
view.layoutIfNeeded()
}
}
Don't forget to add class name UIMarginLabel in storyboard label object. Happy Coding!
As per Swift 4.2 (Xcode 10 beta 6) "UIEdgeInsetsInsetRect" being deprecated. I've also declared the class public to make it more useful.
public class UIPaddedLabel: UILabel {
@IBInspectable var topInset: CGFloat = 5.0
@IBInspectable var bottomInset: CGFloat = 5.0
@IBInspectable var leftInset: CGFloat = 7.0
@IBInspectable var rightInset: CGFloat = 7.0
public override func drawText(in rect: CGRect) {
let insets = UIEdgeInsets.init(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
super.drawText(in: rect.inset(by: insets))
}
public override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(width: size.width + leftInset + rightInset,
height: size.height + topInset + bottomInset)
}
public override func sizeToFit() {
super.sizeThatFits(intrinsicContentSize)
}
}
Swift 4+
class EdgeInsetLabel: UILabel {
var textInsets = UIEdgeInsets.zero {
didSet { invalidateIntrinsicContentSize() }
}
override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
let textRect = super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines)
let invertedInsets = UIEdgeInsets(top: -textInsets.top,
left: -textInsets.left,
bottom: -textInsets.bottom,
right: -textInsets.right)
return textRect.inset(by: invertedInsets)
}
override func drawText(in rect: CGRect) {
super.drawText(in: rect.inset(by: textInsets))
}
}
usage:
let label = EdgeInsetLabel()
label.textInsets = UIEdgeInsets(top: 2, left: 6, bottom: 2, right: 6)
Swift 3, iOS10 solution:
open class UIInsetLabel: UILabel {
open var insets : UIEdgeInsets = UIEdgeInsets() {
didSet {
super.invalidateIntrinsicContentSize()
}
}
open override var intrinsicContentSize: CGSize {
var size = super.intrinsicContentSize
size.width += insets.left + insets.right
size.height += insets.top + insets.bottom
return size
}
override open func drawText(in rect: CGRect) {
return super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))
}
}
Subclass UILabel. (File-New-File- CocoaTouchClass-make Subclass of UILabel).
// sampleLabel.swift
import UIKit
class sampleLabel: UILabel {
let topInset = CGFloat(5.0), bottomInset = CGFloat(5.0), leftInset = CGFloat(8.0), rightInset = CGFloat(8.0)
override func drawTextInRect(rect: CGRect) {
let insets: UIEdgeInsets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets))
}
override func intrinsicContentSize() -> CGSize {
var intrinsicSuperViewContentSize = super.intrinsicContentSize()
intrinsicSuperViewContentSize.height += topInset + bottomInset
intrinsicSuperViewContentSize.width += leftInset + rightInset
return intrinsicSuperViewContentSize
}
}
On ViewController:
override func viewDidLoad() {
super.viewDidLoad()
let labelName = sampleLabel(frame: CGRectMake(0, 100, 300, 25))
labelName.text = "Sample Label"
labelName.backgroundColor = UIColor.grayColor()
labelName.textColor = UIColor.redColor()
labelName.shadowColor = UIColor.blackColor()
labelName.font = UIFont(name: "HelveticaNeue", size: CGFloat(22))
self.view.addSubview(labelName)
}
OR Associate custom UILabel class on Storyboard as Label's class.
I edited a little in the accepted answer. There is a problem when leftInset and rightInset increase, a part of text will be disappeared, b/c the width of label will be narrowed but the height does not increase as figure:
To resolve this problem you need to re-calculate height of text as follow:
@IBDesignable class PaddingLabel: UILabel {
@IBInspectable var topInset: CGFloat = 20.0
@IBInspectable var bottomInset: CGFloat = 20.0
@IBInspectable var leftInset: CGFloat = 20.0
@IBInspectable var rightInset: CGFloat = 20.0
override func drawTextInRect(rect: CGRect) {
let insets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets))
}
override func intrinsicContentSize() -> CGSize {
var intrinsicSuperViewContentSize = super.intrinsicContentSize()
let textWidth = frame.size.width - (self.leftInset + self.rightInset)
let newSize = self.text!.boundingRectWithSize(CGSizeMake(textWidth, CGFloat.max), options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: [NSFontAttributeName: self.font], context: nil)
intrinsicSuperViewContentSize.height = ceil(newSize.size.height) + self.topInset + self.bottomInset
return intrinsicSuperViewContentSize
}
}
and result:
I hope to help some people in the same situation as me.
Just use autolayout:
let paddedWidth = myLabel.intrinsicContentSize.width + 2 * padding
myLabel.widthAnchor.constraint(equalToConstant: paddedWidth).isActive = true
Done.
Another option without subclassing would be to:
- Set label
text sizeToFit()then increase label height a little to simulate padding
label.text = "someText" label.textAlignment = .center label.sizeToFit() label.frame = CGRect( x: label.frame.x, y: label.frame.y,width: label.frame.width + 20,height: label.frame.height + 8)
In Swift 3
best and simple way
class UILabelPadded: UILabel {
override func drawText(in rect: CGRect) {
let insets = UIEdgeInsets.init(top: 0, left: 5, bottom: 0, right: 5)
super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))
}
}
Just like other answers but fix a bug.
When label.width is controlled by auto layout, sometimes text will be cropped.
@IBDesignable
class InsetLabel: UILabel {
@IBInspectable var topInset: CGFloat = 4.0
@IBInspectable var leftInset: CGFloat = 4.0
@IBInspectable var bottomInset: CGFloat = 4.0
@IBInspectable var rightInset: CGFloat = 4.0
var insets: UIEdgeInsets {
get {
return UIEdgeInsets.init(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
}
set {
topInset = newValue.top
leftInset = newValue.left
bottomInset = newValue.bottom
rightInset = newValue.right
}
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
var adjSize = super.sizeThatFits(size)
adjSize.width += leftInset + rightInset
adjSize.height += topInset + bottomInset
return adjSize
}
override var intrinsicContentSize: CGSize {
let systemContentSize = super.intrinsicContentSize
let adjustSize = CGSize(width: systemContentSize.width + leftInset + rightInset, height: systemContentSize.height + topInset + bottomInset)
if adjustSize.width > preferredMaxLayoutWidth && preferredMaxLayoutWidth != 0 {
let constraintSize = CGSize(width: bounds.width - (leftInset + rightInset), height: .greatestFiniteMagnitude)
let newSize = super.sizeThatFits(constraintSize)
return CGSize(width: systemContentSize.width, height: ceil(newSize.height) + topInset + bottomInset)
} else {
return adjustSize
}
}
override func drawText(in rect: CGRect) {
super.drawText(in: rect.inset(by: insets))
}
}
Easy padding (Swift 3.0, Alvin George answer):
class NewLabel: UILabel {
override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
return self.bounds.insetBy(dx: CGFloat(15.0), dy: CGFloat(15.0))
}
override func draw(_ rect: CGRect) {
super.drawText(in: self.bounds.insetBy(dx: CGFloat(5.0), dy: CGFloat(5.0)))
}
}
An elaboration on Mundi's answer.
i.e. embedding a label in a UIView and enforcing padding through Auto Layout. Example:
Overview:
1) Create a UIView ("panel"), and set its appearance.
2) Create a UILabel and add it to the panel.
3) Add constraints to enforce padding.
4) Add the panel to your view hierarchy, then position the panel.
Details:
1) Create the panel view.
let panel = UIView()
panel.backgroundColor = .green
panel.layer.cornerRadius = 12
2) Create the label, add it to the panel as a subview.
let label = UILabel()
panel.addSubview(label)
3) Add constraints between the edges of the label and the panel. This forces the panel to keep a distance from the label. i.e. "padding"
Editorial: doing all this by hand is super-tedious, verbose and error-prone. I suggest you pick an Auto Layout wrapper from github or write one yourself
label.panel.translatesAutoresizingMaskIntoConstraints = false
label.topAnchor.constraint(equalTo: panel.topAnchor,
constant: vPadding).isActive = true
label.bottomAnchor.constraint(equalTo: panel.bottomAnchor,
constant: -vPadding).isActive = true
label.leadingAnchor.constraint(equalTo: panel.leadingAnchor,
constant: hPadding).isActive = true
label.trailingAnchor.constraint(equalTo: panel.trailingAnchor,
constant: -hPadding).isActive = true
label.textAlignment = .center
4) Add the panel to your view hierarchy and then add positioning constraints. e.g. hug the right-hand side of a tableViewCell, as in the example image.
Note: you only need to add positional constraints, not dimensional constraints: Auto Layout will solve the layout based on both the intrinsicContentSize of the label and the constraints added earlier.
hostView.addSubview(panel)
panel.translatesAutoresizingMaskIntoConstraints = false
panel.trailingAnchor.constraint(equalTo: hostView.trailingAnchor,
constant: -16).isActive = true
panel.centerYAnchor.constraint(equalTo: hostView.centerYAnchor).isActive = true
Easy way
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.addSubview(makeLabel("my title",x: 0, y: 100, w: 320, h: 30))
}
func makeLabel(title:String, x:CGFloat, y:CGFloat, w:CGFloat, h:CGFloat)->UILabel{
var myLabel : UILabel = UILabel(frame: CGRectMake(x,y,w,h))
myLabel.textAlignment = NSTextAlignment.Right
// inser last char to right
var titlePlus1char = "\(title)1"
myLabel.text = titlePlus1char
var titleSize:Int = count(titlePlus1char)-1
myLabel.textColor = UIColor(red:1.0, green:1.0,blue:1.0,alpha:1.0)
myLabel.backgroundColor = UIColor(red: 214/255, green: 167/255, blue: 0/255,alpha:1.0)
// create myMutable String
var myMutableString = NSMutableAttributedString()
// create myMutable font
myMutableString = NSMutableAttributedString(string: titlePlus1char, attributes: [NSFontAttributeName:UIFont(name: "HelveticaNeue", size: 20)!])
// set margin size
myMutableString.addAttribute(NSFontAttributeName, value: UIFont(name: "HelveticaNeue", size: 10)!, range: NSRange(location: titleSize,length: 1))
// set last char to alpha 0
myMutableString.addAttribute(NSForegroundColorAttributeName, value: UIColor(red:1.0, green:1.0,blue:1.0,alpha:0), range: NSRange(location: titleSize,length: 1))
myLabel.attributedText = myMutableString
return myLabel
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Similar to other answers, but with a func class to setup the padding dinamically:
class UILabelExtendedView: UILabel
{
var topInset: CGFloat = 4.0
var bottomInset: CGFloat = 4.0
var leftInset: CGFloat = 8.0
var rightInset: CGFloat = 8.0
override func drawText(in rect: CGRect)
{
let insets: UIEdgeInsets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))
}
override public var intrinsicContentSize: CGSize
{
var contentSize = super.intrinsicContentSize
contentSize.height += topInset + bottomInset
contentSize.width += leftInset + rightInset
return contentSize
}
func setPadding(top: CGFloat, left: CGFloat, bottom: CGFloat, right: CGFloat){
self.topInset = top
self.bottomInset = bottom
self.leftInset = left
self.rightInset = right
let insets: UIEdgeInsets = UIEdgeInsets(top: top, left: left, bottom: bottom, right: right)
super.drawText(in: UIEdgeInsetsInsetRect(self.frame, insets))
}
}
If you want to add 2px padding around the textRect, just do this:
let insets = UIEdgeInsets(top: -2, left: -2, bottom: -2, right: -2)
label.frame = UIEdgeInsetsInsetRect(textRect, insets)
If you don't want or need to use an @IBInspectable / @IBDesignable UILabel in Storyboard (I think those are rendered too slow anyway), then it is cleaner to use UIEdgeInsets instead of 4 different CGFloats.
Code example for Swift 4.2:
class UIPaddedLabel: UILabel {
var padding = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
public override func drawText(in rect: CGRect) {
super.drawText(in: rect.inset(by: padding))
}
public override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(width: size.width + padding.left + padding.right,
height: size.height + padding.top + padding.bottom)
}
}
Use this code if you are facing text trimming problem while applying padding.
@IBDesignable class PaddingLabel: UILabel {
@IBInspectable var topInset: CGFloat = 5.0
@IBInspectable var bottomInset: CGFloat = 5.0
@IBInspectable var leftInset: CGFloat = 5.0
@IBInspectable var rightInset: CGFloat = 5.0
override func drawText(in rect: CGRect) {
let insets = UIEdgeInsets.init(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))
}
override var intrinsicContentSize: CGSize {
var intrinsicSuperViewContentSize = super.intrinsicContentSize
let textWidth = frame.size.width - (self.leftInset + self.rightInset)
let newSize = self.text!.boundingRect(with: CGSize(textWidth, CGFloat.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: self.font], context: nil)
intrinsicSuperViewContentSize.height = ceil(newSize.size.height) + self.topInset + self.bottomInset
return intrinsicSuperViewContentSize
}
}
extension CGSize{
init(_ width:CGFloat,_ height:CGFloat) {
self.init(width:width,height:height)
}
}
Easy padding:
import UIKit
class NewLabel: UILabel {
override func textRectForBounds(bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
return CGRectInset(self.bounds, CGFloat(15.0), CGFloat(15.0))
}
override func drawRect(rect: CGRect) {
super.drawTextInRect(CGRectInset(self.bounds,CGFloat(5.0), CGFloat(5.0)))
}
}
One pragmatic solution is to add blank labels of the same height and color as the main label. Set the leading/trailing space to the main label to zero, align vertical centers, and make the width your desired margin.
Swift 4+
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.firstLineHeadIndent = 10
// Swift 4.2++
label.attributedText = NSAttributedString(string: "Your text", attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle])
// Swift 4.1--
label.attributedText = NSAttributedString(string: "Your text", attributes: [NSAttributedStringKey.paragraphStyle: paragraphStyle])
来源:https://stackoverflow.com/questions/27459746/adding-space-padding-to-a-uilabel






