satisfying two auto layout constraints programmatically - Swift - swift

I want to set a UIButton with autoLayout constraints:
Basically I want the height to be the multiplier of the container view height:
button.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.43).isActive = true
and if the height doesn't reach the constant of 44 I want to set it to 44, I added this:
button.heightAnchor(greaterThanOrEqualToConstant: 44).isActive = true
Obviously programmatically setting 2 constraints like this causes a conflict, is there a way for programmatically accomplishing these 2 prerequisites without causing a warning?

You will need to set lower priority for the "ratio" constraint
let constraint = button.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.43)
constraint.priority = .defaultHigh
constraint.isActive = true
if the engine still have troubles with figuring out the layout, you can try different priority value.
UILayoutPriority documentation

You either set a low priority of
let con = button.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.43)
con.layoutPriority //////////////
con.isActive = true
or calculate it yourself mathematically and decide with if statement

Related

How can I set priority on constraints in Swift?

I've been struggling to make priority on constraints work programmatically in Swift.
My goal is to have the meetingFormView no more than 300 wide. Using IB I would give the width constraint a lower priority and give a higher priority to the "lessThanOrEqualToConstant". But I can't get it to work.
I've tried this:
meetingFormView.translatesAutoresizingMaskIntoConstraints = false
let constraintWidth = NSLayoutConstraint(
item: meetingFormView,
attribute: NSLayoutAttribute.width,
relatedBy: NSLayoutRelation.equal,
toItem: startView,
attribute: NSLayoutAttribute.width,
multiplier: 1,
constant: 0)
constraintWidth.priority = .defaultHigh
NSLayoutConstraint.activate([
meetingFormView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
meetingFormView.heightAnchor.constraint(equalToConstant: 170),
meetingFormView.widthAnchor.constraint(lessThanOrEqualToConstant: 300),
meetingFormView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
constraintWidth
])
It actually seems to take three lines of code to set up a prioritized "anchor-based" constraint in code:
let widthConstraint = meetingFormView.widthAnchor.constraint(equalToConstant: 170)
widthConstraint.priority = UILayoutPriority(rawValue: 500)
widthConstraint.isActive = true
It seems like if I try to set isActive with the let declare Xcode (maybe Swift?) doesn't recognize that the type is NSLayoutConstraint. And using UILayoutPriority(rawValue:) seems to be the best (only?) way to set priorities.
While this answer doesn't conform exactly with what you are doing, I believe it will work with IB. Just replace the let with creating an IBOutlet and there should be no need for the isActive line.
Obviously, to change the priority later in code you just need:
widthConstraint.priority = UILayoutPriority(rawValue: 750)
You can write a small extension like this:
extension NSLayoutConstraint
{
func withPriority(_ priority: Float) -> NSLayoutConstraint
{
self.priority = UILayoutPriority(priority)
return self
}
}
then use it like this:
NSLayoutConstraint.activate([
...
meetingFormView.widthAnchor.constraint(lessThanOrEqualToConstant: 300).withPriority(500),
...
])

Swift 4 - Getting half the width of a button

I'm trying to make a fully circular button. I have created it in code and setup the constraints like this:
addButton.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.2).isActive = true
addButton.heightAnchor.constraint(equalTo: addButton.widthAnchor).isActive = true
addButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -30).isActive = true
addButton.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: -30).isActive = true
I then try and set the corner radius of the button by using this:
addButton.layer.cornerRadius =
addButton.layer.masksToBounds = true
But I don't know what to put in the corner radius. I have tried frame.width / 2 but the button remains square. I need to get the value of the width constraint but I can't convert it into a CGFloat.
Any suggestions on how to do this?
Thanks
Inside viewDidLayoutSubviews put
addButton.layer.cornerRadius = self.view.frame.width * 0.1
You can either use the above answer posted by #Sh_Khan (url: https://stackoverflow.com/a/51808795/7425588) or go with the following:
addButton.layer.cornerRadius = addButton.widthAnchor.constant / 2.0
addButton.clipToBounds = true
Remember to make an UIObject complete circular, make it's height and width equal or aspect ratio to 1:1. Set clip to bounds to true and give corner radius as half of width or height.

Can't add constraints to video programmatically

