How to implement such pop over view animation? - ios

I was using this ride-sharing ios app and found this pop over animation of options view. I wanted to implement similar pop over but could figure out if it is a custom transition or animation? Here is a link to the GIF of popover in application.
It will be helpful to link me to examples/tutorial/code with similar animation so that I can begin implementing on my ios app.

This is pretty simple. Just position your options view on the screen where you want it to appear and set the alpha to 0 so it is hidden. Then, prior to animation, make the view wider using scaleX, and translate it down using translationY. Then simply animate it back to .identity and animate the alpha back to 1.0 so it fades in. A basic example is below. When you dismiss the view you just do the opposite. Let me know if you need help with that.
yourView.transform = CGAffineTransform(scaleX: 1.3, y: 0)
yourView.transform = CGAffineTransform(translationX: 0.0, y: 200.0)
yourView.alpha = 0
UIView.animate(withDuration: 0.3, animations: {
yourView.transform = .identity
yourView.alpha = 1.0
})

I achieved the following animation using leading, trailing and button constraints. Please let me know if such animation can be done in other ways.
yourview.alpha = 0
UIView.animate(withDuration: 0.6, animations: {
self.leadingConstraints.constant = 20
self.trailConstraints.constant = -20
self.buttonConstraints.constant = 20
self.yourview.alpha = 1
self.yourview.layoutIfNeeded()
})

Related

iOS: jumping uiview when rotating via CATransform3DRotate

I try to rotate a uiview (height & width of 100) with an angle of 90 degrees with an own anchor point. After changing the anchor point I translate my view so that both changes cancel each other out.
When I rotate the view with (Double.pi) it animates as expected but when I change to (Double.pi/2) the view is jumping up.
testView.layer.anchorPoint = CGPoint(x: 0.5, y: 1)
testView.layer.transform = CATransform3DMakeTranslation(0, -50, 0)
UIView.animate(withDuration: 2.3, delay: 0, options: .curveLinear, animations: { () -> Void in
var allTransofrom = self.testView.layer.transform
var c3d = CATransform3DIdentity
c3d.m34 = 2.5 / (2000)
let rot = CATransform3DRotate(c3d, CGFloat(Double.pi/2), 1, 0, 0)
allTransofrom = CATransform3DConcat(allTransofrom, rot)
self.testView.layer.transform = allTransofrom
}, completion: {(finished) in
} )
full code here
I was able to found a solution by myself but
I still don't know where the jump is coming from.
Instead of doing a translation via CGAffineTransform.translatedBy or CATransform3DMakeTranslation I simply adapted my NSLayoutConstraint.
testViewAnchors[0].constant = testViewAnchors[0].constant + CGFloat(50)
In my case an animation for the "translation" wasn't necessary but as it is possible to animate NSLayoutConstraints it shouldn't be a problem.

ios jerky animation when applying scaling transformation

