I have an element I need to animate and I followed this question to transform it as follows.
I defined a button that when I click it, the view would move horizontally. However, I need to define certain functions after the transformation (For example, I would need the print statement to be working after the transformation is completed in 5 seconds). But currently, it is printed simultaneously.
How can I define functionality to be done after the transformation.
#IBAction func mybutton(_ sender: Any) {
UIView.animate(withDuration: 5.0) {
self.hhview.transform = CGAffineTransform(translationX: -160, y: 0)
print("Voila!")
}
}
You can use a completion handler to execute code after the transformation:
UIView.animate(withDuration: 5.0) {
self.hhview.transform = CGAffineTransform(translationX: -160, y: 0)
} completion: { _ in
print("Completed")
}
I am trying to animate a cover view in my code. Essentially the cover slides up (closes) from off-screen to on-screen, and after the user presses a button the cover slides down (opens up) all the way off-screen.
My code to slide the cover view up the screen and into view is working perfectly. However, the code to slide the cover view down the screen and out of view is not working! The starting position for the cover sliding down is what seems to be incorrect. The cover slides down as expected, but starts far too high, such that the animation ends where it should start.
I've got a screen recording of the error in action (obviously slowed the animation down so you can see what I mean by the error). The cover sliding down should be sliding down from its original position where it slid up to.
Link to recording.
Can anyone spot where I'm going wrong in my code?
func showScoresCover() {
verticalDistance = ScoresCoverView.frame.size.height
self.ScoresCoverView.frame.origin.y += verticalDistance
UIView.animate(
withDuration: 0.5,
delay: 0.25,
options: .curveEaseOut,
animations:
{ self.ScoresCoverView.frame.origin.y -= self.verticalDistance},
completion:
{ finished in
print("Score cover closed.")
})
}
func hideScoresCover() {
verticalDistance = ScoresCoverView.frame.size.height
UIView.animate(
withDuration: 0.5,
delay: 0.25,
options: .curveEaseIn,
animations:
{ self.ScoresCoverView.frame.origin.y += self.verticalDistance},
completion:
{ finished in
print("Score cover opened.")
})
}
EDIT
hey #Nickolans thanks for your help on this issue. So... I have a bizarre update for you on this query. I implemented your code, and it works if I have a pre-programmed button which fires the showScoreCover and hideScoresCover. Turns out even my original code works, but again, only if I have a pre-programmed button which fires the show/hide functions.
However, I need to fire the function using a button where I programmatically reconfigure what the button does. How I've implemented this is through .addTarget and a number of selectors, and those selectors will differ based on where in the game the users are.
Anyway, this is what my code looks like:
self.continueButton.addTarget(self, action: #selector(self.hideScoresCover), for: UIControlEvents.touchUpInside)
self.continueButton.addTarget(self, action: #selector(self.startTeamB), for: UIControlEvents.touchUpInside)
Originally I only used one .addTarget and included the hideScoresCover() in the startTeamB function. This was resulting in the original issue where the cover doesn't hide correctly (starts too high on the screen). So I split it up into the above two .addTargets, and that also results in the same issue. HOWEVER, if I comment out the entire .addTarget line above related to the startTeamB selector and leave the selector related to hideScoresCover, then the cover is shown and is hidden absolutely as I want to to be, the animation is perfect. This means the underlying code in hideScoresCover() is working fine, but that the issue is arising somehow in how hideScoresCover is being run when the continue button is pressed.
Do you have any idea why this would be? somehow that continueButton target is firing incorrectly whenever there are two addTargets, or when there is only one target but hideScoresCover() is included in startTeamB().
I'm stumped as to why this would be the case. startTeamB() is not a complex function at all, so shouldn't be interfering with hideScoresCover(). So this leads me only to conclude it must be the way the continueButton fires hideScoresCover() when its either a separate target in addition to startTeamB being a target, or when hideScoresCover() is included in startTeamB itself. Unusual that it does work correctly when the only .addTarget is a selector calling hidesScoresCover?
#objc func startTeamB() {
self.UpdateTeam() //Just changes the team name
self.TeamBTimer() //Starts a timer to countdown teamB's remaining time
self.TeamAIndicator.isHidden = true //Just hides teamA image
self.TeamBIndicator.isHidden = false //Just shows teamB image
print("Hintify: ","-----Start Team B.")
}
I took it into Xcode and set it up myself. I believe the issue may be how you're changing your Y value, instead of changing the origin change the view layers y position.
The following code works so you can just take it! Make sure to change the frame to whatever you need it to be when you first start.
class ViewController: UIViewController {
//Bool to keep track if view is open
var isOpen = false
var verticalDistance: CGFloat = 0
var ScoresCoverView = UIView()
var button = UIButton()
override func viewDidLoad() {
//Setup Score view
ScoresCoverView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.width)
ScoresCoverView.backgroundColor = .blue
view.addSubview(ScoresCoverView)
//Set vertical distance
verticalDistance = ScoresCoverView.frame.height
//TEST button to test view animations
button.frame = CGRect(x: 200, y: 500, width: 100, height: 100)
button.backgroundColor = .purple
button.addTarget(self, action: #selector(hasBeenFired), for: .touchUpInside)
self.view.addSubview(button)
}
//When button pressed
#objc func hasBeenFired() {
if isOpen {
self.hideScoresCover()
self.isOpen = false
} else {
self.showScoresCover()
self.isOpen = true
}
}
//Show
func showScoresCover() {
self.ScoresCoverView.isHidden = false
UIView.animate(withDuration: 0.5, delay: 0.25, options: .curveEaseOut, animations: {
self.ScoresCoverView.layer.position.y -= self.verticalDistance
}) { (finished) in
print("Score cover closed.")
}
}
//Hide
func hideScoresCover() {
UIView.animate(withDuration: 0.5, delay: 0.25, options: .curveEaseIn, animations: {
self.ScoresCoverView.layer.position.y += self.verticalDistance
}) { (finished) in
self.ScoresCoverView.isHidden = true
print("Score cover Opened.")
}
}
}
EDIT
If you'd like to change the functionality of the button, make sure to remove the previous target before adding a new one.
For example, if you want to switch from show/hide to teamB remove show/hide and add teamB-- and vice versa.
When you first start, add your initial target:
self.continueButton.addTarget(self, action: #selector(self.hideScoresCover), for: UIControlEvents.touchUpInside)
Switch to startTeamB:
self.continueButton.removeTarget(self, action: #selector(self.hideScoresCover), for: UIControlEvents.touchUpInside)
self.continueButton.addTarget(self, action: #selector(self.startTeamB), for: UIControlEvents.touchUpInside)
Switch to hideScoreCover:
self.continueButton.removeTarget(self, action: #selector(self.startTeamB), for: UIControlEvents.touchUpInside)
self.continueButton.addTarget(self, action: #selector(self.hideScoresCover), for: UIControlEvents.touchUpInside)
I just want to say one thing, maybe the += is not necessary here.
func showScoresCover() {
verticalDistance = ScoresCoverView.frame.size.height
self.ScoresCoverView.frame.origin.y += verticalDistance
UIView.animate(
withDuration: 0.5,
delay: 0.25,
options: .curveEaseOut,
animations:
{ self.ScoresCoverView.frame.origin.y -= self.verticalDistance},
completion:
{ finished in
print("Score cover closed.")
})
}
should be
`self.ScoresCoverView.frame.origin.y = verticalDistance`
I have an UIScrollView with some labels inside. I can move the scroll view to the other 'page' with an button. But the offset isn't right when I push it too fast.
My code to move the scrollview to the next page:
#IBAction func moveToRight(_ sender: Any) {
let size = Int(scrView.contentOffset.x) + Int(scrView.frame.size.width);
scrView.setContentOffset(CGPoint(x: size, y: 0), animated: true)
}
The offset isn't right when I push it too fast. It looks like that the current animation stops and is going to perform the next one from it's current (unfinished) position.
Does anybody has an solution for this?
I don't have time to test but I think you are on the right track about the main reason of the issue. Since animated is set to true, my guess is contentOffset.x has not yet been set to its ultimate value until animation is finished.
Why don't you change your logic a bit and create a property which remembers the current page at where you last scrolled:
var currentPage: Int = 0
Then every time you moved right, increment the current page number if possible:
#IBAction func moveToRight(_ sender: Any) {
let maxX = scrollView.contentSize.x - scrollView.frame.width
let newX = CGFloat(currentPage + 1) * scrollView.frame.width
if newX <= maxX {
currentPage = currentPage + 1
scrollView.setContentOffset(CGPoint(x: newX, y: 0), animated: true)
}
}
I am trying to make a button in my view that rotates my image of a gimmicky clock by 45 degrees on each tap of the "rotate" button. All tutorials & answers that I researched online only go to the point where you hit the button once, then it resets (in my case, app only does the action once, then does nothing). So I am trying to make it go from 0 to 45 degrees on the 1st tap, 45 to 90 degrees on 2nd tap, and so on, infinitely, ie store the result every time the button is tapped, then continue from there. Any thoughts? Here is my simple code. Any help would be appreciated as am a newbie in coding - many thanks
#IBAction func Rotate(_ sender: AnyObject) {
let rotation: CGFloat = 0.785398
UIView.animate(withDuration: 0.5) {
self.myWatchImage.transform = CGAffineTransform(rotationAngle: rotation)
}
}
Apply rotated:by: to the current transform to get the next one:
#IBAction func doRotate(_ sender: UIButton) {
let rotation = CGFloat.pi / 4.0
let transform = myWatchImage.transform
let rotated = transform.rotated(by: rotation)
UIView.animate(withDuration: 0.5) {
self.myWatchImage.transform = rotated
}
}
I'm trying animations and I'm facing a big problem: when my animation finished and I do something ( touch the screen, etc), the elements reset their positions to their first position.
I found this: [blog]: Animation Blocks resets to original position after updating text
they say it's because elements have constraints or auto layout so turn it off to fix it but I don't want to turn it off.
Can we update constraints programmatically?
is there an other solution?
here's my animation:
#IBOutlet var tfUser: UITextfiled!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
tfUser.center.x += view.bounds.width
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
UIView.animateWithDuration(2, delay: 1.8, options: [.CurveEaseInOut], animations: {
self.tfUser.center.x += self.view.bounds.width
//self.tfUser.frame = CGRectMake(0, 233, 375 , 89)
// i tried CGRectMake but it delete the animation
}, completion: nil)
}
In comment of my question , #Hardik Shekhat told to use :
self.tfUser.translatesAutoresizingMaskIntoConstraints = true
it works for me !