Add uibutton with Auto Layout programmatically - swift

I want to add UIButton like this:
let switchTheme: UIButton = {
let button = UIButton.init()
button.backgroundColor = .red
button.setTitleColor(.blue, for: .normal)
button.setTitle(Settings.isLightTheme() ? Strings.Various.switchToDark.value : Strings.Various.switchToLight.value, for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
And then set constraints like:
switchTheme.bottomAnchor.constraint(equalTo: view.bottomAnchor)
switchTheme.leftAnchor.constraint(equalTo: view.leftAnchor)
switchTheme.rightAnchor.constraint(equalTo: view.rightAnchor)
switchTheme.heightAnchor.constraint(equalToConstant: 40.0)
But it shown not on bottom as it suppose to but on top and without constraints applied.

You will need to set constraints activate state = true. You can do it simply,
NSLayoutConstraint.activate([
//Move your existing code HERE with comma separated
])
In case of any problem, you can check this following function:
func setConstraints() {
NSLayoutConstraint.activate([
switchTheme.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor), // bottomAnchor to set bottom target.
switchTheme.leftAnchor.constraint(equalTo: self.view.leftAnchor), // leftAnchor to set X left
switchTheme.rightAnchor.constraint(equalTo: self.view.rightAnchor), // rightAnchor to set X right
switchTheme.heightAnchor.constraint(equalToConstant: 40.0) //heightAnchor to set appropriate height.
])
}

You need to active those constraints just simple like this :
switchTheme.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
switchTheme.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
switchTheme.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
switchTheme.heightAnchor.constraint(equalToConstant: 40.0).isActive = true

Your constrains must be activated :
switchTheme.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true

Related

Why is my UIView not displaying correctly when updating autolayout constraints?

I am trying to animate a simple autolayout constraint change, but when I call it the first time it animates but instead of just stretching and changing the height, it moves the whole view up, if I call it again it then fixes itself.
Here is how I set up the constraints:
hiddenView.addSubview(topView)
topView.translatesAutoresizingMaskIntoConstraints = false
topView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
topView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
topView.bottomAnchor.constraint(equalTo: hiddenView.bottomAnchor).isActive = true
topViewDefaultTopAnchorConstraints.append(topView.topAnchor.constraint(equalTo: hiddenView.centerYAnchor))
topViewSelectedTopAnchorConstraints.append(topView.topAnchor.constraint(equalTo: hiddenView.topAnchor))
NSLayoutConstraint.activate(topViewDefaultTopAnchorConstraints)
And here is how I am updating them:
func showTopView() {
NSLayoutConstraint.deactivate(topViewDefaultTopAnchorConstraints)
NSLayoutConstraint.activate(topViewSelectedTopAnchorConstraints)
UIView.animate(withDuration: 0.25) {
self.view.layoutIfNeeded()
}
}
Update:
Here is a gif of what happens when calling showTopView, calling it again fixes the bottom constraint:
It should just animate up like in the second image, not bringing the whole view up, as the bottomAnchor does not change, how can I fix this?
Update: I realised that I am rounding the corners of topView and bottomView, if I don't round the top corners then it works correctly, so it has something to do with this.
override func viewDidLayoutSubviews() {
topView.roundCorners(corners: [.topLeft, .topRight], radius: 100*ScreenDimensions.ASPECT_RATIO_RESPECT_OF_XMAX)
bottomView.roundCorners(corners: [.topLeft, .topRight], radius: 100*ScreenDimensions.ASPECT_RATIO_RESPECT_OF_XMAX)
topConstraint = topView.topAnchor.constraint(equalTo: hiddenView.topAnchor, constant: hiddenView.frame.height/2)
topConstraint.isActive = true
}
Seems the hiddenView's bottomAnchor is pulled up along with the topView's bottom here. Make sure the hiddenView's bottomAnchor is constrained properly. And better way to do this would be to provide a heightAnchor and increase the heightConstraint constant value to animate the view to increased height.
I think I know what your issue is. You might need to play with the constant rather than having two different constraint defined that you activate/deactivate.
First, define a constraint a the top of your ViewController like so:
var topConstraint: NSLayoutConstraint!
Then, initialize it as shown below:
hiddenView.addSubview(topView)
topView.translatesAutoresizingMaskIntoConstraints = false
topView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
topView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
topView.bottomAnchor.constraint(equalTo: hiddenView.bottomAnchor).isActive = true
topConstraint = topView.topAnchor.constraint(equalTo: hiddenView.topAnchor, constant: hiddenView.frame.height/2)
topConstraint.isActive = true
Then inside showTopView, you only need to update the constant and that should create the animation you are looking for:
func showTopView() {
topConstraint.isActive = false
topConstraint.constant = 0
topConstraint.isActive = true
UIView.animate(withDuration: 0.25) {
self.view.layoutIfNeeded()
}
}
Similarly if you want to put the view back to normal, you'd implement it as follows:
func hideTopView() {
topConstraint.isActive = false
topConstraint.constant = hiddenView.frame.height/2
topConstraint.isActive = true
UIView.animate(withDuration: 0.25) {
self.view.layoutIfNeeded()
}
}
You may have better luck with this approach:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let r = 100*ScreenDimensions.ASPECT_RATIO_RESPECT_OF_XMAX
topView.layer.cornerRadius = r
topView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
bottomView.layer.cornerRadius = r
bottomView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
}

