UIView.animate don't work if i update a UItextView - Swift - ios

I'm new in Swift programming. I have a problem with an animation of a view when i try to change the text of a UITextView.
This is a method that is triggered from a button that is in the main view. self.parkingMenuTittle is a UITextView that i also have in the main view. When the button is triggered I want to change the text and set the view height to the ycomponent variable that my code has, but the view instead of doing that, goes to the original size. If I don't update the UITextView text my animation works just fine.
#objc func seeMore(_ sender: UIButton){
self.parkingMenuTittle.text = "test"
UIView.animate(
withDuration: 0.2,
animations: {
let frame = self.view.frame
let ycomponent = UIScreen.main.bounds.height-300
self.view.frame = CGRect(x: 0, y: ycomponent, width: frame.width, height: frame.height)
},completion: { (finished: Bool) in
//Code
})
}
I also tried to put the text in the completion block but don't work.
Thanks for your help!

Related

How to use completion block for UIView.animate()?

I'm working on a project to learn animations and am having trouble using the completion block for the UIView.animate(withDuration:) func. MY animation is a shoebox that falls from the top of the screen, lands on a pedestal, then opens. Once the box opens I want a UIImageView to come out of the box, grow to full size of the screen and then segue to the next page, but the completion handler that the code for segueing is called before my animation completes and the UIImageView doesn't appear at all.
Here's my code:
override func viewDidAppear(_ animated: Bool) {
UIView.animate(withDuration: 1.5, delay: 0.0, options: .curveEaseInOut,
animations: {
//Opening the box
self.shoeBoxImage.shoeBox.animationImages = self.boxOpeningAnimation
self.shoeBoxImage.shoeBox.animationDuration = 1.5
self.shoeBoxImage.shoeBox.animationRepeatCount = 1
self.shoeBoxImage.shoeBox.contentMode = .scaleAspectFill
self.shoeBoxImage.shoeBox.startAnimating()
//set to the final image
self.shoeBoxImage.shoeBox.image = UIImage(named: "frame13")
},completion: {_ in
let nextPage = UIImageView()
nextPage.frame = CGRect(origin: self.shoeBoxImage.center, size: CGSize(width: 0.0, height: 0.0))
nextPage.image = UIImage(named: "FirstLoadBackgroundImg.jpeg")
nextPage.autoresizesSubviews = true
self.view.addSubview(nextPage)
self.view.bringSubviewToFront(nextPage)
UIView.animate(withDuration: 5.0, animations: {
nextPage.transform = CGAffineTransform(scaleX: 428, y: 926)
})
self.performSegue(withIdentifier: "FinishedLoading", sender: self)
})
}
This is my first time working with animations and programatically creating views so if someone could explain how to make the completion block wait for the animation to complete. In order to make the UIImageView appear and animate then once it's full screen, segue to the next page it would be very much appreciated.
The size is 0, 0. Transforming zero by any scale is still zero. I would advise you to not use transform at all, but rather just set the final frame to be what you want.
E.g.,
let startFrame = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
let endFrame = view.bounds
let imageView = UIImageView(image: ...)
imageView.contentMode = .scaleAspectFill
view.addSubview(imageView)
imageView.frame = startFrame
UIView.animate(withDuration: 3, delay: 0, options: .curveEaseInOut) {
imageView.frame = endFrame
} completion: { _ in
// do something here
}
That yields:
By the way, the performSegue probably should be inside a completion closure of the inner animate call.

UITapGestureRecognizer doesn't work after resizing UIView

