Animate NSLayoutConstraints multiplier and/or isActive state - ios

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()

Related

How to animate centered square to the top

I have a UIView in a portrait-only app.
The view is centered vertically and horizontally with AutoLayout ("manually" using storyboards).
The width equals the (main)view.width * 0.9
The height is the same size of the width (it is a square).
I want to tap a button inside this UIView and animate it only vertically until it reaches the top border of the screen (eg. height*0.9, 10 pts, whatever is possible).
When I click again, I want to reposition back the view to its original position (centered as it was when I first tapped).
During the transition the square should not be tappable.
After reading many posts I could not understand what's the best way to do this (I red mainly developers saying old techniques using centerX should be avoided and lamentations about some versions of the SO behaving in strange ways).
I suppose I should find a way to get the current "position" of the constraints and to assign a constraint the "final" position, but I was not able to do it.
Any help is appreciated
You are all going the wrong way about this.
Add one constraint that pins the view to the top, and add one constraint that pins the view to centerY. It will complain, so pick one and disable it (I think the property in Interface Builder is called Installed).
If the initial state is the view in the center, disable the constraint that pins it to the top, and viceversa.
Now write IBOutlets for both constraints in your controller and connect them to those constraints. Make sure the declaration of that variable is not weak, otherwise the variable will become nil when the constraint is disabled.
Whenever you want to toggle your animation you can enable one constraint and disable the other.
#IBOutlet var topConstraint: NSLayoutConstraint!
#IBOutlet var centerConstraint: NSLayoutConstraint!
func toggleState(moveToTop: Bool) {
UIView.animateWithDuration(0.25) {
self.topConstraint.isActive = moveToTop
self.centerConstraint.isActive = !moveToTop
self.view.layoutIfNeeded()
}
}
While you can use Autolayout to animate - to take the constraint constraining the centerY and set its constant to a value that would move to the top (e.g., constant = -(UIScreen.main.bounds.height / 2)), I would recommend using view's transform property.
So to move the view to the top you can use:
let topMargin = CGFloat(20)
let viewHalfHeight = self.view.bounds.height / 2
let boxHalfHeight = self.box.bounds.height / 2
UIView.animate(withDuration: 0.2) {
box.transform = CGAffineTransform.identity
.translatedBy(x: 0, y: -(viewHalfHeight - (boxHalfHeight + topMargin)))
}
You are moving box.center related to the view.center - so if you want to move the box to the top, you have to move its center by half a view's height (because the view's centerY is exactly height / 2 far from the view's top). That is not enough though, because then only a bottom half of the box is visible (now the box.centerY == view.top). Therefore you have to move it back by the box.bounds.height / 2 (in my code boxHalfHeight) - to make the top half visible. And to that boxHalfHeight you add topMargin so that there is some margin to the top.
Then, to move the box back to original position:
UIView.animate(withDuration: 0.2) {
box.transform = CGAffineTransform.identity
}
EDIT
If you really want to go with autolayout, you have to have a reference to the centerY constraint, so for example if it is created this way:
let boxCenterYConstraint = self.box.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)
boxCenterYConstraint.isActive = true
Then you can try this:
// calculating the translation is the same
let topMargin = CGFloat(20)
let viewHalfHeight = self.view.bounds.height / 2
let boxHalfHeight = self.box.bounds.height / 2
let diff = -(viewHalfHeight - (boxHalfHeight + topMargin))
boxCenterYConstraint.constant = diff
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.2) {
self.view.layoutIfNeeded()
}
And animation back:
boxCenterYConstraint.constant = 0
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.2) {
self.view.layoutIfNeeded()
}

iOS - Change constraints in runtime works only first time

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.

UIImageView doesn't animate when using Auto Layout

My image has the following constraints:
leading = 0
trailing = 0
top = 75
height = 250
I want my image to start from the right side of the screen and move to initial place where the constraints are set.
Here is my code:
imageLeadingConstraint.constant = 100
imageTrailingConstraint.constant = -100
imageTopConstraint.constant = 100
image.layoutIfNeeded()
UIView.animate(withDuration: 1.0) {
self.imageLeadingConstraint.constant = 0
self.imageTrailingConstraint.constant = 0
self.imageTopConstraint.constant = 75
self.image.layoutIfNeeded()
}
The issue is that the image is moving to initial place, but not using an animation.
Is my code right ? How come the image just appears in the new position and not animating ?
You should call layoutIfNeeded for imageView's superview.
self.image.superview.layoutIfNeeded()
I'm not sure that I understood you, but:
1) Change constraints (second time) outside of animation block.
2) use self.image.superview?.layoutIfNeeded() instead

Swift: removefromSuperview removes constraints

Currently working on some swift program and I've a call to action where I remove a blurview from the superview and at the same time I'm animating 2 buttons.
Everything works like it should but there is one small problem. When I remove the blurview from my superview the constraints on my 2 buttons are being set to 0 on the bottom and animating from that position.
I don't want them to shift to 0. If I don't remove the blurview my animation is working perfectly. I've checked if my button constraints are related to the blurview, but that isn't the case. Because I assumed that it could only reset my constraints when they are relative to the blurview.
My storyboard looks the following:
view
|-> camera_view
|-> blur_view
|-> record_label
|-> record_button
The code that I'm executing is the following:
#IBAction func recordButton(sender: AnyObject) {
self.blurView?.removeFromSuperview()
UIButton.animateWithDuration(0.3, delay: 0.2, options: .CurveEaseOut, animations: {
var recordButtonFrame = self.recordButton.frame
var recordLabelFrame = self.recordLabel.frame
recordButtonFrame.origin.y -= recordButtonFrame.size.height
recordLabelFrame.origin.y -= recordLabelFrame.size.height
self.recordButton.frame = recordButtonFrame
self.recordLabel.frame = recordLabelFrame
}, completion: { finished in
print("Button moved")
})
}
What am I doing wrong?
Kind regards,
Wouter
Instead of removing blurView from superview you could hide it.
Replace
self.blurView?.removeFromSuperview()
with
self.blurView?.hidden = true
The problem is that you're animating frames while using constraints. You should be animating constrain changes / constraint constant value changes.
When you don't remove the view the layout isn't recalculated so your frame animation 'works'. It isn't correct and will get reorganised at some point in the future.
When you remove the view the layout is recalculated and everything moves around before your animation starts.
You don't give details of your constraints but it seems likely that you should be animating constraints before removing the view, then removing and ensuring the constraints are all sane on completion.

Swift animateWithDuration()

I want to change the imageView's y in an animation and change the text of the label. But when I do that. The ImageView goes up 100 and then does the animation from -100 to 0. But I want it do go down 100.
If I don't change the text of the label, the animation works perfectly.
label.text = "test"
UIView.animateWithDuration(0.5, animations: {
self.imageView.frame.origin.y += 100
})
Also if I do that, it only changes the text and doesn't move the imageView.
label.text = "test"
imageView.frame.origin.y += 100
You are using AutoLayout. You cannot change the frame directly when using auto layout.
You have to set up the constraints and then animate the change in constraints to animate the views.
Also, you can't do blah.frame.origin.y += 100 IIRC the origin on CGrect is read only.
You can do...
Blah.frame = CGRectOffset(blah.frame, 0, 100)
But again that won't work with auto layout.
Edit
The origin is settable in swift. Thanks jrturton:-)

Resources