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

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

Related

UIKit rotate view 90 degrees while keeping the bounds to edge

Update:
I have since tried setting my layer anchor paint to (0,0) and translate it back to frame (0,0) after rotation using this tutorial.
This, however, still doesn't address the early wrapping issue. See below. Setting the content inset on the right side (bottom side) does not work.
textView.frame = CGRect(x: 0, y: 0, width: view.bounds.height, height: view.bounds.width)
print(textView.frame)
textView.setAnchorPoint(CGPoint(x: 0, y: 0))
textView.transform = CGAffineTransform(rotationAngle: (CGFloat)(Double.pi/2));
print(textView.frame)
textView.transform = textView.transform.translatedBy(x: 0, y: -(view.bounds.width))
print(textView.frame)
textView.contentInset = UIEdgeInsets(top: 0, left: height, bottom: 0, right: 0)
Original question:
I want to rotate the only UIView in subview clockwise by 90 degrees while keeping its bounds to edges of the screen, that is, below the Navigation Bar and above the Tab Bar and in between two sides.
Normally there are two ways to do this, either set translatesAutoresizingMaskIntoConstraints to true and set subview.frame = view.bounds
or set translatesAutoresizingMaskIntoConstraints to false and add four anchors constraints (top, bottom,leading, trailing) to view's four anchor constrains.
This is what it usually will do.
However, if I were to rotate the view while keeping its bound like before, like below, how would I do that?
Here's my current code to rotate a UITextView 90 degrees clockwise. I don't have a tab bar in this case but it shouldn't matter. Since before the textview grows towards the bottom, after rotation the textview should grow towards the left side. The problem is, it's not bound to the edge I showed, it is behind the nav bar.
var textView = UITextView()
view.addSubview(textView)
textView.translatesAutoresizingMaskIntoConstraints = true
textView.transform = CGAffineTransform(rotationAngle: (CGFloat)(Double.pi/2));
textView.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height)
Here it completely disappears if I rotate it after setting the frames
I also tried adding arbitrary value to the textView's y frame like so
textView.frame = CGRect(x: 0, y: 100, width: view.bounds.width, height: view.bounds.height)
but the result is that words get wrapped before they reach the bottom edge.
I also tried adding constrains by anchor and setting translateautoresizingmaskintoconstraints to false
constraints.append(contentsOf: [
textView.topAnchor.constraint(equalTo: view.topAnchor),
textView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
textView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
textView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
but the result is still a white screen.
Besides what I showed, I experimented with a lot of things, adding bit of value here and there, but they all are kind of a hack and doesn't really scale. For example, if the device gets rotated to landscape mode, the entire view gets screwed up again.
So, my question is, what is the correct, scalable way to do this? I want to have this rotated textview that is able to grow(scroll) towards the left and correctly resized on any device height and any orientation.
I know this could be related to anchor point. But since I want my view to actually bound to edges and not just display its content like an usual rotated UIImage. What are the steps to achieve that?
Thank you.
We need to embed the UITextView in a UIView "container" ... constraining it to that container ... and then rotate the container view.
Because the textView will continue to have a "normal" rotation relative to its superview, it will behave as desired.
Quick example:
class ViewController: UIViewController {
let textView = UITextView()
let containerView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
textView.translatesAutoresizingMaskIntoConstraints = false
// we're going to explicitly set the container view's frame
containerView.translatesAutoresizingMaskIntoConstraints = true
containerView.addSubview(textView)
view.addSubview(containerView)
// we'll inset the textView by 8-points on all sides
// so we can see that it's inside the container view
// avoid auto-layout error/warning messages
let cTrailing = textView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -8.0)
cTrailing.priority = .required - 1
let cBottom = textView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -8.0)
cBottom.priority = .required - 1
NSLayoutConstraint.activate([
textView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 8.0),
textView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 8.0),
// activate trailing and bottom constraints
cTrailing, cBottom,
])
textView.font = .systemFont(ofSize: 32.0, weight: .regular)
textView.text = "The quick red fox jumps over the lazy brown dog, and then goes to the kitchen to get some dinner."
// so we can see the framing
textView.backgroundColor = .yellow
containerView.backgroundColor = .systemBlue
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// inset the container view frame by 40
// leaving some empty space around it
// so we can tap the view
containerView.frame = view.frame.insetBy(dx: 40.0, dy: 40.0)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// end editing if textView is being edited
view.endEditing(true)
// if container view is NOT rotated
if containerView.transform == .identity {
// rotate it
containerView.transform = CGAffineTransform(rotationAngle: (CGFloat)(Double.pi/2));
} else {
// set it back to NOT rotated
containerView.transform = .identity
}
}
}
Output:
tapping anywhere white (so, tapping the controller's view instead of the container or the textView itself) will toggle rotated/non-rotated:
Edit - responding to comment...
The reason we have to work with the frame is because of the way .transform works.
When we apply a .transform it changes the frame of the view, but not its bounds.
Take a look at this quick example:
class ExampleViewController: UIViewController {
let greenLabel = UILabel()
let yellowLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
greenLabel.translatesAutoresizingMaskIntoConstraints = false
yellowLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(greenLabel)
view.addSubview(yellowLabel)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
yellowLabel.widthAnchor.constraint(equalToConstant: 200.0),
yellowLabel.heightAnchor.constraint(equalToConstant: 80.0),
yellowLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
yellowLabel.centerYAnchor.constraint(equalTo: g.centerYAnchor),
greenLabel.topAnchor.constraint(equalTo: yellowLabel.topAnchor),
greenLabel.leadingAnchor.constraint(equalTo: yellowLabel.leadingAnchor),
greenLabel.trailingAnchor.constraint(equalTo: yellowLabel.trailingAnchor),
greenLabel.bottomAnchor.constraint(equalTo: yellowLabel.bottomAnchor),
])
yellowLabel.text = "Yellow"
greenLabel.text = "Green"
yellowLabel.textAlignment = .center
greenLabel.textAlignment = .center
yellowLabel.backgroundColor = .yellow.withAlphaComponent(0.80)
greenLabel.backgroundColor = .green
// we'll give the green label a larger, red font
greenLabel.font = .systemFont(ofSize: 48.0, weight: .bold)
greenLabel.textColor = .systemRed
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// if yellow label is NOT rotated
if yellowLabel.transform == .identity {
// rotate it
yellowLabel.transform = CGAffineTransform(rotationAngle: (CGFloat)(Double.pi/2));
} else {
// set it back to NOT rotated
yellowLabel.transform = .identity
}
print("Green - frame: \(greenLabel.frame) bounds: \(greenLabel.bounds)")
print("Yellow - frame: \(yellowLabel.frame) bounds: \(yellowLabel.bounds)")
}
}
We've created two labels, with the yellow label on top of the green label, and the green label constrained top/leading/trailing/bottom to the yellow label.
Notice that when we apply a rotation transform to the yellow label, the green label does not change.
If you look at the debug console output, you'll see that the yellow label's frame changes, but its bounds stays the same:
// not rotated
Green - frame: (87.5, 303.5, 200.0, 80.0) bounds: (0.0, 0.0, 200.0, 80.0)
Yellow - frame: (87.5, 303.5, 200.0, 80.0) bounds: (0.0, 0.0, 200.0, 80.0)
// rotated
Green - frame: (87.5, 303.5, 200.0, 80.0) bounds: (0.0, 0.0, 200.0, 80.0)
Yellow - frame: (147.5, 243.5, 80.0, 200.0) bounds: (0.0, 0.0, 200.0, 80.0)
So... to make use of auto-layout / constraints, we want to create a custom UIView subclass, which has the "container" view and the text view. Something like this:
class RotatableTextView: UIView {
public var containerInset: CGFloat = 0.0 { didSet { setNeedsLayout() } }
public var textViewInset: CGFloat = 0.0 {
didSet {
tvConstraints[0].constant = textViewInset
tvConstraints[1].constant = textViewInset
tvConstraints[2].constant = -textViewInset
tvConstraints[3].constant = -textViewInset
}
}
public let textView = UITextView()
public let containerView = UIView()
private var tvConstraints: [NSLayoutConstraint] = []
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
textView.translatesAutoresizingMaskIntoConstraints = false
// we're going to explicitly set the container view's frame
containerView.translatesAutoresizingMaskIntoConstraints = true
containerView.addSubview(textView)
addSubview(containerView)
// avoid auto-layout error/warning messages
var c: NSLayoutConstraint!
c = textView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: textViewInset)
tvConstraints.append(c)
c = textView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: textViewInset)
tvConstraints.append(c)
c = textView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -textViewInset)
c.priority = .required - 1
tvConstraints.append(c)
c = textView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -textViewInset)
c.priority = .required - 1
tvConstraints.append(c)
NSLayoutConstraint.activate(tvConstraints)
}
func toggleRotation() {
// if container view is NOT rotated
if containerView.transform == .identity {
// rotate it
containerView.transform = CGAffineTransform(rotationAngle: (CGFloat)(Double.pi/2));
} else {
// set it back to NOT rotated
containerView.transform = .identity
}
}
override func layoutSubviews() {
super.layoutSubviews()
var r = CGRect(origin: .zero, size: frame.size)
containerView.frame = r.insetBy(dx: containerInset, dy: containerInset)
}
}
Now, in our controller, we can use constraints on our custom view like we always do:
class ViewController: UIViewController {
let rotTextView = RotatableTextView()
override func viewDidLoad() {
super.viewDidLoad()
rotTextView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(rotTextView)
// we'll inset the custom view on all sides
// so we can tap on the "root" view to toggle the rotation
let inset: CGFloat = 20.0
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
rotTextView.topAnchor.constraint(equalTo: g.topAnchor, constant: inset),
rotTextView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: inset),
rotTextView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -inset),
rotTextView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -inset),
])
// let's use a 32-point font
rotTextView.textView.font = .systemFont(ofSize: 32.0, weight: .regular)
// give it some initial text
rotTextView.textView.text = "The quick red fox jumps over the lazy brown dog, and then goes to the kitchen to get some dinner."
// if we want to inset either the container or the text view
//rotTextView.containerInset = 8.0
//rotTextView.textViewInset = 4.0
// so we can see the framing if insets are > 0
// if both insets are 0, we won't see these, so they don't need to be set
//rotTextView.backgroundColor = .systemBlue
//rotTextView.containerView.backgroundColor = .systemYellow
// let's set the text view background color to light-cyan
// so we can see its frame
rotTextView.textView.backgroundColor = UIColor(red: 0.75, green: 1.0, blue: 1.0, alpha: 1.0)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// end editing if textView is being edited
view.endEditing(true)
rotTextView.toggleRotation()
}
}
Note that this is just Example Code, but it should get you on your way.

