IOS 13: spacing Issue with UITextField rightView - ios

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
}
}
}
}

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"
}

autoresizingMask not working as expected for UITableView.tableHeaderView's subview

I'm adding a header view to my UITableView and want to add a subview to it having some margins.
class ViewController: UIViewController {
private let tablewView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
tablewView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tablewView)
[
tablewView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
tablewView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
tablewView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
tablewView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
].forEach{ $0.isActive = true}
let headerView = UIView(frame: CGRect(x: 0, y: 120, width: 200, height: 100))
headerView.backgroundColor = .blue
let subView = UIView(frame: CGRect(x: 10, y: 10, width: 180, height: 80))
subView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
subView.backgroundColor = .yellow
headerView.addSubview(subView)
tablewView.tableHeaderView = headerView
}
}
The problem is that the right margin isn't preserved when the header is resized (when the table view is laid out). As you can see on the image, the right margin is missing:
If I'm using the same view without UITableView, then the margin is preserved as expected.
Is it a UIKit bug? Are there any workarounds?
I know that I can try AutoLayout solutions from here Is it possible to use AutoLayout with UITableView's tableHeaderView? but they're looking a bit hacky. autoresizingMask is supposed to work, after all.
In Cocoa programming as in comedy, timing is everything.
Add the subview in a one-time implementation of viewDidLayoutSubviews and all will be well. The subview will appear correctly, and will continue working if the table view is resized (e.g. due to rotation of the interface).
So, cut these four lines:
let subView = UIView(frame: CGRect(x: 10, y: 10, width: 180, height: 80))
subView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
subView.backgroundColor = .yellow
headerView.addSubview(subView)
And instead:
var didLayout = false
override func viewDidLayoutSubviews() {
guard !didLayout else { return }
didLayout.toggle()
if let h = tablewView.tableHeaderView {
let subView = UIView(frame: h.bounds.insetBy(dx: 10, dy: 10))
subView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
subView.backgroundColor = .yellow
h.addSubview(subView)
}
}

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)
}

Is it possible to use Auto Layout in a UITextField's leftView?

I want to customize a UITextField's leftView with a view that is automatically sized depending on its contents:
func set(leftImage image: UIImage) {
let imageView = UIImageView(image: image)
let paddingContainer = UIView()
// This is the crucial point:
paddingContainer.translatesAutoresizingMaskIntoConstraints = false
paddingContainer.addSubview(imageView)
imageView.pin(toMarginsOf: paddingContainer)
leftView = paddingContainer
leftViewMode = .always
}
where the pin method just pins the image view on all four sides to the margins of the paddingContainer:
func pin(toMarginsOf view: UIView) {
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor),
leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor)
])
}
On iOS 12, everything works as expected, but on iOS versions < 12, the image is completely misplaced. It's not even within the bounds of the text field but in the upper left corner of my view controller's view.
To me it seems like older versions of iOS don't support using Auto Layout inside the view that you set as a text field's leftView. The documentation states:
The left overlay view is placed in the rectangle returned by the leftViewRect(forBounds:) method of the receiver.
but it doesn't state how it's placed there: By using constraints or by setting the frame directly.
Are there any reliable sources or educated guesses if using Auto Layout is supported at all for the leftView?
extension UITextField{
func setLeft(image: UIImage, withPadding padding: CGFloat = 0) {
let wrapperView = UIView.init(
frame: CGRect.init(
x: 0,
y: 0,
width: bounds.height,
height: bounds.height
)
)
let imageView = UIImageView()
imageView.image = image
imageView.contentMode = .scaleAspectFit
wrapperView.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.leadingAnchor.constraint(
equalTo: wrapperView.leadingAnchor,
constant: padding
),
imageView.trailingAnchor.constraint(
equalTo: wrapperView.trailingAnchor,
constant: -padding
),
imageView.topAnchor.constraint(
equalTo: wrapperView.topAnchor,
constant: padding
),
imageView.bottomAnchor.constraint(
equalTo: wrapperView.bottomAnchor,
constant: -padding
)
])
leftView = wrapperView
leftViewMode = .always
}
}
hope this will help
You can set its frame on layout subviews function like this
override func layoutSubviews() {
super.layoutSubviews()
if let lv = self.leftView {
lv.frame = CGRect(x: 0, y: 0, width: self.bounds.height, height: self.bounds.height)
}
}

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

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:

Resources