Making translatesAutoResizingMaskIntoConstraints false causes ambiguous layout issue on UITextFields rightview - swift

I don't know whether making every objects translatesAutoresizingMaskIntoConstraints flag false is bad habit but here is my question related with this;
I have UITextField object and I am setting its rightView as UIImageView but when UIImageView's translatesAutoresizingMaskIntoConstraints property sets to false in debug view hierarchy ambiguous layout warning appears,
apple's documentation says under translatesAutoresizingMaskIntoConstraints topic:
If you want to use Auto Layout to dynamically calculate the size and >position of your view, you must set this property to false, and then >provide a non ambiguous, nonconflicting set of constraints for the view.
but there is method of UITextField
open func rightViewRect(forBounds bounds: CGRect) -> CGRect
that
Returns the drawing location of the receiver’s right overlay view.
Additionally when I am trying to give constraints between rightView and UITextField, it says they are not under same hierarchy.
So why making that flag as false causes ambiguous layout?
let imageView = UIImageView(image: UIImage(named: "infoIcon"))
imageView.contentMode = .scaleAspectFit
imageView.translatesAutoresizingMaskIntoConstraints = false //this line causes ambiguous layout
imageView.widthAnchor.constraint(equalToConstant: 15).isActive = true
textField.rightViewMode = .unlessEditing
textField.rightView = imageView

I was having a similar issue but in my case I forgot the add translatesAutoresizingMaskIntoConstraints as false.
The things your wrote about translatesAutoresizingMaskIntoConstraints is correct. And usually it is not great couple when you used with set a frame or bounds.
Anyway I tried your code and I have some idea what might be effect;
First check your UITextField constraints. If your constraint is not fit well then it could be break after adding rightView.
Secondly init your imageView with frame like below;
let imageView = UIImageView(frame: .zero)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.image = UIImage(named: "logo") imageView.contentMode = .scaleAspectFit
imageView.widthAnchor.constraint(equalToConstant: 15).isActive = true
textField.rightViewMode = .always
textField.rightView = imageView
And finally I think it would be better if you do not use imageView.widthAnchor.constraint(equalToConstant: 15).isActive = true
Use an image with best optimal resolution for your textfield and rest would be fine.

Related

UIStackView and a placeholder view in another UIStackView problem

