I'm new in swift, recently I want to change my heightAnchor programmatically. I have looked up some solutions on stack overflow but most of them are using IBOutlet in storyboard. My question is, if there exists a constraint already, is there any simple way to just update a new constraint?
for example
something.translatesAutoresizingMaskIntoConstraints = false
something.widthAnchor.constraint(equalToConstant: 50).isActive = true // original constraint
something.widthAnchor.constraint(equalToConstant: 100).isActive = true // want to override this one
If you have access to original constraint, hold a strong reference to it and update its constant value directly
declare a variable as
var somethingWidthConstraint:NSLayoutConstraint? = nil
and wherever you are setting this constraint originally, hold a strong reference to it
self.somethingWidthConstraint = something.widthAnchor.constraint(equalToConstant: 50)
self.somethingWidthConstraint.isActive = true
whenever you need to change its constant simply say
self.somethingWidthConstraint?.constant = 100
self.view.layoutIfNeeded()
In order for your view to accept this value and update its frame immediately (without having to wait for next UI render cycle), you might have to call self.view.layoutIfNeeded and if you wanna animate this change you can always wrap it inside UIView.animate
UIView.animate(withDuration: 0.1) {
self.view.layoutIfNeeded()
}
Thats all
Try to set the old constraint to false before setting the new one.
something.translatesAutoresizingMaskIntoConstraints = false
something.widthAnchor.constraint(equalToConstant: 50).isActive = true
something.widthAnchor.constraint(equalToConstant: 50).isActive = false
something.widthAnchor.constraint(equalToConstant: 100).isActive = true
Related
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]
}
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.
Hi I'm just wanting to have a childView centered on the x axis of a NSViewController view using auto layout. I'm doing the following:
override func viewWillAppear()
{
super.viewWillAppear()
let childView = PagesView.init(frame: CGRect(x: 0, y: 0, width:100, height: 100))
childView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(childView)
childView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
}
The childView doesn't even appear on the screen. When I remove the last line, then it gets positioned at 0,0. So something about that last line is causing it go haywire and I'm not sure why? I've used this exact same logic on iOS and it has worked fine.
All views should have constraints to define its location and size.
// This would create dimension constraints if your class cannot already infer them
childView.widthAnchor.constraint(equalToConstant: 100).isActive = true
childView.heightAnchor.constraint(equalToConstant: 100).isActive = true
You also need to specify a vertical anchor constraint so the view has enough information to be displayed.
childView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
I have a subclass of UIView, I want to manipulate a constraint but it does not work.
When pressing a button exchangesViewActivated changes and the functions get called.
var exchangesViewActivated = false {
didSet {
if exchangesViewActivated == true {
setExchangesView()
} else {
setUpLayout()
}
}
}
Die subview and translatesAutoresizingMaskIntoConstraints are set.
func setUpLayout() {
bottomContainer.heightAnchor.constraint(equalToConstant: 500).isActive = true
bottomContainer.leadingAnchor.constraint(equalTo: scrollViewContrainer.leadingAnchor).isActive = true
bottomContainer.trailingAnchor.constraint(equalTo: scrollViewContrainer.trailingAnchor).isActive = true
bottomContainer.topAnchor.constraint(equalTo: configBar.bottomAnchor).isActive = true
bottomContainer.bottomAnchor.constraint(equalTo: scrollViewContrainer.bottomAnchor).isActive = true
}
Now I want to manipulate the constraint by calling this function:
func setExchangesView() {
bottomContainer.bottomAnchor.constraint(equalTo: scrollViewContrainer.bottomAnchor).isActive = false
bottomContainer.heightAnchor.constraint(equalToConstant: 500).isActive = false
bottomContainer.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
}
But this constraint stays activated:
bottomContainer.heightAnchor.constraint(equalToConstant: 500).isActive
Am I missing something? Is it not enough to set a constraint to false to deactivate it? Do I have to call something else?
setUpLayout() is adding new constraints every time you call setUpLayout() method. You must save constraint reference and update it next time.
For example.
// class variable
var bottomConstraint:NSLayoutConstraint? = nil
bottomConstraint = bottomContainer.heightAnchor.constraint(equalToConstant: 500)
bottomConstraint.isActive = true
And later update constraint
bottomConstraint.constant = 100
bottomConstraint.isActive = true
This doesn't deactivate old constraints as it deactivates the newly created ones so do
var heightCon:NSLayoutConstraint!
var botCon:NSLayoutConstraint!
//
heightCon = bottomContainer.heightAnchor.constraint(equalToConstant: 500)
heightCon.isActive = true
botCon = bottomContainer.bottomAnchor.constraint(equalTo: scrollViewContrainer.bottomAnchor)
botCon.isActive = true
Then you can easily reference the constraints and deactivates them and add the new constraints
I have a UITextField, and I'm trying to change its position when the keyboard comes up. More specifically, I want to move the text field up so that it sits just above the keyboard. My code looks something like this
let textField = myCustomTextField()
override func viewDidLoad() {
//set up textfield here
//constrains blah blah
textField.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor,
constant: -view.bounds.height * 0.05).isActive = true
//more constraints
}
What I want to do next is change that constraint so that it raises up the textfield when the keyboard comes up. My code looks like this:
#objc func keyboardWillShow(notification: NSNotification) {
textField.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor,
constant: -view.bounds.height * 0.05).constant = 200 //200 is a sample number I have a math calculation there
textField.layoutIfNeeded()
}
That doesn't work because referencing that constraint by just using constraint(equalTo: constant:) doesn't actually return any constraint. Is there any way to reference that constraint without creating a variable for each constraint I want to change and changing its constant?
The problem with your code is that you create a second constraint instead of changing the current , you should hold a reference to the created on and change it's constant , you can reference it like this
var bottomCon:NSLayoutConstraint?
override func viewDidLoad() {
bottomCon = textField.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor,
constant: -view.bounds.height * 0.05)
bottomCon.isActive = true
}
then you can access it in any method and play with constant property
Edit: for every constraint you create assign identifier and access it as below
let textFieldCons= button.constraints.filter { $0.identifier == "id" }
if let botCons = textField.first {
// process here
}