inputAccessoryView not respecting safeAreaLayoutGuide when keyboard is collapsed

I am trying to get an inputAccessoryView working correctly. Namely, I want to be able to display, in this case, a UIToolbar in two possible states:
Above the keyboard - standard and expected behavior
At the bottom of the screen when the keyboard is dismissed (e.g. command + K in the simulator) - and in such instances, have the bottomAnchor respect the bottom safeAreaLayoutGuide.
I've researched this topic extensively but every suggestion I can find has a bunch of workarounds that don't seem to align with Apple engineering's suggested solution. Based on an openradar ticket, Apple engineering proposed this solution be approached as follows:
It’s your responsibility to respect the input accessory view’s
safeAreaInsets. We designed it this way so developers could provide a
background view (i.e., see Safari’s Find on Page input accessory view)
and lay out the content view with respect to safeAreaInsets. This is
fairly straightforward to accomplish. Have a view hierarchy where you
have a container view and a content view. The container view can have
a background color or a background view that encompasses its entire
bounds, and it lays out it’s content view based on safeAreaInsets. If
you’re using autolayout, this is as simple as setting the content
view’s bottomAnchor to be equal to it’s superview’s
safeAreaLayoutGuide.
The link for the above is: http://www.openradar.me/34411433
I have therefore constructed a simple xCode project (iOS App template) that has the following code:
class ViewController: UIViewController {
var field = UITextField()
var containerView = UIView()
var contentView = UIView()
var toolbar = UIToolbar()
override func viewDidLoad() {
super.viewDidLoad()
// TEXTFIELD
field = UITextField(frame: CGRect(x: 20, y: 100, width: view.frame.size.width, height: 50))
field.placeholder = "Enter name..."
field.backgroundColor = .secondarySystemBackground
field.inputAccessoryView = containerView
view.addSubview(field)
// CONTAINER VIEW
containerView.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: 50)
containerView.backgroundColor = .systemYellow
containerView.translatesAutoresizingMaskIntoConstraints = false
// CONTENT VIEW
contentView.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: 50)
contentView.backgroundColor = .systemPink
contentView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(contentView)
// TOOLBAR
toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 50))
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
let doneButton = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(didTapDone))
toolbar.setItems([flexibleSpace, doneButton], animated: true)
toolbar.backgroundColor = .systemGreen
toolbar.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(toolbar)
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: containerView.topAnchor),
contentView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: contentView.superview!.safeAreaLayoutGuide.bottomAnchor),
toolbar.topAnchor.constraint(equalTo: contentView.topAnchor),
toolbar.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
toolbar.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
toolbar.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
])
}
#objc private func didTapDone() {
print("done tapped")
}
}
The result works whilst the keyboard is visible but doesn't once the keyboard is dimissed:
I've played around with the heights of the various views with mixed results and making the container view frame height larger (e.g. 100), does show the toolbar when the keyboard is collapsed, it also makes the toolbar too tall for when the keyboard is visible.
Clearly I'm making some auto layout constraint issues but I can't work out and would appreciate any feedback that provides a working solution aligned with Apple's recommendation.
Thanks in advance.
In my case I use the following approach:
import UIKit
extension UIView {
func setDimensions(height: CGFloat, width: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
widthAnchor.constraint(equalToConstant: width).isActive = true
}
func setHeight(_ height: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
class CustomTextField: UITextField {
override init(frame: CGRect) {
super.init(frame: frame)
}
convenience init(placeholder: String) {
self.init(frame: .zero)
configureUI(placeholder: placeholder)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureUI(placeholder: String) {
let spacer = UIView()
spacer.setDimensions(height: 50, width: 12)
leftView = spacer
leftViewMode = .always
borderStyle = .none
textColor = .white
keyboardAppearance = .dark
backgroundColor = UIColor(white: 1, alpha: 0.1)
setHeight(50)
attributedPlaceholder = NSAttributedString(string: placeholder, attributes: [.foregroundColor: UIColor(white: 1, alpha: 0.75)])
}
}
I was able to achieve the effect by wrapping the toolbar (chat input bar in my case) and constraining it top/right/left + bottom to safe area of the wrapper.
I'll leave an approximate recipe below.
In your view controller:
override var inputAccessoryView: UIView? {
keyboardHelper
}
override var canBecomeFirstResponder: Bool {
true
}
lazy var keyboardHelper: InputBarWrapper = {
let wrapper = InputBarWrapper()
let inputBar = InputBar()
helper.addSubview(inputBar)
inputBar.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
inputBar.topAnchor.constraint(equalTo: helper.topAnchor),
inputBar.leftAnchor.constraint(equalTo: helper.leftAnchor),
inputBar.bottomAnchor.constraint(equalTo:
helper.safeAreaLayoutGuide.bottomAnchor),
inputBar.rightAnchor.constraint(equalTo: helper.rightAnchor),
])
return wrapper
}()
Toolbar wrapper subclass:
class InputBarWrapper: UIView {
var desiredHeight: CGFloat = 0 {
didSet { invalidateIntrinsicContentSize() }
}
override var intrinsicContentSize: CGSize {
CGSize(width: 0, height: desiredHeight)
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
override init(frame: CGRect) {
super.init(frame: frame);
autoresizingMask = .flexibleHeight
backgroundColor = UIColor.systemGreen.withAlphaComponent(0.2)
}
}

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

Align UIImage vertically center

I am trying to align an image vertically central using Swift. I understand you do this by using constraints, however I've been unable to get this to work.
func getLogo() {
let logo = UIImage(named: "LogoWhite")
let logoView = UIImageView(image: logo)
logoView.contentMode = .scaleAspectFit
self.addSubview(logoView)
}
If you don't want to use constraints (I personally do not like them) you can check for container center and put your UIImageView there.
Example:
containerView -> the view that contains your logo
logo -> the view you want vertically centered
logo.center.y = containerView.center.y
If the containerView is the screen, then
let screen = UIScreen.main.bounds
let height = screen.height
logo.center.y = screen.height / 2
I would suggest using extensions on UIView to make auto layout much easier. This seems like a lot of code to begin with for such a simple task but these convenience functions will make things a lot quicker in the long run for example:
public func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding: UIEdgeInsets = .zero, size: CGSize = .zero){
translatesAutoresizingMaskIntoConstraints = false
//Set top, left, bottom and right constraints
if let top = top {
topAnchor.constraint(equalTo: top, constant: padding.top).isActive = true
}
if let leading = leading {
leadingAnchor.constraint(equalTo: leading, constant: padding.left).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom).isActive = true
}
if let trailing = trailing {
trailingAnchor.constraint(equalTo: trailing, constant: -padding.right).isActive = true
}
//Set size contraints
if size.width != 0 {
widthAnchor.constraint(equalToConstant: size.width).isActive = true
}
if size.height != 0 {
heightAnchor.constraint(equalToConstant: size.height).isActive = true
}
}
Then you could simply call:
someUIView.anchor(anchor(top: topAnchor, leading: leadingAnchor, bottom: bottomAnchor, trailing: nil, padding: .init(top: 16, left: 16, bottom: 16, right: 16), size: .init(width: 80, height: 80))
Then to answer your question directly you could then add more extensions to do some stuff easily:
public func anchorCenterXToSuperview(constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
if let anchor = superview?.centerXAnchor {
centerXAnchor.constraint(equalTo: anchor, constant: constant).isActive = true
}
}
Finally you could then simply call anchorCenterXToSuperview() on any UIView to centre any object.
Don't forget to make sure you've added your view to a view hierarchy before attempting to layout your views otherwise you'll get errors.

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

Resources