"Magic Move" Effect Custom Transition - ios

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 !

Related

Swift - Animate only specific constraints

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

How to animate a UIImage constraints in Swift 4

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
}

UIView frame animation not updating subviews layout - UIViewPropertyAnimator

I am trying to to a simple frame animation with UIViewPropertyAnimator. If I animate the frame, it works great.
The problem arrives when you add subviews with autolayout constraints. Subviews frames are calculated at the beginning of the animation and changed immediately instead of being done as the animation progresses.
Any idea on how to animate subViews as the view frame changes?
let frameChange = UIViewPropertyAnimator(duration: duration, curve: .easeInOut) {
animatableCustomView.frame = finalFrame
}
frameChange.startAnimation()
My custom view is just a view with an ImageView inside constrained to all 4 edges to its superview.

CABasicAnimation not reliable in UIStackView?

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!

Animate subview on UIViewController view

I have UITableView as a subview over UIView (parent view) of UIViewController. When i'm animating my parent view as below:
UIView.animateWithDuration(duration, animations: {
viewController?.view.frame = CGRectMake(0,0,320,568)
}, completion: {_ in
})
I observed that my subview (table view) is not animating. I tried setting Autoresizing Mask of subviews to flexibleWidth and flexibleHeight, but din't get any success. Anybody having any idea why its happening.
If you are using auto layout (which is the default in iOS 8/9 on Xcode 6/7) you need to change the constraints of your view inside he UIView animation closure, instead of changing the frame.
If you are not using auto layout, then you can update the frame as above, however, I'd imagine you are. Thus you need to call layoutSubviews, then programatically set new constraints that represent your desired change to the layout, and finally call layoutSubviews again, inside the animation closure.
Example:
func AnimateBackgroundHeight() {
self.view.layoutSubviews()
UIView.animateWithDuration(0.5, animations: {
self.heightCon.constant = 600 // heightCon is the IBOutlet to the constraint
self.view.layoutSubviews()
})
}

Resources