change heightAnchor of higher size then current size is not working

I have one view whose height is 50
myView.heightAnchor.constraint(equalToConstant: 50).isActive = true
Now what I want to do is when I click button (buton1), I want to change its height to 20. So I make below and it's working fine.
myView.heightAnchor.constraint(equalToConstant: 20).isActive = true
Now in myView I have another button (button2), when I click on it, I want to go back to previous height of 50, but it's not working.
myView.heightAnchor.constraint(equalToConstant: 50).isActive = true
Problem is here. Now the view is not re-sizing like when click on button1.
Note :
When I click on button1 & use below code, re-size also not work.
myView.heightAnchor.constraint(equalToConstant: 100).isActive = true
What I found is if I try to re-size to higher size of current size, it don't work.
Any idea why view is not re-sizing?
Edit 1
Even I wrote below after updating height anchor but still not working.
myView.layoutIfNeeded()
self.view.layoutIfNeeded()
Edit 2
Working Option 1
When click on button1, I do below and works.
myView.heightAnchor.constraint(equalToConstant: 40).isActive = true
When click on button2, I do below and works.
myView.heightAnchor.constraint(equalToConstant: 30).isActive = true
Not Working Option 2
When click on button1, I do below and works.
myView.heightAnchor.constraint(equalToConstant: 40).isActive = true
When click on button2, I do below and NOT WORK.
myView.heightAnchor.constraint(equalToConstant: 50).isActive = true
So what I found is I change height higher then previous, it don't work
At the moment, you are creating a new rule about the view's height every time. iOS doesn't understand how to make a view both 10, 20 and 30 pixels high, so it will do the lowest one. To avoid creating conflicting heights, you need to have one height rule and change that one's constant when needed. Here's a somewhat contained example.
class CoolView: UIViewController {
lazy var myView = UIView()
lazy var myViewHeightAnchor = myView.heightAnchor.constraint(equalToConstant: 10)
override func viewDidLoad() {
super.viewDidLoad()
myView.backgroundColor = .black//livesMatters
myView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(myView)
NSLayoutConstraint.activate([
myView.topAnchor.constraint(equalTo: view.topAnchor),
myView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
myView.widthAnchor.constraint(equalToConstant: 10),
myViewHeightAnchor
])
}
#objc func someButtonTapped() {
myViewHeightAnchor.constant = 20
view.layoutIfNeeded()
}
#objc func anotherButtonTapped() {
myViewHeightAnchor.constant = 30
view.layoutIfNeeded()
}
}
You should keep the reference to the constraint and change the constant value and then call layoutIfNeeded method to achieve this. Here's how:
// First
let heightConstraint = myView.heightAnchor.constraint(equalToConstant: 50)
heightConstraint.isActive = true
// Then to change
heightConstraint.constant = 20
myView.layoutIfNeeded()
As requested I'm adding playground sample here (try this out on Xcode-playground):
import UIKit
import PlaygroundSupport
class ViewController: UIViewController {
var heightConstraint: NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
let sampleView = UIView()
sampleView.backgroundColor = .green
sampleView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(sampleView)
heightConstraint = sampleView.heightAnchor.constraint(equalToConstant: 50)
let button = UIButton(type: .contactAdd)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(handleAction), for: .touchUpInside)
view.addSubview(button)
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
sampleView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
sampleView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
heightConstraint!,
sampleView.widthAnchor.constraint(equalToConstant: 200)
])
}
#objc func handleAction() {
heightConstraint?.constant = heightConstraint?.constant == 50 ? 30 : 50
}
}
PlaygroundPage.current.setLiveView(ViewController())
Here you can see how updating the heightConstraint changes the height of the sampleView when the button is clicked. I'm toggling between 50 and 30 points in height here.
So short story,
I was adding below line every time I want to update height which is wrong.
myView.heightAnchor.constraint(equalToConstant: 100).isActive = true
With this, what it do is take the least height and set that. Ex If I set height 3 times as 100, 20, 80, it will take 20 as the height even I run with 80.
To avoid this, we have to add a variable for the height constraint
var heightConstraint : NSLayoutConstraint?
Now while adding constraints to view, add as below.
heightConstraint = mainCVView.heightAnchor.constraint(equalToConstant: 50)
heightConstraint!.isActive = true
Now whenever you want to update height do it as below instead of adding constraint again.
heightConstraint?.constant = 0
heightConstraint?.isActive = true
myView.layoutIfNeeded()
This is most important step and not to add constraint the way I was adding in question
So whenever you want to update just update constant as show above. That's it.

