Swift iOS - How to line up a sublayer position to match the center of button text - ios

I have a button that is text. I added a red background subLayer and I made the backgroundLayer's width and height bigger then the button text. I tried to center the background layer to the button using:
backgroundLayer.position = button.center
It's not centering. This is what I get:
I know I can set the background color and cornerRadius on the button directly but when I do it that way the red background hugs the text:
button.backgroundColor = UIColor.red
button.layer.cornerRadius = 10
I want the redbackground to be wider and taller then the text:
backgroundLayer.frame = CGRect(x: 0, y: 0, width: buttonTextSize.width + 10, height: buttonTextSize.height + 5
I would Photoshop an example but I don't have Photshop in front of me at the moment. This is the closest I can find. This is a button from Vimeo. They aren't using text but the backgroundLayer is much wider and taller then the button image and the backgroundLayer's position is aligned with the button's midX and midY:
How do I get the position of the background subLayer to line up with the center of the button's text?
let button: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Next", for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.contentHorizontalAlignment = .center
button.titleLabel?.font = UIFont.systemFont(ofSize: 23)
return button
}()
let backgroundLayer: CALayer = {
let layer = CALayer()
layer.backgroundColor = UIColor.red.cgColor
layer.cornerRadius = 10
return layer
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
view.addSubview(button)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
let buttonText = button.titleLabel?.text
let buttonTextSize = (buttonText! as NSString).size(withAttributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 23.0)])
// I added 10 points to the backgroundLayer's width and 5 points to the backgroundLayer's height so its wider then the text
backgroundLayer.frame = CGRect(x: 0, y: 0, width: buttonTextSize.width + 10, height: buttonTextSize.height + 5)
button.layer.insertSublayer(backgroundLayer, at: 0)
backgroundLayer.position = button.center
}

Here's a button that seems to look the way you want (of course you can adjust any parameters that don't suit your sensibilities):
This button is automatically red, corner-rounded, and considerably larger than its text (even when the button is positioned using auto layout).
Here's how it was achieved through a subclass:
class MyRedButton : UIButton {
override func layoutSubviews() {
super.layoutSubviews()
self.backgroundColor = .red
self.layer.cornerRadius = 10
}
override var intrinsicContentSize: CGSize {
var sz = super.intrinsicContentSize
sz.width += 30; sz.height += 30
return sz
}
}

Matt's upvoted answer is correct.
Two things he pointed out to me in the comments that I was initially doing wrong was.
I tried to set backgroundLayer.position = button.center. This is wrong because the button's center is based on the frame's center and not it's bounds center. I should've set the backgroundLayer.position to match the center of the button's bounds
I tried to set the backgroundLayer's position to the button's center in viewWillLayoutSubviews which he said the button's bounds weren't known yet so the backgroundLayer had no information to base it on. I was supposed to add the code to viewDidLayoutSubviews
Here's the code here:
// 1. add the code to viewDidLayoutSubviews
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let text = button.titleLabel?.text
let textSize = (text! as NSString).size(withAttributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 23.0)])
backgroundLayer.frame = CGRect(x: 0, y: 0, width: textSize.width + 10, height: textSize.height + 5)
button.layer.insertSublayer(backgroundLayer, at: 0)
// 2. get the buttons bound's center by accessing it's midX and midY
let buttonMidX = button.bounds.midX
let buttonMidY = button.bounds.midY
let buttonBoundsCenter = CGPoint(x: buttonMidX, y: buttonMidY)
// 3. set the backgroundLayer's postion to the buttonBoundsCenter
backgroundLayer.position = buttonBoundsCenter
}
And it works:

Related

Swift & UILabel : How to add padding and margin in Swift programmatically? [duplicate]

