Swift || Bug when Animating and Removing Subview from Superview - ios

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

Related

Up and down image view and vice versa Swift/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
})
}
}

How to embed a UILabel inside a UIView in swift

I need to create a UIView programmaticaly in swift, that view should contain an image and a label. Also I need to embed that label and the imageview inside that created view so that as I animate the constraints of the view, the label and the image shold also reposition itseld relative to the view. This is my code co far -
func banner(viewController:UIViewController){
let width = UIScreen.main.bounds.width
let dynamicView=UIView(frame: CGRect(x:0,y: -70, width:width, height:70))
let label = UILabel(frame: CGRect(x:0,y: 35, width:width, height:20))
dynamicView.backgroundColor=UIColor.green
label.center = CGPoint(x: width/2, y: 35)
label.textAlignment = .center
label.text = "I'am a test label"
UIView.animate(withDuration: 1) {
dynamicView.frame = dynamicView.frame.offsetBy( dx: 0, dy: 70 )
viewController.view.addSubview(DynamicView)
viewController.view.addSubview(label)
}
UIView.animate(withDuration: 1, delay: 3, usingSpringWithDamping: 1, initialSpringVelocity: 4, options: .curveEaseIn, animations: {
dynamicView.frame = DynamicView.frame.offsetBy( dx: 0, dy: -70 )
}, completion: nil)
}
}
I could achieve that by changing x and y position of each views,labels etc separately. But that code would not be clean and consise.
From the question, it is seen that you are adding the label as subview to the viewController's view. Since you wish to embed the label to the Dynamic view try adding the label to Dynamic view like
UIView.animate(withDuration: 1) {
dynamicView.frame = dynamicView.frame.offsetBy( dx: 0, dy: 70 )
viewController.view.addSubview(DynamicView)
dynamicView.addSubview(label)
}

Make UIImage appear from left to right

I'm trying to make a cool launch screen but it's a little bit hard. I would like to make my image appear the way this appears:
what I would like
But for the moment the only animation I succeeded to do is this:
what I've made
The image I use is a PNG with transparent border and the yellow drawing.
The code I use to have the animation I have
( "yellowSide" is actually the name of the outlet from the imageView that hold my image with the yellow drawing):
func animation() {
yellowSide.frame = CGRect(x: yellowSide.frame.origin.x, y: yellowSide.frame.origin.y, width: 0, height: 47)
yellowSide.alpha = 0
UIView.animate(withDuration: 0.5, delay: 2.0, options: .curveEaseIn, animations: {
var yellowSideFrame = self.yellowSide.frame
self.yellowSide.frame = CGRect(x: yellowSideFrame.origin.x, y: yellowSideFrame.origin.y, width: 243, height: 47)
self.yellowSide.alpha = 1
}, completion: nil)
}
As you guess the width / value I enter are the value that I need to have so the image looks the way I want when it's fully appeared.
You are changing the frame but you are not clipping out of frame image . you need to do like below .
// in ViewDidLoad method
yellowSide.clipsToBounds=true
func animation() {
yellowSide.frame = CGRect(x: yellowSide.frame.origin.x, y: yellowSide.frame.origin.y, width: 0, height: 47)
yellowSide.alpha = 0
UIView.animate(withDuration: 0.5, delay: 2.0, options: .curveEaseIn, animations: {
var yellowSideFrame = self.yellowSide.frame
self.yellowSide.frame = CGRect(x: yellowSideFrame.origin.x, y: yellowSideFrame.origin.y, width: 243, height: 47)
self.yellowSide.alpha = 1
}, completion: nil)
}

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

SWIFT slide one Object "in and out"

Is there a way to animate an object in swift (UIView, UIButton, etc.) so that it slides out on the left side of the screen and slides in at the same time on the right side of the screen, that the parts of f.e. an UIView that already slided out on the left side are already sliding in again from the right side.
I hope you can understand my question, its a bit tricky to explain this problem.
I'm using Xcode + Swift 3.0.
Thank you.
that the parts of f.e. an UIView that already slided out on the left side are already sliding in again from the right side
This almost slipped by me. The only way you have one "view" appear in two places on a screen is by really having two views, one on the left and one on the right.
Then, you could set the widthAnchors (properly), hide the subviews (properly), and animate. If the animation is fast enough (my one-sided slide out is set for 0.3 seconds) the user would think it's all one view.
You can use UIView.animate(). Trigger a second animation after the first has completed.
Possible implementation to get the idea:
var container = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 200.0, height: 200.0))
let view = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 50.0, height: 50.0))
view.center = CGPoint(x: 100, y: 100)
view.backgroundColor = UIColor.green
container.addSubview(view)
let screenWidth = CGFloat(356.0)
// animate to left
UIView.animate(withDuration: 2.0, delay: 0.0, options: [], animations: {
view.center = CGPoint(x: -view.frame.size.width, y: view.frame.origin.y)
}, completion: { (finished: Bool) in
//set view to right of screen
view.center = CGPoint(x: view.frame.size.width + screenWidth, y: view.frame.origin.y)
// animate back to center
UIView.animate(withDuration: 2.0, delay: 0.0, options: [], animations: {
view.center = CGPoint(x: 100, y: 100)
})
})

Resources