I want to programmatically move and expand the size of a UIView on tap and then, if the UIView is tapped again, animate it back to its original size and position.
I can get the UIView to expand and move on the first tap. However, taps aren't registered at all after that initial animation.
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
myView.heightAnchor.constraint(equalToConstant: 50).isActive = true
myView.widthAnchor.constraint(equalToConstant: 50).isActive = true
myView.isUserInteractionEnabled = true
myView.addGestureRecognizer(tapRecognizer)
Below is my function that is called by handleTap to perform the initial animation on the first tap. It runs fine. I translate the position of myView and then change the frame size to make it bigger.
fileprivate func expandView(_ sender: UITapGestureRecognizer) {
UIView.animate(withDuration: 0.5, animations: {
let translate = CGAffineTransform(translationX: 100, y: 50 )
sender.view?.transform = translate
sender.view?.backgroundColor = .green
}) { (_) in
UIView.animate(withDuration: 0.5, animations: {
// change frame size of view to make it bigger
sender.view?.frame = CGRect(x: 100, y: 50, width: 100, height: 100)
})
}
}
I tried commenting out the animation that changes the frame size, leaving only the XY translation. That also doesn't allow handleTap to fire again when tapping myView.

How to create multiple labels without them all disappearing at once?

I used the code below to create a label every time a button is pressed, then have the label move to a specific location, and then have it delete.
My problem is it can't create multiple labels because it's deleting the label way too soon, because it removes everything named label.
How can I fix this so it creates multiple labels that only get deleted when a label individually completes its animation?
A solution I've thought of, but can't figure out is where you have it create a label with a different name such as label1, label2, and so on, so that way it can delete a specific label when it completes it's animation, instead of deleting all of them.
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
func createLabel() {
// Find the button's width and height
let labelWidth = label.frame.width
// Find the width and height of the enclosing view
let viewWidth = self.view.frame.width
// Compute width and height of the area to contain the button's center
let xwidth = viewWidth - labelWidth
// Generate a random x and y offset
let xoffset = CGFloat(arc4random_uniform(UInt32(xwidth)))
// Offset the button's center by the random offsets.
label.center.x = xoffset + labelWidth / 2
label.center.y = 300
label.font = UIFont(name:"Riffic Free", size: 18.0)
label.textColor = UIColor.white
label.textAlignment = .center
label.text = "+1"
self.view.addSubview(label)
}
func clearLabel() {
UIView.animate(withDuration: 0.9, delay: 0.4, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .curveLinear, animations: {
self.label.center = CGPoint(x: 265, y: 75 )
}, completion: { (finished: Bool) in
self.label.removeFromSuperview()
})
}
#IBAction func clicked(_ sender: Any) {
createLabel()
clearLabel()
}
The problem is with this code:
#IBAction func clicked(_ sender: Any) {
createLabel()
clearLabel()
}
There are two issues. The first is that you are doing the animation and removal before you even give the label a chance to become part of the interface. You need to introduce a delay (you can use my delay utility, https://stackoverflow.com/a/24318861/341994):
#IBAction func clicked(_ sender: Any) {
createLabel()
delay(0.1) {
clearLabel()
}
}
The second problem, as you have rightly said, is that you have one label instance variable, which you are using to share the label between createLabel and clearLabel. Thus another label cannot come along during the animation.
But you don't actually need any instance variable. So get rid of your label declaration completely! Instead, modify createLabel so that it actually creates the label (i.e. calls UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 100))) as a local variable, and then returns a reference to label it creates, like this:
func createLabel() -> UILabel {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
// ...
return label
}
... And then just have clearLabel take that same label as a parameter so that it moves that label and removes it at the end of the animation, like this:
func clearLabel(_ label : UILabel) {
// ...
}
Your clicked implementation will thus look like this, passing the label out of createLabel and into clearLabel:
#IBAction func clicked(_ sender: Any) {
let label = self.createLabel()
delay(0.1) {
self.clearLabel(label)
}
}
(The remaining details of modifying createLabel and clearLabel to make that work are left as an exercise for the reader.)
Now each tap on the button creates and animates and removes a new label, independently of whatever else may have happened before.

The completion block of UIView animation gets called before the animation is finished