I am trying to programmatically constraint a video into the center of the page. My AV controller is called avPlayerController .
I have already given its x and y values along with the width and height:
avPlayerController.view.frame = CGRect(x: 36 , y: 20, width: 343, height: 264)
So how do i center it?
I HAVE TRIED: Programmatically Add CenterX/CenterY Constraints
But, as you can guess it did not work :(
Here is my code:
super.viewDidLoad()
let filepath: String? = Bundle.main.path(forResource: "rockline", ofType: "mp4")
let fileURL = URL.init(fileURLWithPath: filepath!)
avPlayer = AVPlayer(url: fileURL)
let avPlayerController = AVPlayerViewController()
avPlayerController.player = avPlayer
avPlayerController.view.frame = CGRect(x: 36 , y: 20, width: 343, height: 264)
// hide/show control
avPlayerController.showsPlaybackControls = false
// play video
avPlayerController.player?.play()
self.view.addSubview(avPlayerController.view)
avPlayerController.view.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
Here's the code, with explanation.
Always remember that if you are using auto layout constraints, do not set frames. The layout engine will walk all over them. If you are instantiating your view in code, don't set a frame, or if necessary, it communicates things best if you set the frame to CGRect.zero.
Understand the view life cycle. Specifically, you can set your constraints in viewDidLoad, where they should be created only once.
Remember to set the auto resizing mask to false. This is the most common error when you learning auto layout in code.
There are actually three ways to create constraints, and a few ways to activate them. In your question, I think the easiest way is to use anchors.
Here's an example of centering a view (any view) with a width of 343 and a height of 264:
let myView = UIView() // note, I'm not setting any frame
override func viewDidLoad() {
super.viewDidLoad()
view.translatesAutoresizingMaskIntoConstraints = false
myView.translatesAutoresizingMaskIntoConstraints = false
myView.widthAnchor.constraint(equalToConstant: 343.0).isActive = true
myView.heightAnchor.constraint(equalToConstant: 264.0).isActive = true
myView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
myView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
That's all there is to it! BUT....
I'd suggest one more thing. Don't use constants in setting the height and width. That's not being "adaptive". Your 4 inch iPhone SE has a screen size of 568x320, where this may look centered and large enough. But on an iPhone Plus with a screen size of 736x414 it may be pretty small. (To say nothing of a 12.9 inch iPad Pro!)
Notice how my code uses the superview for the centerX/centerY anchors. (And instead of equalToConstant it's equalTo.) Do the same with the width and height. Through the use of multiplier and constant, along with UILayoutGuides, you can make your layouts adapt to whatever screen size Apple throws at you.
You can try this.
avPlayerController.view.enableAutoLayoutConstraint()
avPlayerController.view.setCenterXConstraint(.equal, constantValue: 0)
avPlayerController.view.setCenterYConstraint(.equal, constantValue: 0)
extension UIView
{
//Method to making translatesAutoresizingMaskIntoConstraints false.
func enableAutoLayoutConstraint()
{
self.translatesAutoresizingMaskIntoConstraints = false
}
//Method to set Center-X Consttraint
func setCenterXConstraint(_ relationType:NSLayoutRelation , constantValue:CGFloat)->NSLayoutConstraint
{
let constraint = NSLayoutConstraint(item: self, attribute:.centerX, relatedBy: relationType, toItem: self.superview, attribute: .centerX, multiplier: 1, constant: constantValue)
self.superview?.addConstraint(constraint)
return constraint
}
//Method to set Center-Y Consttraint
func setCenterYConstraint(_ relationType:NSLayoutRelation , constantValue:CGFloat)->NSLayoutConstraint
{
let constraint = NSLayoutConstraint(item: self, attribute:.centerY, relatedBy: relationType, toItem: self.superview, attribute: .centerY, multiplier: 1, constant: constantValue)
self.superview?.addConstraint(constraint)
return constraint
}
}

Does it matter if height constraint is not set?

I'm setting up constraints programmatically in Swift 3 and have a question about constraints. If I don't set a height constraint but set a topAnchor and bottomAnchor, does that do the same thing?
self.squadTableView.translatesAutoresizingMaskIntoConstraints = false
self.squadTableView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
self.squadTableView.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true
self.squadTableView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0).isActive = true
self.squadTableView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
Compared to taking out the bottomAnchor constraint and then doing
self.squadTableView.heightAnchor.constraint(equalTo: self.view.heightAnchor).isActive = true
Having a top and bottom is sufficient if they are anchored to things that have sufficient constraints. You just need constraints that determine position and size, but it can be any combination that does that.

How to add constraints to a dynamically created UITextField?

I have created UITextFields dynamically. Now i want to refer to the TextFields to check for some constraints. How do i do so?
func displayTextBox1(height: Int, placeHolder: String, xtb: Int, ytb: Int, lableName: String, xl: Int, yl: Int) {
DispatchQueue.main.async{
self.textField = UITextField(frame: CGRect(x: xtb, y: ytb, width: 343, height: height))
self.textField.textAlignment = NSTextAlignment.left
self.textField.textColor = UIColor.black
self.textField.borderStyle = UITextBorderStyle.line
self.textField.autocapitalizationType = UITextAutocapitalizationType.words // If you need any capitalization
self.textField.placeholder = placeHolder
print("hi")
self.view.addSubview(self.textField)
self.displayLabel(labelName: lableName, x: xl, y: yl)
}
}
You can set constraints programmaticaly using the sample explained code below:
let constraint = NSLayoutConstraint(item: self.textField, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.width, multiplier: 1, constant: -50)
NSLayoutConstraint.activate([constraint])
As you can see, I am creating a constraint for item textField which width should be equal to width of view multiplied by 1 minus 50. That means the width of your textField will be 50 pixels less than the width of the view. The last line of code activates given set of created constraints.
Welcome to Stackoverflow, and hope you're enjoying learning Swift!
I'm going to make some assumptions based on your code snippet:
the details you need to create the textfield and label (position, placeholder text etc) are coming from some service that operates on a background thread (perhaps a HTTP request?) which is why you're using DispatchQueue.main.async to perform UI events back on the main thread.
there will be multiple textfield/label pairs that you're going to configure and add to the interface (not just a single pair)... perhaps a 'todo' list sort of app where a label and textfield let people enter a note (more on this in part 2 of answer) which is why these views (and constraints) are being created in code rather than in a storyboard.
you want to invest in a constraint-based layout rather than frame-based positioning.
Answer part 1
If any of those assumptions are incorrect, then parts of this answer probably won't be relevant.
But assuming the assumptions are correct I suggest a couple things:
Use a separate helper methods to create a textfield/view and return the result (rather than doing everything in a single method) -- methods that have a single purpose will make more sense and be easier to follow.
Don't use a mixture of setting view position/size with frame and constraints - use one approach or the other (since you're new it will be easier to keep a single mental model of how things are working rather than mixing).
Here's a snippet of what a view controller class might start to look like:
import UIKit
class ViewController: UIViewController {
func triggeredBySomeEvent() {
// assuming that you have some equivilent to `YouBackgroundRequestManager.getTextFieldLabelDetails`
// that grabs the info you need to create the label and textfield on a background thread...
YouBackgroundRequestManager.getTextFieldLabelDetails { tfPlaceholder, tfHeight, tfX, tfY, labelName, labelX, labelY in
// perform UI work on the main thread
DispatchQueue.main.async{
// use our method to dynamically create a textfield
let newTextField: UITextField = self.createTextfield(with: tfPlaceholder, height: tfHeight)
// add textfield to a container view (in this case the view controller's view)
self.view.addSubview(newTextField)
// add constraints that pin the textfield's left and top anchor relative to the left and top anchor of the view
newTextField.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: tfX).isActive = true
newTextField.topAnchor.constraint(equalTo: self.view.topAnchor, constant: tfY).isActive = true
// repeat for label...
let newLabel: UILabel = self.createLabel(with: labelName)
self.view.addSubview(newLabel)
newLabel.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: labelX).isActive = true
newLabel.topAnchor.constraint(equalTo: self.view.topAnchor, constant: labelY).isActive = true
}
}
}
// create, configure, and return a new textfield
func createTextfield(with placeholder: String, height: CGFloat) -> UITextField {
let textField = UITextField(frame: .zero) // set the frame to zero because we're going to manage this with constraints
textField.placeholder = placeholder
textField.textAlignment = .left
textField.textColor = .black
textField.borderStyle = .line
textField.autocapitalizationType = .words
// translatesAutoresizingMaskIntoConstraints = false is important here, if you don't set this as false,
// UIKit will automatically create constraints based on the `frame` of the view.
textField.translatesAutoresizingMaskIntoConstraints = false
textField.heightAnchor.constraint(equalToConstant: height).isActive = true
textField.widthAnchor.constraint(equalToConstant: 343.0).isActive = true
return textField
}
// create, configure and return a new label
func createLabel(with labelName: String) -> UILabel {
let label = UILabel()
label.text = labelName
label.translatesAutoresizingMaskIntoConstraints = false
return label
}
}
Answer part 2
I'm struggling to imagine the situation where you actually want to do this. If you are making a UI where elements repeat themselves (like a todo list, or maybe a spreadsheet-type interface) then this approach is not the right way to do.
If you want to create a UI where elements repeat themselves as repeating elements in a single column you should investigate using a UITableViewController where you create a cell that represents a single element, and have a tableview manage that collection of cells.
If you want to create a UI where elements repeat themselves in any other way than a vertical list, then you investigate using UICollectionViewController which is a little more complex, but a lot more powerful.
Apologies if this answer goes off-topic, but hope that inspires some ideas that are useful for you.