iOS - Change constraints in runtime works only first time - ios

I have this code, that changle constraint based on visibility of element:
if (self.collectionView.isHidden){
controller.view.bottomAnchor.constraint(equalTo: self.collectionView.topAnchor).isActive = false
controller.view.bottomAnchor.constraint(equalTo: self.view2.topAnchor).isActive = true
}
else {
controller.view.bottomAnchor.constraint(equalTo: self.collectionView.topAnchor).isActive = true
controller.view.bottomAnchor.constraint(equalTo: self.view2.topAnchor).isActive = false
}
If I do this after collectionView.isHidden is set to true, it works. However, after I set collectionView.isHidden = true and call this code, it no longer works and controller.view is still attached to the top of view2.
There is also an height constraint attached to collectionView ant ist values is 50.
I also have tried manually setting collectionView.frame.size.height = 50 (or some other default value) because without this, height of collectionView.frame.size.height is zero. But not works. I have tried call collectionView.updateConstraints(), but it has no effect either.

So, I think you are setting a new constraint every time you call the function, you are not really removing the previous one.
Usually when I need this kind of logic I keep a reference to the constraint so that I can activate/deactivate it later, like this:
var controllerBottomAnchor: NSLayoutConstraint?
Then I assign it like so:
controllerBottomAnchor = controller.view.bottomAnchor.constraint(equalTo: self.collectionView.topAnchor)
controllerBottomAnchor?.isActive = true
Once I need to change it I just use the reference:
controllerBottomAnchor?.isActive = false
I typically use it for width and height anchors.

Related

Constraints on Child Container

I am adding a child view using the View.addChild method
The containing view is clearly 350 pixels. However, the child view takes up ALL the space of the containing view....so my idea is to force the child view to be smaller than its parent...but my code does not work. I can tell you that if I uncomment the two lines it almost works, but then the child view does not occupy the size that I want it to and it blocks other elements. Here is where I am:
child.view.translatesAutoresizingMaskIntoConstraints = false
let safeArea = view.layoutMarginsGuide
//child.view.topAnchor.constraint(equalTo: tableContainer.topAnchor).isActive = true
// child.view.bottomAnchor.constraint(equalTo: tableContainer.bottomAnchor).isActive = true
child.view.leftAnchor.constraint(equalTo: tableContainer.leftAnchor).isActive = true
child.view.rightAnchor.constraint(equalTo: tableContainer.rightAnchor).isActive = true
child.view.heightAnchor.constraint(equalToConstant: 250).isActive = true
self.addChild(child)
Let me state very clearly, my goal is to get the child view to 250 pixels. Thank you.
Your solution might look something like this.
child.view.translatesAutoresizingMaskIntoConstraints = false
let safeArea = view.layoutMarginsGuide
//Your left and right anchors tell the compiler exactly how wide the view should be.
//If you have it set to equal both then the view MUST be exactly the width of parent.
child.view.leftAnchor.constraint(equalTo: tableContainer.leftAnchor).isActive = true
child.view.rightAnchor.constraint(equalTo: tableContainer.rightAnchor).isActive = true
//Since you're defining your own height here you need to explicitly state where the
//view starts at. If you don't define a top, center, bottom or some other constraint
//it's impossible to know where to put the view.
child.view.heightAnchor.constraint(equalToConstant: 250).isActive = true
child.view.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
self.addChild(child)
The reason for this is that when creating anchors, you MUST have an X & Y anchors for all views, even logically. For example if you define a width of 100 and a height of 100 you've created a square, but where does that square go? You don't have an X or a Y in that example. However, if you define a width to match the parent view using the left and right anchor then it knows the width of the view just by the anchors on the left and right. The same principle applies to a top and bottom anchor. If you define a top and bottom anchor then it will be the size of the view (Given that you set them equalTo) and it will know the height.
In your instance, you've defined height of 250 however it doesn't know where to start at. Does it start at the top, middle, bottom, it doesn't have a clue because you haven't set it. The IDE is very literal with constraints and no obscurity will work.

How do I limit the size of a UIView in a storyboard designer and the runtime view overall?