I have this icon that I want the user to be able to move around and when it enters a specific area of its parent view, it should scale up in size to indicate that. I retrieve where the user wants to move the icon with a pan gesture recognizer and the command:
let translationPoint = sender.translation(in: view)
Then I try to animate the desired behavior with CGAffineTranformations with the following code:
let moveTransformation = CGAffineTransform(translationX: response.translationPoint.x, y: response.translationPoint.y)
var scaleTransformation: CGAffineTransform = CGAffineTransform(scaleX: 1, y: 1)
if response.shouldScaleUp {
scaleTransformation = CGAffineTransform(scaleX: 1.3, y: 1.3)
}
let transformation = scaleTransformation.concatenating(moveTransformation)
And then I apply the transformation on the icon view. It works quite well except for the fact that it jerks a bit when I enter and exit the area that should trigger this behavior.
I've read online that it's in general a bad idea to be applying two transformations, and I thought that maybe I should just update the actual frame of the view itself, but animating transformation changes makes it easier to reset the position when the user lets go (I've also heard it's meant to be lighter to do).
You guys and girls have any suggestions? Thanks for your help
UPDATE
My animation code:
DispatchQueue.main.async {
UIView.animate(withDuration: 0.15) {
self.iconView.transform = transformation
}
}
There should be no problem applying multiple transforms. Without seeing more of your code(Animation Block or Network Call for response) I am going to make two guesses
1) You are missing your UIView animation block.
let translationPoint = sender.translation(in: view)
let moveTransformation = CGAffineTransform(translationX: response.translationPoint.x, y: response.translationPoint.y)
var scaleTransformation: CGAffineTransform = .identity
if response.shouldScaleUp {
scaleTransformation = CGAffineTransform(scaleX: 1.3, y: 1.3)
}
let transformation = scaleTransformation.concatenating(moveTransformation)
DispatchQueue.main.async {
UIView.animate(withDuration: 0.2) {
iconView.transform = scaleTransformation
}
}
2) More likely or combined with the problem above you are trying to change the UI on a background thread from the network call. This could create delays and jerkiness and would need to be wrapped in a main thread call like above animation.
let translationPoint = sender.translation(in: view)
let moveTransformation = CGAffineTransform(translationX: response.translationPoint.x, y: response.translationPoint.y)
var scaleTransformation: CGAffineTransform = .identity
if response.shouldScaleUp {
scaleTransformation = CGAffineTransform(scaleX: 1.3, y: 1.3)
}
let transformation = scaleTransformation.concatenating(moveTransformation)
DispatchQueue.main.async {
iconView.transform = scaleTransformation
}
Nest the icon view in a parent view. Apply the position translation to the parent view and the scale to the child view.
This keeps the transforms separate and the result more easily predictable.

UIView animation with autolayout and child views

I have a weird problem that seems to be fairly easy to solve. I think I could use some workaround to be able to have the behavior that I want but I want to know if there is a better way to do it.
I have a view called contentView (blue in the image) that it will expand its height using a UIView.animation, this is no problem at all and works as expected.
The problem here is that this view has a child component (a button) that has an autolayout constraint to its bottom equal to 22 like this:
This is the autolayout constraint:
If I do the height resize without the animation it works fine, the view height change and the button are always 22 points of the bottom of the contentView. But when I use an animation to make this change more smoothy and user-friendly the button moves to the end position before the animation even start.
I want to know how I could achieve a smooth animation here but with the button moving along its parent view
The part of the code that handles the animation is pretty straightforward but I'll post it in here:
#IBAction func openDetail(_ sender: ExpandCourseDetail) {
let rotation = sender.getOpen() ? CGAffineTransform.identity : CGAffineTransform(rotationAngle: CGFloat.pi)
UIView.animate(withDuration: 0.5, delay: 0.1, options: [.curveEaseInOut], animations: {
sender.transform = rotation
}, completion: {
success in
sender.setOpen(!sender.getOpen())
})
UIView.animate(withDuration: 1.0, delay: 0.5, options: [.curveEaseInOut], animations: {
self.contentView.frame.size.height = sender.getOpen() ? self.contentView.frame.height - 300 : self.contentView.frame.height + 300
}, completion: nil)
}
As a side note, the button itself has an animation that rotates the button 180 degrees to show the user that the view is expanding.
Thank you so much for your help.
It's super easy with constraints, just create a superView height constraint IBOutlet and change its constant value.
#IBAction func btnPressed(_ sender: UIButton) {
self.toggleButton.isSelected = !sender.isSelected
//Animation starts here
self.view.layoutIfNeeded()
UIView.animate(withDuration: 0.7) {
if self.toggleButton.isSelected {
//transform button
self.toggleButton.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi))
//change your ParentView height to desired one
self.constContentViewHeight.constant = self.view.frame.size.height - 220
self.view.layoutIfNeeded()
} else {
self.toggleButton.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi*2))
// Set height constraint to original value
self.constContentViewHeight.constant = 250
self.view.layoutIfNeeded()
}
}
}
I have created a demo, check it out.
The issue you are facing is due to two animation blocks. So I have changed some lines and put both button transformation and height animation into one animation block.
func openDetail(_ sender: ExpandCourseDetail) {
let isOpen = sender.getOpen() ? CGAffineTransform.identity : CGAffineTransform(rotationAngle: CGFloat.pi)
UIView.animate(withDuration: 1.0, delay: 0.5, options: [.curveEaseInOut], animations: {
sender.transform = rotation
self.contentView.frame.size.height = self.contentView.frame.height + (isOpen ? 300 : -300)
}, completion: { (success) in
sender.setOpen(!isOpen)
})
}

