Up and down image view and vice versa Swift/iOS - ios

For my personal project, I try to make an image of a leaf go up for 5 seconds and then lower it for 5 seconds.
It works well, only after raising and lowering the sheet, it changes position and is transported well to the bottom of the screen.
I would like it to go up from the place of the end of the descent. I have tried several things but nothing works. I wish I could restore the original position after descent but I think it will make the image flicker on the screen.
Any ideas ?
func upLeaf(){
let xPosition = imageLeaf.frame.origin.x
let yPosition = imageLeaf.frame.origin.y - 100 // Slide Up - 20px
let width = imageLeaf.frame.size.width
let height = imageLeaf.frame.size.height
UIView.animate(withDuration: 5.0, animations: {
self.imageLeaf.frame = CGRect(x: xPosition, y: yPosition, width: width, height: height)
})
}
func downLeaf(){
let xPosition = imageLeaf.frame.origin.x
let yPosition = imageLeaf.frame.origin.y + 100 // Slide Up - 20px
let width = imageLeaf.frame.size.width
let height = imageLeaf.frame.size.height
UIView.animate(withDuration: 5.0, animations: {
self.imageLeaf.frame = CGRect(x: xPosition, y: yPosition, width: width, height: height)
})
secondUp = true
}

Instead of using frame to perform the animation you could use CGAffineTransform. It's more powerful and equally easy:
UIView.animate(withDuration: 5.0, animations: {
line3.transform = CGAffineTransform(translationX: 0, y: 100)
})
When you want to return to the initial state you should type:
UIView.animate(withDuration: 5.0, animations: {
line3.transform = CGAffineTransform.identity
})
CGAffineTransform remembers the initial parameters for your view, so that you don't have to do any calculations yourself.

If you only want the leaf to go up and down, you can just animate imageLeaf's frame.origin.y (no need to keep track of origin.x, width, or height).
func upDownLeaf() {
UIView.animate(withDuration: 5.0, animations: {
self.imageLeaf.frame.origin.y -= 100 /// go up
}) { _ in
UIView.animate(withDuration: 5.0, animations: {
self.imageLeaf.frame.origin.y += 100 /// go down
})
}
}

Related

Swift || Bug when Animating and Removing Subview from Superview