This question already has answers here:
Add padding between label and its border
(4 answers)
Closed 8 months ago.
I have created a text programmatically with a grey background using UILabel.
Now I would like to add padding to this paragraph/text. Also, it would be great if you could show me how to add margin to my UILabel as well.
import UIKit
final class SignUpViewController: UIViewController {
public let identifier = "Sign Up"
private let logoImage : UIImageView = {
let imageView = UIImageView()
imageView.layer.masksToBounds = true
imageView.contentMode = .scaleAspectFit
imageView.image = UIImage(named: "MyLogoWithTitle")
imageView.clipsToBounds = true
return imageView
}()
private let instructionText : UILabel = {
let label = UILabel()
label.text = "Please read terms and conditions below carefully before proceeding with the registration."
label.backgroundColor = UIColor().colorFromHex(hex: "#2C333C", opacity: 0.4)
label.numberOfLines = 0
label.tintColor = .white
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.addSubview(logoImage)
view.addSubview(instructionText)
view.backgroundColor = UIColor().colorFromHex(hex: "#141920", opacity: 1.0)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
logoImage.frame = CGRect(x: 0,
y: 0,
width: 140,
height: 60)
logoImage.center = CGPoint(x: view.center.x, y: view.height/5)
instructionText.frame = CGRect(
x: 5,
y: 5 + logoImage.bottom,
width: view.width - 20,
height: 50)
.integral
instructionText.layer.cornerRadius = 10
}
}
Notice that I created an extension to UIColor so that I can input hex color in this way - UIColor().colorFromHex(hex: "#2C333C", opacity: 0.4) .
I am looking forward to hearing from you. Thank you.
You can insert this UILabel into the container (any UIView) and set its position inside.
But the simplest trick is to use UIButton instead of UILabel. You can configure UIEdgeInsets for padding.
So that UIButton does not act as a button simply set button.isUserInteractionEnabled = false.
By default, text in the button are placed in the center, but its position is easy to change with contentHorizontalAlignment and contentVerticalAlignment
And as a bonus, you can add icons right near to the text. :)
UPD.
Could you give me a simple example? I tried that way but I didn't get the result I expected. – Punreach Rany
let buttonUsedAsLabel = UIButton()
// Your question was about padding
// It's it!
buttonUsedAsLabel.titleEdgeInsets = UIEdgeInsets(top: 5, left: 20, bottom: 5, right: 20)
// Make it not user interactable as UILabel is
buttonUsedAsLabel.isUserInteractionEnabled = false
// set any other properties
buttonUsedAsLabel.setTitleColor(.white, for: .normal)
buttonUsedAsLabel.contentVerticalAlignment = .top
buttonUsedAsLabel.contentHorizontalAlignment = .left
// Set title propeties AFTER it was created with text because it's nullable
// You can use attributed title also
// Never use any (button.titleLabel) before its creation
// for example: (button.titleLabel?.text = "zzz") do nothing here
buttonUsedAsLabel.setTitle("This is the text", for: .normal)
buttonUsedAsLabel.titleLabel?.font = .systemFont(ofSize: 20, weight: .medium)
buttonUsedAsLabel.titleLabel?.numberOfLines = 0
buttonUsedAsLabel.titleLabel?.lineBreakMode = .byWordWrapping
// and so on
// ...
// This is the triсk :)
Of course, you can do it with a storyboard if prefer.
1. Add this class
PaddingLabel.swift
import UIKit
class PaddingLabel: UILabel {
var edgeInset: UIEdgeInsets = .zero
override func drawText(in rect: CGRect) {
let insets = UIEdgeInsets.init(top: edgeInset.top, left: edgeInset.left, bottom: edgeInset.bottom, right: edgeInset.right)
super.drawText(in: rect.inset(by: insets))
}
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(width: size.width + edgeInset.left + edgeInset.right, height: size.height + edgeInset.top + edgeInset.bottom)
}
}
2. Add this code to your ViewController
let label = PaddingLabel()
override func viewDidLoad() {
super.viewDidLoad()
label.backgroundColor = UIColor().colorFromHex(hex: "#2C333C", opacity: 0.4)
//Setting the padding label
label.edgeInset = UIEdgeInsets(top: 5, left: 10, bottom: 5, right: 10)
}
The answer to the link below is that I wrote the same content based on the storyboard.
Add padding between label and its border
I use textfield. Set padding and text in textfield. And do not allow editing.
extension UITextField {
func addLeftPadding() {
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 12, height: self.frame.height))
self.leftView = paddingView
self.leftViewMode = ViewMode.always
}
}
//ViewController
#IBOutlet weak var myTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
myTextField.addLeftPadding()
myTextField.isUserInteractionEnabled = false
myTextField.text = "your label text"
}

Text overlapping issue in Textfield in iOS Swift

In my textfield there is a right view and datepicker added. When I select the date, text is overlapping with the right view.
I am using a Designable class for adding right view. How can I fix overlapping issue
Here is the code for setting up rightview
rightViewMode = UITextField.ViewMode.always
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 17, height: 17))
imageView.contentMode = .scaleAspectFit
imageView.image = rightImage
imageView.tintColor = color
// Added containerView for repositioning image
let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 40, height: 20))
self.addSubview(containerView)
containerView.addSubview(imageView)
rightView = containerView
Without seeing all your code it's kind of hard to tell what is going on here. Do you use storyboards and constraints? Or do you hardcode all the frames of your views?
What I would do here, is using a UIStackView and constraints, because it helps getting rid of all the hardcoded positioning values, and it gives you much more flexibility for laying out your UI.
let textField = UITextField()
textField.placeholder = "16 December 2018"
let imageView = UIImageView()
imageView.backgroundColor = .systemBlue
let stackView = UIStackView(arrangedSubviews: [textField, imageView])
stackView.axis = .horizontal
stackView.distribution = .fill
stackView.alignment = .center
stackView.spacing = 10
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
imageView.widthAnchor.constraint(equalToConstant: 17).isActive = true
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor).isActive = true
As you can see I set the stack view's distribution property to .fill here, so because the width of your image is constrained to 17, your text field width will adjust to fill the width of the stack view. You may want to adjust this property, and the spacing property, depending on what kind of behaviour you're looking for.
I have solved my problem with this peace of code:
Added Padding For Textfields
let padding = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: padding)
}