UIButton is resized to fit UIImage

let btn = UIButton(frame: CGRect.zero)
btn.setImage(..., for: UIControlState.normal)
addSubview(btn)
btn.translatesAutoresizingMaskIntoConstraints = false
btn.rightAnchor.constraint(equalTo: btn.superview!.rightAnchor, constant: -32).isActive = true
btn.topAnchor.constraint(equalTo: btn.superview!.topAnchor, constant: 6 + vc.view.safeAreaInsets.top).isActive = true
btn.widthAnchor.constraint(equalToConstant: 32)
btn.heightAnchor.constraint(equalToConstant: 32)
btn.backgroundColor = .black //to check the rect
The size of the image is 16x16. The button should be larger at least twice (because of small touch area).
But when I run the app the button becomes 16x22 (22 - height). In "debug view hierarchy" I also see that its constraints become width==16 and height==22.
In the same time distance constraints work normally.
So what is my mistake? Or must I use image resources which fit buttons 1:1?
Try this. It tells the button that it shouldn't hug its contents, and spread out according to its other constraints:
btn.setContentHuggingPriority(.defaultLow, for: .horizontal)
btn.setContentHuggingPriority(.defaultLow, for: .vertical)

How to programmatically disable constraint when animating

I am have a view with a fixed width anchored to the left. I want this view to animate (moving left to right) by anchoring it to the right (thus removing the left anchor). How do I do this?
I tried setting a priority but I am not sure of the syntax. I also tried to disable the constraint, but that did not work.
fileprivate func sparkle() {
let sparkleView = UIView()
sparkleView.backgroundColor = UIColor.yellow
sparkleView.alpha = 0.5
addSubview(sparkleView)
sparkleView.translatesAutoresizingMaskIntoConstraints = false
sparkleView.topAnchor.constraint(equalTo: topAnchor).isActive = true
sparkleView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
sparkleView.widthAnchor.constraint(equalToConstant: frame.width / 5).isActive = true
sparkleView.leftAnchor.constraint(equalTo: leftAnchor).priority = UILayoutPriorityDefaultLow // Doesn't work
sparkleView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
layoutIfNeeded()
sparkleView.leftAnchor.constraint(equalTo: leftAnchor).isActive = false // Doesn't work
sparkleView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
UIView.animate(withDuration: 2, animations: {
self.layoutIfNeeded()
}) { (_) in
sparkleView.removeFromSuperview()
}
}
Currently, the width constraint gets broken since we are assigning a left and right anchor.
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x6000020b8eb0 UIView:0x7fc846d551d0.width == 15.35 (active)>
-- EDIT --
I have tried creating an instance variable to hold the constraint.
let leftCons = sparkleView.leftAnchor.constraint(equalTo: leftAnchor)
leftCons.isActive = true
I then tried to modify the priority
leftCons.priority = UILayoutPriorityDefaultLow
However, this causes error
'Mutating a priority from required to not on an installed constraint (or vice-versa) is not supported. You passed priority 250 and the existing priority was 1000.'
Instead, I need to just set it as inactive (from the answers)
leftCons.active = false
-- Correct code if interested.. --
fileprivate func sparkle() {
let sparkleView = UIView()
sparkleView.backgroundColor = UIColor.yellow
sparkleView.alpha = 0.5
addSubview(sparkleView)
sparkleView.translatesAutoresizingMaskIntoConstraints = false
sparkleView.topAnchor.constraint(equalTo: topAnchor).isActive = true
sparkleView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
sparkleView.widthAnchor.constraint(equalToConstant: frame.width / 5).isActive = true
let leftCons = sparkleView.leftAnchor.constraint(equalTo: leftAnchor)
leftCons.isActive = true
layoutIfNeeded()
leftCons.isActive = false
sparkleView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
UIView.animate(withDuration: 0.5, animations: {
self.layoutIfNeeded()
}) { (_) in
sparkleView.removeFromSuperview()
}
}
You need to create a var
var leftCon:NSLayoutConstraint!
then alter it , you problem is every line create a new constraint
sparkleView.widthAnchor.constraint(equalToConstant: frame.width / 5).isActive = true
sparkleView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
sparkleView.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
note: above 3 constraints create a conflict as the view can't be pinned to left and right at the same time with a width constraint that why you see the conflict break => NSLayoutConstraint:0x6000020b8eb0 UIView:0x7fc846d551d0.width == 15.35 (active)
regardless of the active assignment so those
sparkleView.leftAnchor.constraint(equalTo: leftAnchor).priority = UILayoutPriorityDefaultLow // Doesn't work
sparkleView.leftAnchor.constraint(equalTo: leftAnchor).isActive = false // Doesn't work
are on the fly and don't alter the created constraint , so to be
leftCon = sparkleView.leftAnchor.constraint(equalTo: leftAnchor)
leftCon.isActive = true
Regarding this crash
'Mutating a priority from required to not on an installed constraint (or vice-versa) is not supported
you need to set the priority before the activation
leftCon.priority = UILayoutPriorityDefaultLow
leftCon.isActive = true