Im made and DropDown Menu to select Action like the GIF below.
Therefore I made a Subview and animated it in.
When animating the Subview out, it looks really weird.
In particular the problem is that it just looks like a blank small bar and not like the Menu.
Does anyone know where the problem might be?
The ViewController I'm making the Subview of is a simple ViewController with a TableView inside and 1 prototype cell.
Code:
let blackView = UIView()
var tvx: OptionsVC = OptionsVC()
var h: CGFloat!
.
func optionsClicked() {
self.h = CGFloat(70 + (52 * (OptionsVC().arrayFunctionCellNames.count)))
navigationController?.hidesBarsOnTap = false
tvx = self.storyboard?.instantiateViewController(withIdentifier: "options") as! OptionsVC
if let window = UIApplication.shared.keyWindow {
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
blackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss)))
view.addSubview(blackView)
view.addSubview(tvx.view)
let y = (self.navigationController?.navigationBar.frame.size.height)! + UIApplication.shared.statusBarFrame.height
tvx.view.frame = CGRect(x: 0, y: (y - self.h), width: view.frame.width, height: h)
blackView.frame = CGRect(x: 0, y: y, width: view.frame.width, height: view.frame.height)
blackView.alpha = 0
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.blackView.alpha = 1
self.tvx.view.frame = CGRect(x: 0, y: y, width: self.view.frame.width, height: self.h)
self.blackView.frame = CGRect(x: 0, y: (y + self.h), width: self.view.frame.width, height: self.view.frame.height)
}, completion: nil)
}
}
.
func handleDismiss() {
UIView.animate(withDuration: 5.5, animations: {
let y = (self.navigationController?.navigationBar.frame.size.height)! + UIApplication.shared.statusBarFrame.height
self.blackView.alpha = 0
if let window = UIApplication.shared.keyWindow {
self.blackView.frame = CGRect(x: 0, y: y, width: self.view.frame.width, height: self.view.frame.height)
self.tvx.view.frame = CGRect(x: 0, y: (y - self.h), width: self.view.frame.width, height: self.h)
}
}, completion: {(finished:Bool) in
self.blackView.removeFromSuperview()
self.tvx.view.removeFromSuperview()
self.navigationController?.hidesBarsOnTap = true
})
}
GIF of BUG:
I made it so slow so you can better see it
Edit: My Solution
The problem was the constraints I set on my subview controller. Normally you set them to all sides, in my case, with was really weird I had to set them only to the bottom and sides. If I set some to the top, it would always do this bug.
You haven't really provided enough information for us to understand what's going on. What are tvx, blackView and self? What is the self.h variable? (From the animation it looks like you're changing the height of your "menu" view to be much shorter before you begin the animation code.)
Do you have a view controller contained inside another one using an embed segue?
If so, you should probably animate the constraints on the container view, not the child view controller's view.
As Glenn and D. Greg say in their comments, you should really be adding constraints to your views, hooking up outlets to those constraints, and animating changes to the constraint's constants rather than manipulating your view's frames directly. Animating changes to your view's frames isn't reliable when you're using AutoLayout, since AutoLayout can change your view's size and position out from under your animation code. That code looks like this, (in broad terms, no specific to your code)
myViewAConstraint.constant = someNewValue
myViewBConstraint.constant = someOtherValue
UIView.animate(withDuration: 5.5,
animations: {
someView.alpha = 0 //If you want the view to fade as it animates
self.view.layoutIfNeeded()
},
completion: { (finished:Bool) in
}
)

SWIFT: Animation changes speeds, which I don't want

I'm trying to create a Star Wars style scrolling text for a viewController.
I have the following code:
override func viewDidAppear(_ animated: Bool) {
UIView.animate(withDuration: 60.0, animations: {
let width = self.scrollingTextLabel.frame.width
let height = self.scrollingTextLabel.frame.height
self.scrollingTextLabel.frame = CGRect(x: 5, y: (180 - height), width: width, height: height)
}, completion:{ _ in
self.buttonTextLabel.text = "Play"
})
}
It works perfectly except for 1 thing, it speeds up, then slows down. This means, it's hard to read at the mid point. Is there a way to make the speed constant?
Pass in .curveLinear in the animation options to make the animation have a constant velocity:
UIView.animate(withDuration: 60.0, delay: 0.0, options: .curveLinear, animations: {
let width = self.scrollingTextLabel.frame.width
let height = self.scrollingTextLabel.frame.height
self.scrollingTextLabel.frame = CGRect(x: 5, y: (180 - height), width: width, height: height)
}, completion:{ _ in
self.buttonTextLabel.text = "Play"
})

UIView transform animations not being called

Currently there is a view I'm trying to translate(move), rotate and scale a view at the same time. For so strange reason it's only scaling it when I put scale at the bottom. But when I change the order and put the scaling first it rotates and translates the view properly but the scale of the view changes briefly to its correct scale before changing back to its original size. I need it to stay in it's scaled form. Here is the code:
class ViewController: UIViewController {
let newView = UIView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(newView)
UIView.animate(withDuration: 1.0, animations: {
self.newView.transform = CGAffineTransform(translationX: 50, y: 70)//translation
self.newView.transform = CGAffineTransform(rotationAngle: -CGFloat.pi / 2)//rotation
self.newView.transform = CGAffineTransform(scaleX: 1, y: 0.5)//scale
})
}
}
Try combine the transforms first, then apply them at a time:
let translate = CGAffineTransform(translationX: 50, y: 70)
UIView.animate(withDuration: 1.0, animations: {
self.view.transform = translate.rotated(by: -CGFloat.pi / 2).scaledBy(x: 1, y: 0.5)
})

How to scale a UIView from left to right and back?