I have a custom UIView that's my little component lets call it PlaceholderView.
My UITableViewCell prototype has a Label and a PlaceholderView that sits inside a UIStackView that's vertically axis.
The PlaceholderView, is supposed to call some custom code that goes to a cache to retrieve a specific view then it adds it to the SubView of the PlaceholderView.
I want this subview to take up the whole surface of the entire PlaceholderView. How do I go about doing that? I tried this but not sure if it does the job
if (view != null)
{
AddSubview(view);
view.SizeToFit();
}
Second question. These view's that I am adding, when I create them during design time, I make a new storyboard, drag and drop a ViewController then proceed to place other controls like Labels and Button's on it.
How do I restrict this ViewController's overall height so it's completely fixed size? I know I can set the simulated metrics, and I am also setting the View. Frame's size to restrict the height.
Are there better ways to make these views constrained easier?
Currently, when I am setting these fixed height values, it does cause some weird issues with overlaps if I set UITableView.RowHeigh to AutomaticDimension.
// attaches all sides of the child to its parent view
extension UIView {
func layoutToSuperview() {
guard let view = self.superview else {return}
self.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
self.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
self.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
self.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
}
}
Usage:
Instead of view.SizeToFit() use view.layoutToSuperview()
I want this subview to take up the whole surface of the entire
PlaceholderView
You can try autolayout:
if (view != null)
{
view.TranslatesAutoresizingMaskIntoConstraints = false;
AddSubview(view);
view.LeadingAnchor.ConstraintEqualTo(this.LeadingAnchor).Active = true;
view.TopAnchor.ConstraintEqualTo(this.TopAnchor).Active = true;
view.TrailingAnchor.ConstraintEqualTo(this.TrailingAnchor).Active = true;
view.BottomAnchor.ConstraintEqualTo(this.BottomAnchor).Active = true;
}
Are there better ways to make these views constrained easier?
The answer is also auto layout. Set a control's height constraint to claim how much space it wants to take. See this thread for more details: Using Auto Layout in UITableView for dynamic cell layouts & variable row heights.
Though it is native oc, you can see its concept.

View doesn't appear after changing the view property 'isHidden = false'

I'm developing an iOS application and then the issue that I have faced now is showing a view using the view property isHidden.
I initialized a custom view including a CAAnimation and then set the default isHidden property true to hide. After a certain condition meets I changed the isHidden property to false to show it. But in this case the view doesn't appear.
private func setupButtonEffectView() {
self.buttonEffectView = ButtonEffectView()
self.buttonEffectView!.translatesAutoresizingMaskIntoConstraints = false
// self.view.addSubview(self.buttonEffectView!)
self.view.insertSubview(self.buttonEffectView!, belowSubview: self.button!)
NSLayoutConstraint.activate([
self.buttonEffectView!.centerXAnchor.constraint(equalTo: self.button!.centerXAnchor),
self.buttonEffectView!.centerYAnchor.constraint(equalTo: self.button!.centerYAnchor),
self.buttonEffectView!.widthAnchor.constraint(equalToConstant: 100),
self.buttonEffectView!.heightAnchor.constraint(equalToConstant: 100)
])
self.buttonEffectView!.isHidden = true
}
I created the button effect using the method above.
Try instead of hiding the view, setting the alpha to 0.0. self.buttonEffectView.alpha = 0.0. Then when you want to show it set the alpha to 1.0.

Animate NSLayoutConstraints multiplier and/or isActive state

I want to animate a constraint changing for a custom view.
I've tried ways similar to this:
if widthConstraint.isActive {
widthConstraint.isActive = false
widthConstraintA.isActive = true
} else {
widthConstraintA.isActive = false
widthConstraint.isActive = true
}
UIView.animate(withDuration: 1) {
imageView.layoutIfNeeded()
}
the same for the multiplier (recreating a new constraint like here Can i change multiplier property for NSLayoutConstraint? ). But all states change instantly.
Is there any method that helps to create smooth interpolated animation between two states?
Yes there is suppose that a view1 in storyboard has a width constraint to the main view (self.view in code) like this
view1Width = view * 0.7 + constant
instead of creating 2 constraints and switching between them by changing Active property ORRR [by deactivating current constraint with old multiplier and creating a new one with a new multiplier] leave it 1 constraint and play with it's constant
suppose i want to change view1 multiplier to be 0.9 instead of 0.7
I may do this
self.view1Width.constant += self.view.frame.size.width * 0.2
same you do for subtraction
with this idea you haven't need to be stuck in how to animate active on/off but you still use
self.view.layoutIfNeeded()

Swift how to set UIView's height constraint based on it's content

