I'm trying to animate constraints in my ViewController.
In order to do that, I added this line to my code:
I'm just trying to change the height of a UIView() (from 0 to 100)
barHeight.constant = CGFloat(100)
UIView.animate(withDuration: 2) {self.view.layoutIfNeeded()}
The problem is that, with that line, all of the constraints are animated, and it's not what I would like.
Do you know how I could animate only specific constraints and not others?
Call layoutSubviews before you edit the constraint you want to animate. This will cause any pending layout updates to be applied without any animation and then you can change the next constraint with animation.
Like so:
self.view.layoutSubviews()
barHeight.constant = CGFloat(100)
UIView.animate(withDuration: 2) {self.view.layoutIfNeeded()}
Related
I am making a card app and I need to make an animation so that a card would change its constraints to move to another place. How would I do this for a UIImage.
Hook the leading , trailing , CenterX OR CenterY constraint of the UIImageView and do this
self.imageLeadCon.constant = // value
UIView.animate(withDuration: 3.0 , animations: {
self.view.layoutIfNeeded()
// animation
}, completion: { _ in
// completion
})
I think you're confusing UIImage with UIImageView. The first one is the image's data representation, the second one is the View that displays the UIImage.
So I guess you want to move around the UIImageView. To do that obtain a reference to the constraints (e.g. by ctrl-dragging from the constraint in the storyboard to your UIViewController instance).
After that you can update the constraint in an animation block like here:
// Make sure the view is laid out as Mischanya Schtrigel stated
self.view.layoutIfNeeded()
UIView.animate(withDuration: 0.5) { // duration in seconds
myConstraint.constant = 50 // update your constraint here
self.view.layoutIfNeeded()
}
Contrary to the other answers, I like putting the constraint changes in the animation closure to make it more clear what is going to be animated.
First of all you have to drag and drop your constraint into your view controller, to make an outlet. The same way you are doing it with UIImageView or UITableView, etc.
Actually, for those who have watched WWDC (2014 I guess), Apple have explained how to animate UI in the proper way.
Firstly, you have to call layoutIfNeeded() method to make sure that everything on your view have laid out. After you can change your constraint and right after that in your animation block you call layoutIfNeeded() method to layout your constraint with the new value.
So code should look like that:
view.layoutIfNeeded() // to make sure your view have laid out
self.constraint.constant = //change your constant to any value
UIView.animate(withDuration: 1.0) {
self.view.layoutIfNeeded() //layout your constraint with the new value
}
I have a UIStackView with axis vertical that is displaying some items similar to a UITableView. However, I am animating them in. Delay using DispatchQueue.main.asyncAfter.
Here is the code I am using to create the CABasicAnimation:
let anim = CABasicAnimation(keyPath: "transform.translation.x")
anim.duration = 0.2
anim.fromValue = UIScreen.main.bounds.size.width
anim.toValue = 0
anim.isRemovedOnCompletion = true
anim.fillMode = kCAFillModeRemoved
For some reason, at least on the Simulator (I don't think this should matter, as this app should work on older devices, or powerful devices like iPhone 7+), the animation doesn't always land in the same spot.
Sometimes, the cell will fly in and be about 8 pixels from the left, sometimes about 12, sometimes about 20. It doesn't seem consistant. But I think my toValue being 0, it should appear where it would appear normally if I didn't add an animation to the layer.
I tried setting isRemovedOnCompletion to true, and fillMode to removed, but this didn't seem to fix the animation.
On a side note, as the animation continues, there is one point where a cell is made from a horizontal UIStackView, where the arrangedSubviews are hidden in it. I use the UIView.animateWithDuration method to set their hidden values to false, and thus, animate the subviews into existence.
The reason I note this is because when this happens, the cells up above pop back into the positions they should be, instead of being a few pixels off.
I tried setNeedsDisplay on the cells after they animate, and also on the verticalStackView, which didn't work either. I don't get what is going on and why these layers just won't animate into their correct positions. Any ideas anyone?
P.S. The vertical stackView is all initialized in the ViewController's init method. The animations occur in the viewDidAppear method. I tried offsetting the animation by adding DispatchQueue.main.asyncAfter, and also tried it first thing after super.viewDidAppear(animated), but both had the same results.
Animating items into and out of a stack view is actually VERY easy.
All you have to do is to set the frame of the view to the place you want it to start before adding it to the stack view, and then call layoutIfNeeded() inside an animation block immediately after adding the new view. Here's some sample code taken from a working app:
let newView = createNewView()
self.stackView.addArrangedSubview(newView)
if sender != nil {
UIView.animate(withDuration: 0.2) {
self.stackView.layoutIfNeeded()
}
}
Figured out the issue. Here's the problem...
1) You want to animate just the presentation layer, but keep the actual position of the elements due to UIStackView positioning things well for you. So CoreAnimation seems like a great option.
2) Because you're dealing with a UIStackView, at first glance, you can't animate position constraints for its arrangedSubviews. So it's another reason you don't want to animate constraints, and go with CoreAnimation.
So you choose CoreAnimation. Since it's only the presentation layer that matters, no need to animate constraints right? Only really need to do those if you want the user interactive portion of the views to adhere to the constraints right? Well yes, and no.
But for whatever reason, CoreAnimation doesn't do what it's supposed to do. Maybe Apple will fix that in the future. But Maybe not. Oh well, it doesn't matter. Here's the solution:
Use a placeholder for all of your arrangedSubviews in the stackview. For me, this was just a UIView that would resize itself based on the contents within it.
Now, let this placeholder have no background color. Only allow it to have visible content that you don't ever want to animate. Since I'm animating the entire cell, I just have a blank UIView with no backgroundColor.
Set constraints for subviews of this placeholder view with respect to this placeholder view. Now, you can animate these constraints with the usual AutoLayout animation tools. For example:
constraint.constant = 0
UIView.animate(withDuration: 0.2,
delay: 0.1,
options: UIViewAnimationOptions.curveEaseOut,
animations: {
cell.layoutIfNeeded()
}, completion: nil)
Booyah! Animations are smooth and actually end up where you want them relative to the arrangedSubview. Woohoo!
I have created a storyboard as in the image. In that I have set the vertical spacing between the red view and tableView is around 20. Its working good.
But when i tried to change the height of red view then the tableview should comes up with the constraints I added but the table view is remained constant at the same place.
I used the following code
redView.frame.size = CGSize(width: 1008, height: 0)
self.view.layoutIfNeeded()
Whenever you are adding a constraint to any object on storyboard then onwards that object's frame will be maintained by autolayout engine. This is the primary design principle I guess. In this case what you can simply do is that just make an outlet of the desired constraint which you want to update and update that constraint within your code. For your understanding I am attaching a screenshot.
Now after making a constraint outlet it must look like as follows.
#IBOutlet weak var containerViewHeight: NSLayoutConstraint!
Now to update the height you just have to do one thing.
containerViewHeight.constant = 0
In my opinion if this update is not working inside viewDidLoad or viewWillAppear then please update inside viewDidLayoutSubviews because when viewDidLoad get called then iOS doesn't apply the layout properly.
In my case what I will do is -
override func viewDidLayoutSubviews() { //This ensures all the layout has been applied properly.
super.viewDidLayoutSubviews()
containerViewHeight.constant = 0
}
Note: - To achieve some nice animation effect during layout changes you can apply this constraint update inside an UIView animation block like as follows. But remember, to see the effect nicely you have to call this after presenting the view properly like after viewDidAppear etc.
UIView.animateWithDuration(1.0, delay: 0.2, usingSpringWithDamping: 8.0, initialSpringVelocity: 1.0, options: UIViewAnimationOptions.CurveEaseIn, animations: {
containerViewHeight.constant = 0
self.view.layoutIfNeeded()
}, completion: nil)
Sorry for any mistake.
Update/Edit is most welcome.
Hope this helped. Thanks
You should be using Autolayout to manipulate the redView's height. Modifying its frame's size is not Autolayout. Modify its height constraint (if it exists)
You need to either use constraints or changes to frames! You cannot do both!
What you need to do is to make an outlet reference to your constraint and change its constant-value instead.
If you placed the code in viewdidload or viewwillappear, you will find issues like this. You need to put the code after all the constraints are loaded.
Also, make an object of the height constraint and name it for example redViewHeight.
and change its value by: redViewHeight.constant = 0
It will work!
Replace the constraints for the views with the following.
Redview
Top constraint to ParentView
Leading Constraint to ParentView
Trailing constraint to ParentView
Height Constraint
TableView
Top constraint to RedView
Leading Constraint to ParentView
Trailing constraint to ParentView
Bottom constraint to ParentView
Now wire an IBOutlet for the HeightConstraint of RedView and modify its constant value. The UITableView will adjust its height as desired.
I have a UIView called descriptionView and I want to hide it initially when the screen first loads by offsetting the y coordinate to be the screen size + the height of descriptionView itself:
However, in my controller, none of my frame changes do anything:
override func viewDidLoad() {
super.viewDidLoad()
...
// descriptionView.frame.origin.y = self.view.frame.height
// UIView.animateWithDuration(1, animations: {
// self.descriptionView.frame.origin.y = self.view.frame.height
// self.view.layoutIfNeeded()
// })
//
print("xxx")
descriptionView.frame = CGRectMake(0, self.view.frame.height, self.view.frame.width, 66)
// descriptionView.frame = CGRectOffset(descriptionView.frame, 0, descriptionView.frame.height)
}
No matter what I do it seems fixed at that visible position like in my storyboard. Can someone help?
In IB you are using NSAutoLayout, so you either need to manipulate the constraints, or tell the view to translate the mask to constraints.
If you want to set the frame directly then you will want to do this:
descriptionView.translatesAutoresizingMaskIntoConstraints = true
descriptionView.frame = CGRectMake(...)
Otherwise you can create IBOutlets to the height and width constraint from IB and update those:
self.descriptionViewHeight.constant = self.view.frame.width
Additionally, I would recommend doing frame manipulations inside of viewWillAppear: rather than viewDidLoad. viewDidLoad does not strictly guarantee final position.
Instead of editing the frame or descriptionView, edit its height constraint.
First, create an NSLayoutConstraint from this constraint by cmd-dragging the height constraint from the Interface Builder to your class (like you do for any UI object).
Then you can set the constant property of this constraint to 0.
(Yes, constant is declared as a varproperty...)
If you are using constraints, chaging the frame view myView.frame property will not affect on view actual position and size. Instead of this make constraint outlet and change it in your code, it will look like this:
descriptionView.heightConstraint.constant = 100
Also, if you want to hide something, you can use property myView.hidden = true, or myView.alpha = 0;
I trying to get a custom transition between two View Controllers. First, here's a picture to illustrate what I want :
I want a UICollectionViewCell to expand to the whole screen. In this cell, the subviews are placed with Autolayout in IB.
I just want each subview to go to the new position. So I tried subview.frame = newSubview.frame in the animation block, but it doesn't work (because of Autolayout I think).
I thought to delete the constraints while the animation is occuring, but it doesn't work.
I also tried to make #IBOutlets of the constraints and to change constant property.
Here's my code :
let detailView = detailViewController.view
let cellView = self.selectedCell
container.addSubview(cellView!)
let duration = self.transitionDuration(transitionContext)
UIView.animateWithDuration(duration, delay: 0.0, options: .CurveEaseInOut, animations: {
let newFrame = detailViewController.view.frame
cellView!.frame = newFrame
cellView!.imageView.frame = newFrame
cellView!.labelTopConstraint.constant = 27
cellView!.labelRightConstraint.constant = 8
cellView!.layoutIfNeeded()
}
...
Actually, when the animation begins the labels snap to a position, then they move and at the end they are not at the right position...
Any ideas ? Thanks
Check out this repo, I think this is what you are looking for BCMagicTransition: https://github.com/boycechang/BCMagicTransition
The issue with your code is that you aren't converting the frames of your subviews of your collection view cell that you want to animate to the coordinates of the container view controller of the transition, and same with the final frames for their positions. Even though you are using AutoLayout you can still manipulate views with their frames.
To accomplish this, checkout this method (documentation):
func convertRect(rect: CGRect, fromView view: UIView?) -> CGRect
You also should look into animating snapshots of the views you wish to animate. Then just add the snapshots to the container view, and animate them to the converted final frames, then remove them when the animation is complete.
Hope this helps !