How set UIButton size by it's title size?

I have UIButton, which title is dynamic changes. Button size should changes with title size and will be equal title size.
How to do this programmatically in Swift?
To have your button use its intrinsic content size and automatically resize based upon its text, use Auto Layout to position the button. Only set constraints to position the button and iOS will use the size of the text to determine the width and height of the button.
For example:
let button = UIButton()
// tell it to NOT use the frame
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Hello", for: .normal)
view.addSubview(button)
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
This also works if you create the button in the Storyboard. Again, only give constraints to place the button and it will resize to accommodate the text.
Follow below steps(its not a proper solution but you can solve your problem by doing like this )
Create a UILabel (because UILabel adjust its height and width depends on the text)
UIlabel number of line to 1
Create a UIButton over UILabel
Set button title to ""
Set button's constraint : Align button's top and leading to UILabel and equals width and height
Hope this will works for you :)
You can get UIButton's width and Height dynamically with its title.
With the help of, NSString's Size property we can achieve this.
let buttonNAme = [" hi ", "welcome", "Login", "Forgot Password ??", "New to here. Sign up??"]
var yPos = CGFloat()
override func viewWillAppear(_ animated: Bool) {
yPos = 40
for i in 0..<buttonNAme.count
{
self.view.addSubview(addingCustomButton(buttonTitle: buttonNAme[i], buttonFontSize: 15, buttonCount: i))
}
}
func addingCustomButton(buttonTitle : String, buttonFontSize: CGFloat, buttonCount : Int) -> UIButton
{
let ownButton = UIButton()
ownButton.setTitle(buttonTitle, for: UIControlState.normal)
ownButton.titleLabel?.font = UIFont.systemFont(ofSize: buttonFontSize)
let buttonTitleSize = (buttonTitle as NSString).size(attributes: [NSFontAttributeName : UIFont.boldSystemFont(ofSize: buttonFontSize + 1)])
ownButton.frame.size.height = buttonTitleSize.height * 2
ownButton.frame.size.width = buttonTitleSize.width
ownButton.frame.origin.x = 30
yPos = yPos + (ownButton.frame.size.height) + 10
ownButton.frame.origin.y = yPos
ownButton.tintColor = UIColor.white
ownButton.backgroundColor = .brown
ownButton.tag = buttonCount
ownButton.setTitleColor(UIColor.darkGray, for: UIControlState.highlighted)
ownButton.addTarget(self, action: #selector(ownButtonAction), for: UIControlEvents.touchUpInside)
return ownButton
}
func ownButtonAction(sender: UIButton)
{
print("\n\n Title \(sender.titleLabel?.text) TagNum \(sender.tag)")
}
Output
Just Constraint it's origin, and the size will fit it's button title