I have a UIView in my swift code
let profile_inf_wrapper: UIView = {
let v = UIView()
v.backgroundColor = .red
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
profile_inf_wrapper.topAnchor.constraint(equalTo: view.topAnchor, constant:64).isActive = true
profile_inf_wrapper.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
profile_inf_wrapper.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
profile_inf_wrapper.heightAnchor.constraint(equalToConstant: view.frame.height/4).isActive = true
backgroundImageView.topAnchor.constraint(equalTo: profile_inf_wrapper.topAnchor).isActive = true
backgroundImageView.leftAnchor.constraint(equalTo: profile_inf_wrapper.leftAnchor).isActive = true
backgroundImageView.rightAnchor.constraint(equalTo: profile_inf_wrapper.rightAnchor).isActive = true
backgroundImageView.bottomAnchor.constraint(equalTo: profile_inf_wrapper.bottomAnchor).isActive = true
profileImage.centerYAnchor.constraint(equalTo: profile_inf_wrapper.centerYAnchor).isActive = true
profileImage.leftAnchor.constraint(equalTo: view.leftAnchor, constant:25).isActive = true
profileImage.widthAnchor.constraint(equalToConstant: 110).isActive = true
profileImage.heightAnchor.constraint(equalToConstant: 110).isActive = true
usernameLabel.topAnchor.constraint(equalTo: profile_inf_wrapper.topAnchor, constant:40).isActive = true
usernameLabel.leftAnchor.constraint(equalTo: profileImage.rightAnchor, constant:20).isActive = true
countryIcon.topAnchor.constraint(equalTo: usernameLabel.bottomAnchor, constant:10).isActive = true
countryIcon.leftAnchor.constraint(equalTo: profileImage.rightAnchor, constant:20).isActive = true
countryIcon.widthAnchor.constraint(equalToConstant: 25).isActive = true
countryIcon.heightAnchor.constraint(equalToConstant: 25 ).isActive = true
countryName.topAnchor.constraint(equalTo: usernameLabel.bottomAnchor, constant:5).isActive = true
countryName.leftAnchor.constraint(equalTo: countryIcon.rightAnchor, constant:10).isActive = true
countryName.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
All these elements are the subviews of profile_inf_wrapper.Sometimes view.frame.height/4 is too small and i want to be able to resize the UIView based on it's content
There's a property in UIView called intrinsicContentSize. It returns the smallest size that the view would need show all of it's content.
While the default implementation is not very useful because a UIView doesn't have any content on it's own, all of the default subclasses implement it.
A UILabel will return a size that fits the text perfectly, and a UIButton will return a size that fits it's contents plus whatever spacing you've added. You get the gist of it.
You can take advantage of this property by only constraining either width or height of a view, not both. If you constrain the width of a UILabel and add more text, it will grow vertically.
Finally, when you add subviews to a UIView, and you add constraints to both margins of an axis (top and bottom or left and right), as long as there's a "chain" of constraints and views, and the view doesn't have any constraints on the size, it will expand to fit.
For example, if you have a view with a label and a button, vertically arranged, if the label is constrained to the top, then constrained to the button, and the button is constrained to the bottom, as long as the container view doesn't have a height constraint, it will expand to fit the two views plus the margins perfectly.
Your goal should always be to use the least amount of constraints to express your design, without removing useful constraints. Make sure you take advantage of the intrinsicContentSize.
For setting the height of uiview dynamically you have to add height/bottom constraint to the view in your problem it might be
profile_inf_wrapper.heightAnchor.constraint(equalToConstant: view.frame.height/4+countryName.frame.size.height).isActive = true
you also need the view size to fit to get actual updated size
like
countryName.sizeToFit()
And then update layout if needed to get all affect
The first thing you want to do is make a reference to the height constraint of profile_inf_wrapper.
var profile_inf_wrapper_height_constraint: NSLayoutConstraint?
I don't know the details of your content, but when the view needs resized, you can check that with a conditional in your viewController,
if contentRequiresResizing {
profile_inf_wrapper_height_constraint.constant = view.frame.width/3
else {
profile_inf_wrapper_height_constraint.constant = view.frame.width/4
}
By referencing constraints, it allows you to support dynamic UI changes easily.
As a side note, I would recommend renaming your UIView variable name so that the reference constraint isn't so long. The Swift 3 API guidelines also support lowerCamelCase, as opposed to underscore naming.

Resources