There is a problem if you have a UIStackView(testStack) and a placeholder UIView(testView) inside another UIStackView(mainStack). It is meant that if there is no content in the testStack it will collapse, and the testView will take all the space. There is even a content hugging priority set to maximum for the testStack so it should collapse its height to 0 when there are no subviews. But it does not. How to make it collapse when there is no content?
PS If there are items in the testStack, everything works as expected: testView takes all available space, testStack takes only the space to fit its subviews.
class AView: UIView {
lazy var mainStack: UIStackView = {
let stack = UIStackView()
stack.axis = .vertical
stack.backgroundColor = .gray
stack.addArrangedSubview(self.testStack)
stack.addArrangedSubview(self.testView)
return stack
}()
let testStack: UIStackView = {
let stack = UIStackView()
stack.backgroundColor = .blue
stack.setContentHuggingPriority(.init(1000), for: .vertical)
return stack
}()
let testView: UIView = {
let view = UIView()
view.backgroundColor = .red
return view
}()
init() {
super.init(frame: .zero)
backgroundColor = .yellow
addSubview(mainStack)
mainStack.translatesAutoresizingMaskIntoConstraints = false
mainStack.topAnchor.constraint(equalTo: topAnchor).isActive = true
mainStack.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
mainStack.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
mainStack.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
When auto-layout arranges subviews in a UIStackView, it looks at:
the stack view's .distribution property
the subviews' height constraints (if given)
the subviews' Intrinsic Content Size
Since you have not specified a .distribution property, mainStack is using the default of .fill.
A UIStackView has NO Intrinsic Content Size, so auto-layout says "testStack has a height of Zero"
A UIView has NO Intrinsic Content Size, so auto-layout says "testView has a height of Zero"
Since the distribution is fill, auto-layout effectively says: "the heights of the arranged subviews are ambiguous, so let's give the last subview a height of Zero, and fill mainStack with the first subview.
Setting .setContentHuggingPriority will have no effect, because there is no intrinsic height to "hug."
If you set mainStack's .distribution = .fillEqually, you will get (as expected) testStack filling the top half, and testView filling the bottom half.
If you set mainStack's .distribution = .fillProportionally, you will get the same result... testStack filling the top half, and testView filling the bottom half, because .fillProportionally uses the arranged subviews' Intrinsic Content Sizes... in this case, they are both Zero, so "proportional" will be equal.
If you set mainStack's .distribution = .equalSpacing or .distribution = .equalCentering, you won't see either testStack or testView ... auto-layout will give each of them a height of Zero, and fill the rest of mainStack with (empty) "spacing."
If your goal is to have testStack "disappear" if it is empty, you can either:
set it hidden, or
subclass it and give it an intrinsic height

UIStackView, Autolayout, Table Cells

I'm creating a UIStackView with an image and a label in a custom UIControl that will be in a custom UITableViewCell, except that my UIStackView has a height & width of 0 and XCode is complaining about breaking constraints. Only if I explicitly set a height and width does it show properly, which I don't want because the label text varies from cell to cell. (This is all happening programmatically.)
The Setup
In my UITableViewCell, I've got the following:
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(control)
NSLayoutConstraint.activate([
control.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
control.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
control.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
control.topAnchor.constraint(equalTo: contentView.topAnchor),
])
}
// empty coder init as well
private let control: MyControl = {
let control: MyControl = MyControl()
control.translatesAutoresizingMaskIntoConstraints = false
return control
}()
In MyControl, I just have the UIStackView, a UIImageView, and a UILabel. To not bore you with code...only the UIStackView (horizontal axis) is using constraints, pinning it to the four sides. The UIImageView (initiated with an image) is one arranged subview, the UILabel is the other (initiated with default text).
If you want to see the code:
class MyControl: UIControl {
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(stackView)
stackView.addArrangedSubview(icon)
stackView.addArrangedSubview(contentLabel)
NSLayoutConstraint.activate([
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
stackView.topAnchor.constraint(equalTo: topAnchor),
])
}
// empty coder init as well
private let contentLabel: UILabel = {
let label: UILabel = UILabel()
label.text = "Initial text"
return label
}()
private let icon: UIImageView = {
let iv: UIImageView = UIImageView(image: UIImage(named: "placeholder_image")!)
iv.contentMode = .scaleAspectFit
return iv
}()
private let stackView: UIStackView = {
let stackView: UIStackView = UIStackView()
stackView.axis = .horizontal
stackView.translatesAutoresizingMaskIntoConstraints = false
return control
}()
}
What I'm Expecting
I'm expecting the UIControl to be the height of the image (because it's taller than the label text), and the width of the image + the label text. (And then display full table cell width because of that constraint pinning). And because these are set on initialization of these components, I'd expect them to have an intrinsic height and width to pass to the UIStackView.
What I'm Getting
A height and width of zero, and XCode complaints of broken constraints. If I remove all constraints, I get no complaints but nothing appears (as if the height & width are zero but XCode doesn't care because I didn't set any constraints).
What I've Tried
Literally every combination of layout constraints, including none on everything and as many as I can on everything. What I'd like is for the image + label text to set the height and width of the UIStackView which would then set the height and width of the UIControl, which would then set the height in the UITableViewCell (I know I have width 100% - that will change later).
Other Considerations
There's nothing else special about my UITableViewCell that would cause any issue here except that in my actual code, I have a multi-line label above MyControl which should (and does) cause my UITableViewCell to expand in height.
The problem with your question is that so much of the code you've shown is bogus that it's hard to guess what you might actually be doing. You claim that in your table view cell (I presume it's a table view cell subclass) you are saying
override init(frame: CGRect) {
super.init(frame: frame)
But that would never compile, as init(frame:) is not the designated initializer for a UITableViewCell. You have code like this:
private let contentLabel: UILabel = {
let label: UILabel = UILabel()
label.text = "Initial text"
return label
}
But that would never compile, as a function is not a label.
If we make allowances for all of that and fix your code, it's difficult to see what you would be doing wrong. I corrected your code so that it would compile, and I got this in my table view:
That might not be exactly what you were after, but the image view is certainly sizing the cells to its own height.
What you are doing is not at all how one makes and configures a table view cell (you should be doing the work in cellForRowAt, not hard coding the cell's image view and label contents in the cell's initializer), but given what you've shown, the image view does size the stack view which does size the cell (contrary to my own initial expectations).
Another issue is that you can't put an image view and a label into an autolayout situation without resolving the ambiguity as to which should predominate. I added this line to do that:
iv.setContentHuggingPriority(.defaultLow+1, for: .horizontal)

Adding constraint to UILabel Programatically with NSLayout

I am trying to add a UILabel to my ViewController and use NSLayout to constrain it. However the UILabel doesn't even appear in my view when I try to use NSLayout. Anyone know a solution?
excersice1label = UILabel()
excersice1label.textAlignment = .center
excersice1label.text = "Excercise 1"
excersice1label.font = UIFont.boldSystemFont(ofSize: 20)
view.addSubview(excersice1label)
NSLayoutConstraint.activate([
excersice1label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
excersice1label.centerYAnchor.constraint(equalTo: view.centerYAnchor ,constant: -110),
excersice1label.widthAnchor.constraint(equalToConstant: view.frame.width - 64)
])
You need to add this line:
exercise1label.translatesAutoresizingMaskIntoConstraints = false
According to Apple documentation:
If this property’s value is true, the system creates a set of
constraints that duplicate the behavior specified by the view’s
autoresizing mask. If you want to use Auto Layout to dynamically
calculate the size and position of your view, you must set this
property to false, and then provide a non ambiguous, nonconflicting
set of constraints for the view.

How to display a UITextView programmatically?

I am trying to display a basic UITextView programmatically. The problem is, if I want to display a UILabel with the same constraints and settings, it's all fine, I can have it displayed. However, if I change it to a UITextView, it just disappears. I created an empty project, just to check it, but still, no avail, just does not display.
Here's my code that works fine for a UILabel:
view = UIView()
view.backgroundColor = .systemBackground
explanationView = UILabel()
explanationView.layer.zPosition = 1
explanationView.translatesAutoresizingMaskIntoConstraints = false
explanationView.numberOfLines = 10
view.addSubview(explanationView)
NSLayoutConstraint.activate([
explanationView.centerXAnchor.constraint(equalTo: view.layoutMarginsGuide.centerXAnchor),
explanationView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
explanationView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
explanationView.bottomAnchor.constraint(greaterThanOrEqualTo: view.layoutMarginsGuide.bottomAnchor, constant: -20)
])
I am using layer.zPosition to display it over a UIImageView. I also add .isScrollEnabled for the text view. I also tried creating the UITextView via a computed property, but did not have any luck either.
Any help is appreciated.
Edit: Below is the code that I use for the text view.
explanationView = UITextView()
explanationView.layer.zPosition = 1
explanationView.translatesAutoresizingMaskIntoConstraints = false
explanationView.isScrollEnabled = true
view.addSubview(explanationView)

Setting constraints on UITableView inside UIView

I have a UIViewController that has some base controls and in the center has a UIView (as container of swappable ui controls). I swap out the UI controls in this center UIView (container) depending on what the user is trying to do. All of the UI controls that go in to the UIView container are defined programmatically and I use programatic constraints to place them inside the UIView container.
This works fine for all of the sets of UI controls I have done so far. Now I am adding a set of controls to the UIView container that includes a UITableView
I cant figure out how to get the TableView to show up inside the UIView programatically. I can define say a button and label and run the app and see the container with the button and the label. If I add the UITableView as below then the container just does not show up at all.
// tableView
tableView.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: inputsContainerView.bottomAnchor).isActive = true
tableView.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor).isActive = true
tableView.heightAnchor.constraint(equalTo: inputsContainerView.heightAnchor, multiplier: 1/2).isActive = true
prior to the above code I have already added all of the needed controls to the container subview ...
// Add controls to the view
inputsContainerView.addSubview(listTextView)
inputsContainerView.addSubview(listImageButton)
inputsContainerView.addSubview(listImageClear)
inputsContainerView.addSubview(tableView)
If I leave off the tableview then the container shows up with the other three fields. If I add the tableview then the container and all the other three controls are gone.
How do I add the tableView to the UIView and have it show up?
Here is how I defined the UITable view
let tableView: UITableView = {
let tv = UITableView()
return tv
}()
as a compare, when I define others controls like this they show up fine after adding to the subview and setting the constraints programatically
e.g.
// DEFINE
let listTextView: UITextView = {
let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.text = ""
textView.textColor = defaultTextColor
textView.font = subtitleFont
textView.layer.borderColor = UIColor.black.cgColor
textView.layer.borderWidth = 1
textView.textAlignment = NSTextAlignment.left
return textView
}()
then later
// Place - with constraints
// listTextView
listTextView.leftAnchor.constraint(equalTo: inputsContainerView.leftAnchor, constant: padFromLeft).isActive = true
listTextView.topAnchor.constraint(equalTo: inputsContainerView.topAnchor, constant: 10).isActive = true
listTextView.widthAnchor.constraint(equalTo: inputsContainerView.widthAnchor, multiplier: 9/16).isActive = true
listTextView.heightAnchor.constraint(equalTo: inputsContainerView.widthAnchor, multiplier: 5/16).isActive = true
Just added my comment as detailed answer, so others can see the solution faster and benefit from it.
So taken from the apple documentation, to set your own constraints programmatically, you need to set translatesAutoresizingMaskIntoConstraints to false:
Note that the autoresizing mask constraints fully specify the view’s size and position; therefore, you cannot add additional constraints to modify this size or position without introducing conflicts. If you want to use Auto Layout to dynamically calculate the size and position of your view, you must set this property to false, and then provide a non ambiguous, nonconflicting set of constraints for the view.
So in your case you miss to set it for your table view, when you define it. Just add this line to it:
tv.translatesAutoresizingMaskIntoConstraints = false