I have a custom animator for view controller interactive transition. There is also a blur effect that is set to nil depending on the transition progress. The effect's animation code is the following:
#objc func blurEffectDissmisal() {
UIView.animate(withDuration: dismissAnimator.durationOfAnimation + 1, animations: {
self.blurEffectView?.effect = nil
}) { (done) in
if (done) {
self.blurEffectView?.removeFromSuperview()
}
}
}
I call it by a notification, which is called on the second view controller when the transition from it to the first one starts.
However, I have a problem here. The completion block is called before the animation ends. When I run the transition for the first time (without canceling it) it works fine, but during the subsequents runs it doesn't.
I had also tried to add the animation to my animator but it didn't work out, either.
Moreover, the completion block gets called before the actual animation ends when I cancel the transition (in this case, I understand why but can't figure out how to make it move backwards. Maybe I should create a reverse animation in a completion block?)
I have tried the suggestion from this answer as you can see, but doesn't help.
If you know how this problem could be solved, I would really appreciate your help.
Use a delay before calling animate function .
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
UIView.animate(withDuration: 2.0,delay: 0, animations: {
self.frame.origin.x = -2000
}) { (done) in
if(done){
self.removeFromSuperview()
}
}
}
I've created a playground where you can take a look at this change, just create new playground, click on the assistant editor (top left corner, two joined circles) and take a look at the animation. This should do.
import PlaygroundSupport
import UIKit
let rect = CGRect(x: 0, y: 0, width: 40, height: 40)
let view = UIView(frame: rect )
view.backgroundColor = UIColor.yellow
let label = UILabel(frame: rect)
label.text = "A"
label.textAlignment = .center
view.addSubview(label)
let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.dark)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
blurEffectView.frame = view.bounds
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(blurEffectView)
PlaygroundPage.current.liveView = view
UIView.animate(withDuration: 4, animations: {
blurEffectView.alpha = 0
}) { (done) in
if (done) {
blurEffectView.removeFromSuperview()
}
}
The problem you are facing is not that the UIView.animate doesn't do it's job, it's just because you are setting the view to nil is not animable. Like imagine deleting someething could be animated...
UIView.animate(withDuration: 1, animations: {
self.blurEffectView?.frame = CGRect(x: self.blurEffectView?.frame.origin.x, y: self.view.frame.size.height, width: self.blurEffectView?.frame.size.width, height: self.blurEffectView?.frame.size.height)
}, completion: {
(value: Bool) in
self.blurEffectView?.removeFromSuperview()
})
Using the same animation (on UIView) I am able to achieve this :

UIView block based animation weird behavior

I have got this very simple animation (case):
class ViewController: UIViewController {
var v: UIView!
var b = false
override func viewDidLoad() {
super.viewDidLoad()
self.v = UIView(frame: CGRect(x: 120, y: 250, width: 30, height: 30))
self.v.backgroundColor = .red
self.view.addSubview(self.v)
}
#IBAction func didTapButton(_ sender: UIButton) {
UIView.animate(withDuration: 3,
delay: 0,
options: [.beginFromCurrentState, .curveLinear],
animations: {
if self.b {
self.v.frame = CGRect(x: 120, y: 250, width: 30, height: 30)
} else {
self.v.frame = CGRect(x: 240, y: 250, width: 30, height: 30)
}
self.b = !self.b
},
completion: nil)
}
}
On each tap of the button the red view moves to left or right depending on current direction. The animation should start from the current position and to be linear.
However if I tap the button when the previous animation is already in progress then the red view does not start immediately to move in the opposite direction. It just freezes in the current position while the previous animation finishes and then starts moving. If I change the animation curve option from linear to easeOut and it works properly.
I am using iOS 10, Xcode 8.2.1
Any ideas why this happens?
When the animation is on, the UI components will not react to new changes. It will buffer the input and react once the animation is complete.
This is not related to the iOS or Xcode versions, but a normal behaviour.
I've found the answer.
I tis because of the additive animations in iOS 8 and above. Here is a very useful link which explains what actually happens and why the animation freezes.
http://iosoteric.com/additive-animations-animatewithduration-in-ios-8/

Resources