IOS 13: spacing Issue with UITextField rightView

I am facing a spacing issue with a right view of UITextField in IOS 13, See my following code and screenshot of ios 13 and ios 12.4
In IOS 12.4 simulator display proper space in the right view (UIButton)of UITextField
In IOS 13.0 simulator has a spacing issue in the right view (UIButton)of UITextField
let dropdownButton = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: txtField.frame.height))
dropdownButton.backgroundColor = UIColor.clear
dropdownButton.setImage(UIImage(named: "ic_DownArrow"), for: UIControl.State())
txtField.rightView = dropdownButton
txtField.rightViewMode = .always
Apparently this was a change in the way rightViewRect(forBounds:) behaves in iOS 13 Beta 5.
From the iOS & iPadOS 13 Developer Beta 5 Release Notes:
UIKit - Resolved Issues
Prior to iOS 13, UITextField assumed that the frames of its leftView and rightView were correctly set when assigned and would never change. Starting in iOS 13, the implementation of leftViewRect(forBounds:) and rightViewRect(forBounds:) now ask the view for its systemLayoutSizeFitting(:). To achieve the previous behavior when linking against and running on iOS 13, add explicit sizing constraints on the view, wrap it in a plain UIView, or subclass the view and implement systemLayoutSizeFitting(:). (51787798)
So Add Auto-Layout constraints to your custom view that you added to the rightView
Example:-
override func rightViewRect(forBounds bounds: CGRect) -> CGRect {
return CGRect(x: bounds.width - 30, y: 0, width: 20 , height: bounds.height)
}
Set the width constraint for the leftView or rightView you're adding.
leftImageView.widthAnchor.set(to: 30.0)
textField.leftView = leftImageView
textField.leftViewMode = .always
Here's the extension I use to set the width constraint:
extension NSLayoutDimension {
#discardableResult
func set(
to constant: CGFloat,
priority: UILayoutPriority = .required
) -> NSLayoutConstraint {
let cons = constraint(equalToConstant: constant)
cons.priority = priority
cons.isActive = true
return cons
}
}
Probably your image is smaller than width: 50, height: txtField.frame.height, so your button is reduced.
You can try add some container:
let dropdownButton = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: txtField.frame.height))
dropdownButton.backgroundColor = .clear
dropdownButton.setImage(UIImage(named: "ic_DownArrow"), for: UIControl.State())
let container = UIView(frame: dropdownButton.frame)
container.backgroundColor = .clear
container.addSubview(dropdownButton)
txtField.rightView = container
txtField.rightViewMode = .always
This worked for me and it also solves the issue that happens in iPhone X/11, when the rightView takes up full width of the textField:
txtField.rightView = dropdownButton
txtField.translatesAutoresizingMaskIntoConstraints = false
txtField.rightView?.widthAnchor.constraint(equalToConstant: 50).isActive = true
I got it to work using Bruno's method.
1) Create a container view and set its width and height using auto layout. Width and height should include the subview's size + required spacing.
2) Create the subview you want to show in the text field. Set the width, height, and layout using auto layout. Add it to the container view.
3) Add the container view to the text field
You can see my container matches the height of the text field. The width is the width of the button (44), plus the spacing I want (16). When I add my subview I'll align it to the left of the container. That will give me 16px spacing between the right of the button and the edge of the text field.
let forgotButtonContainer = UIView()
forgotButtonContainer.translatesAutoresizingMaskIntoConstraints = false
forgotButtonContainer.widthAnchor.constraint(equalToConstant: 44.0 + 16.0).isActive = true
forgotButtonContainer.heightAnchor.constraint(equalToConstant: 48.0).isActive = true
forgotButton = PFSecondaryButton(link: "Forgot?")
forgotButton.translatesAutoresizingMaskIntoConstraints = false
forgotButtonContainer.addSubview(forgotButton)
forgotButton.topAnchor.constraint(equalTo: forgotButtonContainer.topAnchor).isActive = true
forgotButton.leftAnchor.constraint(equalTo: forgotButtonContainer.leftAnchor).isActive = true
forgotButton.bottomAnchor.constraint(equalTo: forgotButtonContainer.bottomAnchor).isActive = true
passwordField.rightView = forgotButtonContainer
I resove the same problem by set this:
override textField rightViewRectForBounds: method,
- (CGRect)rightViewRectForBounds:(CGRect)bounds {
if (self.rightView) {
CGRect rightFrame = self.rightView.frame;
return CGRectMake(bounds.size.width - rightFrame.size.width, 0, rightFrame.size.width, bounds.size.height);
}
return CGRectZero;
}
Another solution I came across for this is to set translatesAutoresizingMaskIntoConstraints to true for the view you are assigning to leftView for iOS 13+ and false otherwise.
you can change right padding and left padding from the extension which I made
...........
you can use IBDesignable or not as you see
import UIKit
#IBDesignable
extension UITextField {
#IBInspectable var paddingLeftCustom: CGFloat {
get {
return leftView!.frame.size.width
}
set {
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: newValue, height: frame.size.height))
if leftView == nil {
leftView = paddingView
leftViewMode = .always
}
}
}
#IBInspectable var paddingRightCustom: CGFloat {
get {
return rightView!.frame.size.width
}
set {
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: newValue, height: frame.size.height))
if rightView == nil {
rightView = paddingView
rightViewMode = .always
}
}
}
}