I want to have an UIView to appear by scaling from left to right if I hit a button. If I hit the button again, the view should scale away from right to left.
I found an explanation how to do it for left to right, but even this solution is working only once. If I hide the view and play the animation again it scales it from the centre again.
Also scaling it back from right to left doesn't work either as it disappears immdiatly without any animation.
Here's my code so far:
if(!addQuestionaryView.isHidden)
{
//Reset input if view is hidden
addQuestionaryInput.text = ""
self.addQuestionaryView.isHidden = false
let frame = addQuestionaryView.frame;
let rect = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.width, height: frame.height)
let leftCenter = CGPoint(x: rect.minX, y: rect.minY)
addQuestionaryView.layer.anchorPoint = CGPoint(x:1,y: 0.5)
addQuestionaryView.layer.position = leftCenter
UIView.animate(withDuration: 0.6,
animations: {
self.addQuestionaryView.transform = CGAffineTransform(scaleX: 0, y: 1)
},
completion: { _ in
self.addQuestionaryView.isHidden = true
})
}
else
{
self.addQuestionaryView.isHidden = false
let frame = addQuestionaryView.frame;
let rect = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.width, height: frame.height)
let leftCenter = CGPoint(x: rect.minX, y: rect.midY)
addQuestionaryView.layer.anchorPoint = CGPoint(x:0,y: 0.5);
addQuestionaryView.layer.position = leftCenter
addQuestionaryView.transform = CGAffineTransform(scaleX: 0, y: 1)
UIView.animate(withDuration: 0.6,
animations: {
self.addQuestionaryView.transform = CGAffineTransform(scaleX: 1, y: 1)
},
completion: nil)
}
As said the appearing part works once and then starts from the centre again. The disappearing part doesn't work at all.
I don't know what "to scale it so it has a little look like Android reveal animations" means (and I hope I never do; I've never seen an Android phone, I never hope to see one).
But do you mean something like this? This is done by animating a mask in front of the view, thus revealing and then hiding it:
Here's the code used in that example:
class MyMask : UIView {
override func draw(_ rect: CGRect) {
let r = CGRect(x: 0, y: 0, width: rect.width/2.0, height: rect.height)
UIColor.black.setFill()
UIGraphicsGetCurrentContext()?.fill(r)
}
}
class ViewController: UIViewController {
#IBOutlet weak var lab: UILabel!
var labMaskOrigin = CGPoint.zero
var didAddMask = false
override func viewDidLayoutSubviews() {
if didAddMask {return}
didAddMask = true
let mask = MyMask()
mask.isOpaque = false
let w = self.lab.bounds.width
let h = self.lab.bounds.height
mask.frame = CGRect(x: -w, y: 0, width: w*2, height: h)
self.lab.mask = mask
self.labMaskOrigin = mask.frame.origin
}
var labVisible = false
func toggleLabVisibility() {
labVisible = !labVisible
UIView.animate(withDuration: 5) {
self.lab.mask!.frame.origin = self.labVisible ?
.zero : self.labMaskOrigin
}
}
}
Remove this:
addQuestionaryView.layer.anchorPoint = CGPoint(x:0,y: 0.5);
addQuestionaryView.layer.position = leftCenter
addQuestionaryView.transform = CGAffineTransform(scaleX: 0, y: 1)
Then in your animate with duration block use the code below instead of CGAfflineTransform
addQuestionaryView.frame = CGRect(0,0,500,100)
Replace 500 width whatever width you want it to be.
Ans to close it back up do this:
addQuestionaryView.frame = CGRect(0,0,0,100);

How can I resize a view, while proportionally resizing it's subviews?

