This is a simple function I am using for animating a view from top to bottom and vice versa (if is top to bottom animation and else is bottom to top animation) :
#objc func openMenu(sender: UIButton) {
if sender.tag == 1 {
self.buttonView.tag = 2
self.moduleView = ModulesCollectionView(frame: CGRect(x: 0, y: self.frame.origin.y + self.frame.size.height + 20, width: UIScreen.main.bounds.size.width, height: 0), collectionViewLayout: UICollectionViewLayout())
self.window?.addSubview(self.moduleView)
UIView.animate(withDuration: 0.7, animations: {
self.moduleView.frame = CGRect(x: 0, y: self.frame.origin.y + self.frame.size.height + 20, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height - self.frame.size.height - 22)
}, completion: { _ in
})
} else {
self.buttonView.tag = 1
UIView.animate(withDuration: 3, animations: {
self.moduleView.frame = CGRect(x: 0, y: self.frame.origin.y + self.frame.size.height + 20, width: UIScreen.main.bounds.size.width, height: 0)
}, completion: { _ in
self.moduleView.removeFromSuperview()
})
}
}
Top animation works fine and the view is animated from top to bottom pleasantly in 0.7 seconds. However, bottom to top animation does not happen. The view is removed instantly. This is the result I am getting :
But I want the animation to be clean while going from bottom to top as well. Just like here.
Secondary : What I finally plan to achieve is PullUpController with the exact reverse animation. So if anyone knows a similar library (pull down drag) can share there inputs.
Update : The issue is coming only with UICollectionView. I replaced collectionView with a simple UIView and it worked perfect.
You should try to use layoutSubViews() method after at the end of animation block. Change the animation block like this.
For if block:
self.moduleView.frame = CGRect(x: 0, y: self.frame.origin.y + self.frame.size.height + 20, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height - self.frame.size.height - 22)
self.moduleView.layoutSubviews()
For else block:
self.moduleView.frame = CGRect(x: 0, y: self.frame.origin.y + self.frame.size.height + 20, width: UIScreen.main.bounds.size.width, height: 0)
self.moduleView.layoutSubviews()
Here is example code, hope it will help.
Show view:
self.containerView = ModulesCollectionView(frame: UIScreen.main.bounds)
self.containerView.center = CGPoint(x: self.view.center.x,
y: self.view.center.y + self.view.frame.size.height)
self.window?.addSubview(self.moduleView)
self.window?.bringSubview(toFront: self.moduleView)
self.window?.endEditing(true)
UIView.animate(withDuration: 0.3, delay: 0.0,
usingSpringWithDamping: 0.7, initialSpringVelocity: 3.0,
options: .allowAnimatedContent, animations: {
self.moduleView.center = self.view.center
}) { (isFinished) in
self.view.layoutIfNeeded()
}
hide view:
UIView.animate(withDuration: 0.7, delay: 0.0,
usingSpringWithDamping: 1, initialSpringVelocity: 1.0,
options: .allowAnimatedContent, animations: {
self.moduleView.center = CGPoint(x: self.view.center.x,
y: self.view.center.y + self.view.frame.size.height)
}) { (isFinished) in
self.moduleView.removeFromSuperview
}
Related
My question is similar to this one. However, I want an updated version for swift, and I want it to go endlessly. Here is my animation now:
bubble.frame.origin = CGPoint(x: 75, y: -120)
UIView.animate(
withDuration: 2.5,
animations: {
self.bubble.transform = CGAffineTransform(translationX: 0, y: self.view.frame.height * -1.3)
}
)
After the bubble goes off screen, I want it to go back on from the bottom and go up off screen again and again endlessly.
Use the option from the UIViewAnimationOptions set:
UIViewAnimationOptions.Repeat
Should look like this with your code:
bubble.frame.origin = CGPoint(x: 75, y: -120)
UIView.animate(withDuration: 2.5, options: [UIViewAnimationOptions.Repeat], animations: {
self.bubble.transform = CGAffineTransform(translationX: 0, y: self.view.frame.height * -1.3)
})
My issue is that when I press the button that runs the dealToPlayer function(), everything acts as if a card has been dealt, updates total value of cards dealt, but it does not show any animation of the card being dealt or placed. Why might this be? I am not using auto layout for this scene at all.
func dealToPlayer() {
let index = Int(arc4random_uniform(UInt32(deckCards.count)))
let drawnCard = deckCards.remove(at: index)
randomCards.append(drawnCard)
randomCard = randomCards[3 + cardSelect]
image = UIImageView(frame: CGRect(x: self.deckLocation.x, y: self.deckLocation.y, width: self.width, height: self.height))
image.image = mapping[randomCard]
cardsOut = cardsOut + 1
print("cards out is currently: \(cardsOut)")
cardSelect = cardSelect + 1
UIView.animate(withDuration: 0.5, delay: 1.5, options: [], animations: {
self.image.frame = CGRect(x: self.cardTwoLocation.x - (self.cardsOut * self.thirdWidth), y: self.cardTwoLocation.y, width: self.width, height: self.height)
}, completion: nil)
checkPlayerValue()
}
First, make sure your image view is added to your view controller.
Second, make sure your animation is called by putting a break point.
Third, print out the location and make sure the new x and y are valid, and they are different than the previous x and y.
Here is a sample duplicate of your code an it works fine
image = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
image.backgroundColor = .red
self.view.addSubview(image)
UIView.animate(withDuration: 0.5, delay: 1.5, options: [], animations: {
self.image.frame = CGRect(x: 200, y: 200, width: 100, height: 100)
}, completion: nil)
I'm creating a custom view programmatically, which is working fine, then I'm trying to animate the view. For some strange reason the view is animating at all, I'm not sure why. Here is the code:
override func viewDidLoad() {
super.viewDidLoad()
let customView = UIView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
customView.backgroundColor = UIColor.blue
customView.layer.cornerRadius = 25
customView.layer.borderWidth = 8
customView.layer.borderColor = UIColor.red.cgColor
self.view.addSubview(customView)
UIView.animate(withDuration: 4, animations: {
customView.transform.translatedBy(x: 40, y: 60)
customView.transform.rotated(by: CGFloat.pi/2)
customView.transform.scaledBy(x: 0, y: 0.5)
})
}
you better call this animation block in viewDidAppear, with some delay.
import UIKit
class ViewController: UIViewController {
var customView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
customView = UIView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
customView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap)))
customView.backgroundColor = UIColor.blue
customView.layer.cornerRadius = 25
customView.layer.borderWidth = 8
customView.layer.borderColor = UIColor.red.cgColor
self.view.addSubview(customView)
}
override func viewDidAppear(_ animated: Bool) {
UIView.animate(withDuration: 5, delay: 1, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.customView.transform = CGAffineTransform(scaleX: 1, y: 0.5)
self.customView.transform = CGAffineTransform(rotationAngle: CGFloat.pi/2)
self.customView.transform = CGAffineTransform(translationX: 40, y: 60)
}, completion: nil)
}
func handleTap() {
UIView.animate(withDuration: 5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.customView.transform = CGAffineTransform(scaleX: 1, y: 0.5)
self.customView.transform = CGAffineTransform(rotationAngle: CGFloat.pi/2)
self.customView.transform = CGAffineTransform(translationX: 40, y: 60)
}, completion: nil)
}
}
Your question is not correct. your view is not animating.
Why?
You are using a return function and your customView needs to catch the modified version like this.
customView.transform = customView.transform.translatedBy(x: 40, y: 60)
So your code would be like this.
let customView = UIView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
customView.backgroundColor = UIColor.blue
customView.layer.cornerRadius = 25
customView.layer.borderWidth = 8
customView.layer.borderColor = UIColor.red.cgColor
self.view.addSubview(customView)
UIView.animate(withDuration: 4, animations: {
customView.transform = customView.transform.translatedBy(x: 40, y: 60)
//customView.transform = customView.transform.rotated(by: CGFloat.pi/2)
//customView.transform = customView.transform.scaledBy(x: 0, y: 0.5)
})
There are lots of online resources how to group animation.Just google it.:)
I need to move UIButtons inside the view when I tap on image, and need to move UIButtons out of the screen when I tap on same image again.
When I tap on a UIImage, 4 UIButtons move inside screen with following code without a problem.
func imageTapped(img: AnyObject)
{UIView.animateWithDuration(0.5,
delay: 0,
options: [.CurveEaseInOut, .AllowUserInteraction],
animations: {
self.imageReceivedTrashCan.center = CGPoint(x: 30, y: 530)
self.imageReceivedRightArrow.center = CGPoint(x: 285, y: 530)
self.imageReceivedForward.center = CGPoint(x: 160, y: 530)
self.imageReceivedCross.center = CGPoint(x: 30, y: 30)
},
completion: { finished in
print("Objects moved!")
})
}
But, I couldn't find a way how can I move UIButtons back to original locations when I click on same image again. Appreciate your help.
Even if this seems really old-school, w/o any autolayout and I randomly guessed you don't change the center's elsewhere and all center's at once:
func imageTapped(img: AnyObject) {
if (self.imageReceivedTrashCan.center == CGPoint(x: 30, y: 530)) {
UIView.animateWithDuration(0.5,
delay: 0,
options: [.CurveEaseInOut, .AllowUserInteraction],
animations: {
self.imageReceivedTrashCan.center = CGPoint(x: -30, y: -530)
self.imageReceivedRightArrow.center = CGPoint(x: -285, y: -530)
self.imageReceivedForward.center = CGPoint(x: -160, y: -530)
self.imageReceivedCross.center = CGPoint(x: -30, y: -30)
},
completion: { finished in
print("Objects moved!")
})
} else {
UIView.animateWithDuration(0.5,
delay: 0,
options: [.CurveEaseInOut, .AllowUserInteraction],
animations: {
self.imageReceivedTrashCan.center = CGPoint(x: 30, y: 530)
self.imageReceivedRightArrow.center = CGPoint(x: 285, y: 530)
self.imageReceivedForward.center = CGPoint(x: 160, y: 530)
self.imageReceivedCross.center = CGPoint(x: 30, y: 30)
},
completion: { finished in
print("Objects moved!")
})
}
}
Firstly, to answer your question :
You can save the initial position for each button then on the second tap you call animateWithDuration:delay:options:animations:completion: and set the position to self.imageReceivedTrashCan.center = initialPosition
You also can try with translation methods from CoreGraphics.
UIView.animateWithDuration(0.5,
delay: 0,
options: [.CurveEaseInOut, .AllowUserInteraction],
animations: {
self.imageReceivedTrashCan.transform = CGAffineTransformMakeTranslation(300, 0)
....
},
completion: { finished in
print("Objects moved!")
})
}
Then on the second tap get back to the Identity transform
UIView.animateWithDuration(0.5,
delay: 0,
options: [.CurveEaseInOut, .AllowUserInteraction],
animations: {
self.imageReceivedTrashCan.transform = CGAffineTransformIdentity
....
},
completion: { finished in
print("Objects moved!")
})
}
But this method is hard coded, you should use autoLayout to make this kind of animations. Look at here
Hope this help you ;)
Select imageView, goto attribute inspector and Check user interaction enabled
UITapGestureRecognizer *TapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(moveMyView:)];
[imageview addGestureRecognizer: TapRecognizer];
- (void) moveMyView:(UITapGestureRecognizer*)sender {
[UIView animateWithDuration:1.0 delay:0.0 options:
UIViewAnimationOptionCurveEaseIn animations:^{
self.imageReceivedTrashCan.center = CGPoint(x: -30, y: -530)
self.imageReceivedRightArrow.center = CGPoint(x: -285, y: -530)
self.imageReceivedForward.center = CGPoint(x: -160, y: -530)
self.imageReceivedCross.center = CGPoint(x: -30, y: -30)
} completion:^ (BOOL completed) {}];
}];
}
I'm trying to animate labels on view. I want to show label 1, then label 2, then remove label2. But label2 is always removed. If I delete the last keyframe block (which "removes" label2), it shows properly).
Here's the code:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let pos1 = self.label1.frame
let pos2 = self.label2.frame
self.label1.frame = CGRect(x: self.view.center.x, y: -10, width: 0, height: 0)
self.label2.frame = CGRect(x: pos2.origin.x, y: self.view.frame.height+10, width: 0, height: 0)
UIView.animateKeyframesWithDuration(1.5, delay: 0.0, options: nil, animations: {
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 0.4, animations: {
self.label1.frame = pos1
})
UIView.addKeyframeWithRelativeStartTime(0.4, relativeDuration: 0.4, animations: {
self.label2.frame = pos2
})
UIView.addKeyframeWithRelativeStartTime(1.0, relativeDuration: 0.5, animations: {
self.label1.frame = CGRect(x: pos2.origin.x, y: self.view.frame.height + 10, width: 0, height: 0)
})
}, completion: nil)
}
From the reference, the timing parameters in addKeyframeWithRelativeStartTime... are values in the range of 0..1 which are interpreted relative to the entire duration of the animation and NOT a value in seconds. So specifying a keyframe with a start time of 1.0 says to start the animation at the END of the entire animation. Change the start times to 0.0 / 1.5, 0.5 / 1.5, 1.0 / 1.5 and the durations to 0.4 / 1.5 and 0.5 / 1.5.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let pos1 = self.label1.frame
let pos2 = self.label2.frame
self.label1.frame = CGRect(x: self.view.center.x, y: -10, width: 0, height: 0)
self.label2.frame = CGRect(x: pos2.origin.x, y: self.view.frame.height + 10, width: 0, height: 0)
UIView.animateKeyframes(withDuration: 1.5, delay: 0.0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0.0 / 1.5, relativeDuration: 0.4 / 1.5, animations: {
self.label1.frame = pos1
})
UIView.addKeyframe(withRelativeStartTime: 0.4 / 1.5, relativeDuration: 0.4 / 1.5, animations: {
self.label2.frame = pos2
})
UIView.addKeyframe(withRelativeStartTime: 1.0 / 1.5, relativeDuration: 0.5 / 1.5, animations: {
self.label1.frame = CGRect(x: pos2.origin.x, y: self.view.frame.height + 10, width: pos1.size.width, height: pos1.size.height)
})
}, completion: nil)
}
It seems there are also issues animating the size of views. Only the final size is used (note that in this case you should see the labels start from 0x0 and grow to their final size, but they don't) So setting the size in the final frame to 0x0 makes label1 have that size through the whole animation. Change the code to maintain the size and it seems to work fine. If you really want to animate the size to zero you'll probably need to animate the transform property through scaling.