How to make round circle UIButton with different height and width in iOS

Is it possible to set corner radius to make UIButton circle using different height and width of UIButton in Swift.
let button = UIButton(frame: CGRectMake(0, 0, 60, 40))
button.layer.cornerRadius = button.layer.bounds.size.width / 2
button.layer.masksToBounds = true
It won't work because button with different height and width. Please tell me if there is any other approach because button height and width going to be update. I need to make the button always be a circle.
TRY THIS:
let myFirstButton = UIButton()
myFirstButton.setTitle("SUBMIT", forState: .Normal)
myFirstButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
myFirstButton.frame = CGRectMake(15, 50, 100, 100)
myFirstButton.layer.cornerRadius = yourButton.bounds.size.width / 2
self.view.addSubview(myFirstButton)
Make a file with name CircularButton
paste the code below class
implement this class on the button on storyboard
Example:
import UIKit
class CircularButton: UIButton {
override func awakeFromNib() {
self.layer.cornerRadius = self.frame.size.width/2
self.clipsToBounds = true
self.layer.borderColor = UIColor.lightGray.cgColor
self.layer.borderWidth = 0.5
}
}

How to center UILabel in Swift?

I'm trying to center some text but I it doesn't seem to be working.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let title = UILabel()
title.text = "Some Sentence"
title.numberOfLines = 0
title.frame = CGRectMake(self.view.bounds.size.width/2,50,self.view.bounds.size.width, self.view.bounds.size.height) // x , y, width , height
title.textAlignment = .Center
title.sizeToFit()
title.backgroundColor = UIColor.redColor()
self.view.addSubview(title)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
That is the code that I'm using but this is what I get:
It's not center to the screen. Can someone tell me what am I doing wrong?
To center a UILabel just add this row
x and y:
title.center = self.view.center
x:
title.center.x = self.view.center.x
y:
title.center.y = self.view.center.y
Auto Layout
To make your app future proof rather use auto layout anchors instead of setting the frame.
1. Disable translatesAutoresizing
titleLabel.translatesAutoresizingMaskIntoConstraints = false
2. Add CenterX & CenterY constraints
titleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
titleLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
3. Set UILabel's text alignment to center
titleLabel.textAlignment = .center
SWIFT 4
This worked for me and seems more future proof. This also works for a multi-line label.
override func loadView() {
self.view = UIView()
let message = UILabel()
message.text = "This is a test message that should be centered."
message.translatesAutoresizingMaskIntoConstraints = false
message.lineBreakMode = .byWordWrapping
message.numberOfLines = 0
message.textAlignment = .center
self.view.addSubview(message)
message.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
message.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
message.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
Actually what you are doing is centering the text inside the UILabel. What you want to do is to center the label. To do it you can do:
title.frame.origin = CGPoint(x: x, y: y)
If you want to center the horizontal you can do:
title.frame.origin = CGPoint(x: self.view.frame.width / 2, y: yValue)
Also if you want to center the x and y value of your label you can do:
title.frame.origin = CGPoint(x: self.view.frame.width / 2, y: self.view.frame.height / 2)
Code for swift 3/4/5
Paste below code after super.viewDidLoad()
var noDataLbl : UILabel?
noDataLbl = UILabel(frame: CGRect(x: 0, y: self.view.center.y, width: 290, height: 70))
noDataLbl?.textAlignment = .center
noDataLbl?.font = UIFont(name: "Halvetica", size: 18.0)
noDataLbl?.numberOfLines = 0
noDataLbl?.text = "Replace this with your text."
noDataLbl?.lineBreakMode = .byTruncatingTail
noDataLbl?.center = self.view.center
view.addSubview(noDataLbl!)

Resources