I'm trying to create a custom segue, where the first view controller shrinks a little and the second one slides over top of it. Here's what I have for segue implementation:
override func perform() {
let firstView = self.sourceViewController.view as UIView!
let secondView = self.destinationViewController.view as UIView!
let screenSize = UIScreen.mainScreen().bounds.size
let window = UIApplication.sharedApplication().keyWindow
secondView.frame = CGRectMake(0.0, screenSize.height, screenSize.width, screenSize.height)
window?.insertSubview(secondView, aboveSubview: firstView)
UIView.animateWithDuration(2, delay: 0, options: .CurveEaseIn, animations: {
// BEGIN RELEVANT PORTION
let offset: CGFloat = 20
firstView.frame = CGRectMake(offset, offset, screenSize.width - (offset * 2), screenSize.height - (offset * 2))
firstView.autoresizesSubviews = true
firstView.layoutIfNeeded()
// END RELEVANT PORTION
}, completion: nil)
UIView.animateWithDuration(5, delay: 1, options: .CurveEaseOut, animations: {
secondView.frame = CGRectMake(0.0, 0.0, screenSize.width, screenSize.height)
}, completion: nil)
}
This produces a result like this:
This is already really great, but not what I intended. I need the subviews ("Hi, I'm Matt." label and button) to resize proportionally to the superview, as if the entire view were falling down on the z axis.
I am using Auto Layout.
Thank you so much in advance!
I solved the problem using CGAffineTransform.
override func perform() {
let firstView = self.sourceViewController.view as UIView!
let secondView = self.destinationViewController.view as UIView!
let screenSize = UIScreen.mainScreen().bounds.size
let window = UIApplication.sharedApplication().keyWindow
secondView.frame = CGRectMake(0.0, screenSize.height, screenSize.width, screenSize.height)
window?.insertSubview(secondView, aboveSubview: firstView)
firstView.layer.anchorPoint = CGPoint(x: 0.0, y: 0.0)
firstView.layer.position = CGPoint(x: 0.0, y: 0.0)
UIView.animateWithDuration(0.2, delay: 0, options: .CurveEaseInOut, animations: {
print(firstView.frame)
let offset: CGFloat = 25
let offsetX = 2 * (offset / firstView.frame.width)
let offsetY = 2 * (offset * (firstView.frame.height / firstView.frame.width) / firstView.frame.height)
let transform = CGAffineTransformScale(CGAffineTransformIdentity, 1 - offsetX, 1 - offsetY)
firstView.transform = transform
firstView.layer.position = CGPoint(x: offset, y: offset)
}, completion: nil)
UIView.animateWithDuration(0.5, delay: 0.05, options: .CurveEaseOut, animations: {
secondView.frame = CGRectMake(0.0, 0.0, screenSize.width, screenSize.height)
}) { finished -> Void in
self.sourceViewController.presentViewController(self.destinationViewController, animated: false, completion: nil)
}
}
Thanks for your help anyway, everyone!
Applying transform is a good view to achieve it.
There's another way with which you can achieve it with no risk of modifying the first viewcontroller's view's frame. Take a snapshot of the first viewcontroller's view and use it instead of the view itself. You can use the following method in UIView to take a snapshot:
- (UIView *)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates;
I tried it using your Scenario in my Xcode now you have to follow these steps
1- Give UIButton & UILabel Leading and Trailing Space to their SuperView
2- Make UIButton central Vertically
3- Give Vertical Space from UIButton to UILabel
Important:
Hope it will work because then trick is that you have to give leading & trailing space to super View to make the InnerViews Shrink or expand w.r.t its superView
One way around can be:
Give both of them(Label and button) width constraints.
Make outlets of both width constraints. constraintWidthLabel, constraintWidthButton
You can make outlet of a constraint like this
When you perform segue you have to make these width constraints to zero with animation.
UIView.animateWithDuration(2, delay: 0, options: .CurveEaseIn, animations: {
// BEGIN RELEVANT PORTION
let offset: CGFloat = 20
constraintWidthLabel.constant = 0
constraintWidthButton.constant = 0;
firstView.frame = CGRectMake(offset, offset, screenSize.width - (offset * 2), screenSize.height - (offset * 2))
firstView.autoresizesSubviews = true
firstView.layoutIfNeeded()
// END RELEVANT PORTION
}, completion: nil)

Resources