How to show a growing sub view over a parent view

I have a view as follows
The colored tiles are basically views. When i long press on the tiled views i am creating a copy of the tile in which it was long pressed. Also when it is long pressed there should be a view as per the following image.
The drop area should come up from bottom part or it should grow from bottom insect of the screen.
When long pressing on the tiled view i am creating a copy of the view and i am able to make the copy successfully and drag on the screen, but i am stuck at creation of this drop area and its animation.
Can someone guide me how to implement this....
Thanks in advance...
let dropView = UIView(frame: CGRect(x:0, y:0, width:300, height:100))
dropView.frame.origin = CGPoint(x: 0, y: self.view.frame.size.height)
dropView.center = CGPoint(x:self.view.frame.size.width/2, y:dropView.center.y)
dropView.backgroundColor = UIColor.blue
self.view.addSubview(dropView)
When you want to animate it to show up use this:
UIView.animate(withDuration: 0.5, delay: 0.0, options: [.repeat, .curveEaseOut, .autoreverse], animations: {
dropView.frame.origin = CGPoint(x:dropView.frame.origin.x, y:dropView.frame.origin.y - dropView.frame.size.width - 10)
}, completion: nil)

How to control the speed of UIView animation?

I am implementing a rotating digit view like this:
https://github.com/jonathantribouharet/JTNumberScrollAnimatedView
But, instead of scrolling from say 1 to 9, I want the animation to continue scrolling (1 to 9 then back to 1 to 9), until I manually stop it.
I implement this by adding UILabel subviews 1-9, vertically stacked, and add an extra label for number 1 at the bottom. When rotating to the bottom (showing the bottom label 1), in the animation completion closure, I set the transform back to origin, showing top label 1, and restart rotating animation.
This works fine, but since each rotation (1-9-1) is separate, the animation from 1-9 accelerates and then stop at 1, and pick up speed at the next iteration. This is not what I want, I want the rotation to maintain a constant speed between the iterations.
How can I do this with UIView animation?
Here is the code:
public func startAnimation(){
UIView.setAnimationCurve(UIViewAnimationCurve.easeIn)
UIView.animate(withDuration: TimeInterval(self.loopDuration), animations: { [unowned self] in
self.container!.transform = CGAffineTransform(translationX: 0, y: CGFloat(-(self.endNumber-self.startNumber+1)*(self.size)))
}) { [unowned self] (completed) in
if self.isAnimationStopped {
self.container!.transform = CGAffineTransform(translationX: 0, y: 0)
UIView.setAnimationCurve(UIViewAnimationCurve.easeOut)
UIView.animate(withDuration: TimeInterval(self.loopDuration), animations: {
self.container!.transform = CGAffineTransform(translationX: 0, y: CGFloat(-self.targetY))
})
} else {
self.container!.transform = CGAffineTransform(translationX: 0, y: 0)
self.startAnimation()
}
}
}
By default UIView animation uses ease-in, ease-out animation timing, where the animation accelerates smoothly, runs, then decelerates to a stop.
You need to use a longer form of UIView.animate, animate(withDuration:delay:options:animations:completion:) that takes an options parameter, and specify a timing curve of .curveLinear.
Just specify 0.0 for the delay and options: .curveLinear
Edit:
Note that if you need to specify multiple options, like .curveLinear and .allowUserInteraction you would use OptionSet syntax, like options: [.curveLinear, .allowUserInteraction]

Resources