I'd like to animate an existing UIButton in swift, the goal is to let get the attention of the user to this button when he taps the view controller. The animation is supposed to start from the top in the view controller and go to the initial position of the button.
I did the following scenario but it's not working:
import UIKit
class ViewController: UIViewController {
#IBOutlet var dismissButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
dismissButton = UIButton()
var tapGesture = UITapGestureRecognizer(target: self, action: Selector("handleTapGesture:"))
view.addGestureRecognizer(tapGesture)
}
func handleTapGesture(tapGesture: UITapGestureRecognizer) {
println("tap")
UIView.animateWithDuration(2.0, delay: 0.5, usingSpringWithDamping: 0.2, initialSpringVelocity: 0.0, options: nil, animations: {
self.dismissButton.center = CGPoint(x: 200, y: 90 + 200)
}, completion: nil)
}
}
Any suggestion?
Thank you!
Step 1: Take IBOutlet of your Top Constraint of button
Step 2: Update your Constraint's constant by using constraintName.constant property
Step 3: In animation block type self.view.layoutIfNeeded()
Here is an simple example: https://stackoverflow.com/a/26040569/3411787
Related
Basically my issue is that I'm trying to create a drawer since iOS/Swift doesn't seem to have a native object for this. For reasons I can't recall, I decided to go with a navigation controller for this, thinking I could just override the back button(s) and their actions to turn this nav bar into a drawer. While I've changed the image/text of the back button successfully to look like a "burger" (drawer icon), I can't figure out how to successfully stop the button from taking us back, and instead have it just open/close my drawer.
Any advice appreciated, including suggestions to take an entirely different approach to creating this.
The following is the controller for the page which has the back button i'm trying to override. You can see in viewDidLoad() I call prepareDrawerPage(), which is a helper I have in another file. This definition can also be seen below.
class CreateListingController: UIViewController {
let DRAWER_OPEN = CGFloat(0)
var DRAWER_CLOSED: CGFloat = 0 // -1 * width of drawer
#IBOutlet var navItem: UINavigationItem!
#IBOutlet var drawerView: UIView!
#IBOutlet var drawerViewLead: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
DRAWER_CLOSED = UIScreen.main.bounds.width * -1 * drawerViewLead.multiplier
drawerViewLead.constant = DRAWER_CLOSED
prepareDrawerPage(controller: self, title: "PBX - Create a Listing")
}
#IBAction func flipMenu(_ sender: UIBarButtonItem) {
if (drawerViewLead.constant == DRAWER_OPEN){
drawerViewLead.constant = DRAWER_CLOSED
UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
})
} else {
drawerViewLead.constant = DRAWER_OPEN
UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseIn, animations: {
self.view.layoutIfNeeded()
})
}
}
}
prepareDrawerPage():
func prepareDrawerPage(controller: UIViewController, title: String) {
let blank = UIImage()
controller.navigationController?.navigationBar.backIndicatorImage = blank
controller.navigationController?.navigationBar.backIndicatorTransitionMaskImage = blank
controller.title = title
}
The above approach works fine on the home page, where we haven't used the navigation bar yet. However, once we click to the create listing page, the button still takes us back home when clicked despite having linked an action between the BarButtonItem (back button) and CreateListingController (flipMenu function).
You can add a custom button on the navigation bar.
override func viewDidLoad() {
...
self.navigationItem.hidesBackButton = true
let newBackButton = UIBarButtonItem(image: YourImage, style: .plain, target: self, action: #selector(back(sender:)))
self.navigationItem.leftBarButtonItem = newBackButton
...
}
#objc func back(sender: UIBarButtonItem) {
// Perform your custom actions
// ...
// Go back to the previous ViewController
// self.navigationController?.popViewController(animated: true)
}
When my app stat I want to show buttons animation. In storyboard my buttons have height = 50 and y = -50. I have this action in code.
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad()
{
super.viewDidLoad()
buttonAnimation()
}
func buttonAnimation()
{
self.buttonTopConstraint.constant += self.ButtonHeightConstraint.constant
UIView.animate(withDuration: 1.5, delay: 0.0, options: [], animations:
{
self.view.layoutIfNeeded()
}
)
}
Animation work well. But when app start my background imageView in storyboard and different imageView animated too. But I need to have only animation for button. How to fix it?
Don't call animation in viewDidLoad because at that time all views of your controller are going to set.
Use viewDidAppear to call animation method.
override func viewDidAppear()
{
buttonAnimation()
}
Use CGAffineTransform to animate your button. Remove y=-50 from storyboard and keep it at its desired place. Then call this function in viewWillAppear and viewDidAppear :
func animateButton() {
yourButton.transform = CGAffineTransform(translationX: yourButton.frame.origin.x, y: yourButton.frame.origin.y - 50)
UIView.animate(withDuration: 1.5, delay: 0.0, options: [], animations: {
self.yourButton.transform = .identity
}, completion: nil)
}
I'm trying to code an animation behind the profile picture, in order to encourage the user to click on it.
It's a circle which become bigger and smaller then.
override func layoutSubviews() {
super.layoutSubviews()
UIView.animate(withDuration: 1.0, delay: 0, options: [.repeat, .autoreverse], animations: {
self.bouncedView.transform = CGAffineTransform(scaleX:
1.3, y: 1.3)
}, completion: nil)
}
The problem is that, when I go to another viewController, the animation is stopped and the circle stays like you can the on the screen shot. Do you know how I could avoid this issue ?
Do a transform to .identity on ViewDidAppear. Something similar to below code:
class HomeController: UIViewController {
#IBOutlet weak var viewToAnimate: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
self.viewToAnimate.transform = .identity
animateView()
}
func animateView(){
UIView.animate(withDuration: 1.0, delay: 0, options: [.repeat, .autoreverse], animations: {
self.viewToAnimate.transform = CGAffineTransform(scaleX:
1.3, y: 1.3)
}, completion: nil)
}
}
The problem as you might have guessed is that the UIView.animate is only called on the ViewDidLoad method and since we don't have access that code while returning to this ViewController from another, it is better to start the animation in the ViewWillAppear method.
If the same issue occurs when you switch between tabs, then please make a separate UIView subclass for the view that you want to animate and proceed as follows:
class AnimateView: UIView {
override func awakeFromNib() {
super.awakeFromNib()
}
override func willMove(toWindow newWindow: UIWindow?) {
super.willMove(toWindow: newWindow)
self.transform = .identity
animateView()
}
func animateView(){
UIView.animate(withDuration: 1.0, delay: 0, options: [.repeat, .autoreverse], animations: {
self.transform = CGAffineTransform(scaleX:
1.3, y: 1.3)
}, completion: nil)
}
}
Here, you have taken the animate function to the UIView object and whenever the view appears, the animation will be reset.
Well, I don't yet have a satisfying answer yet (at least that would satisfy me), but I would like to add at least some insight to which I was able to get by a bit of experimenting.
First of all, it seems that the animation gets ended when the viewController is not currently the one presented. In your case that means that the animation stops and finishes with the state, in which the view is 1.3 times bigger, and stops repeating. layoutSubviews gets called only when you present it the first time. At least the viewController.viewDidLayoutSubviews gets called only at the beginning and not when you go back (so layoutSubviews will not get executed when the view reappears) - so the animation won't get restarted.
I tried to move the animation to the viewDidAppear of a UIViewController - that did not work either, because stopping animation resulted in state in which the view was already scaled 1.3 times. Resetting the state to CGAffineTransform.identity before creating the animation did work.
Now as I said, this can serve you as a workaround on how to get it working, however, I think what you really are looking for is some hook that would tell your view (not viewController) that it got presented again. But the following minimal example can at least help others to take a look at it and not start from scratch.
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
let animator = UIViewPropertyAnimator(duration: 1, timingParameters: UICubicTimingParameters(animationCurve: .linear))
init(title: String) {
super.init(nibName: nil, bundle: nil)
self.title = title
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let animatableView = UIView()
override func loadView() {
let view = UIView()
view.backgroundColor = .white
animatableView.frame = CGRect(x: 150, y: 400, width: 100, height: 100)
animatableView.backgroundColor = UIColor.magenta
view.addSubview(animatableView)
self.view = view
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.animatableView.transform = CGAffineTransform.identity
UIView.animate(withDuration: 1.0, delay: 0, options: [.repeat, .autoreverse], animations: {
self.animatableView.transform = CGAffineTransform(scaleX:
1.3, y: 1.3)
}, completion: nil)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("\(self.title) >> Lay out")
}
}
let tabBar = UITabBarController()
tabBar.viewControllers = [MyViewController(title: "first"), MyViewController(title: "second"), MyViewController(title: "third")]
tabBar.selectedIndex = 0
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = tabBar
EDIT
I added few lines to check if the self.animatableView.layer.animationKeys() contains any animations, it seems that switching tabs removes the animation from the view's layer - so you have to find a way to add the animation everytime the view reappears.
EDIT 2
So I would go with #SWAT's answer and use willMove(toWindow:) instead of layoutSubviews.
I am very new to Xcode, so this could be something very basic that i'm just not finding, but i'm trying to create an app that moves 2 images away from each other (at least one width away) when the user taps the screen, and then have the images return to their original positions when the user taps the screen again, however, i have been unable to find out how to move an image in a specific direction, a specific distance.
i'm also new to Stack Overflow, so i'm sorry if i'm doing something wrong
I am breaking the rules by giving up the answer when it appears you have not googled enough. The general rule is to show the code you have attempted and indicate what part you are having issues with.
The code below will achieve what you are attempting to do.
import UIKit
class slidingIamgesViewController: UIViewController {
#IBOutlet weak var topImage: UIImageView!
#IBOutlet weak var bottomImage: UIImageView!
var doubleTap: Bool! = false
//MARK: View LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
let singleFingerTap = UITapGestureRecognizer(target: self, action: #selector(slidingIamgesViewController.handleSingleTap(_:)))
self.view.addGestureRecognizer(singleFingerTap)
}
// MARK: gestutre recognizer
func handleSingleTap(_ recognizer: UITapGestureRecognizer) {
if (doubleTap) {
UIView.animate(withDuration: 0.7, delay: 1.0, options: .curveEaseOut, animations: {
var basketTopFrame = self.topImage.frame
basketTopFrame.origin.y += basketTopFrame.size.height
var basketBottomFrame = self.bottomImage.frame
basketBottomFrame.origin.y -= basketBottomFrame.size.height
self.topImage.frame = basketTopFrame
self.bottomImage.frame = basketBottomFrame
}, completion: { finished in
print("Images Moved back!")
})
doubleTap = false
} else {
UIView.animate(withDuration: 0.7, delay: 1.0, options: .curveEaseOut, animations: {
var basketTopFrame = self.topImage.frame
basketTopFrame.origin.y -= basketTopFrame.size.height
var basketBottomFrame = self.bottomImage.frame
basketBottomFrame.origin.y += basketBottomFrame.size.height
self.topImage.frame = basketTopFrame
self.bottomImage.frame = basketBottomFrame
}, completion: { finished in
print("Images sperated!")
})
doubleTap = true
}
}
}
Make sure in your storyboard to added a Tap Gesture Recognizer.
I'm a very new-to-code learner. I've been taking some online courses, and came across this project recently.
I basically copied the code as I saw it on the screen, as the project files weren't available to download.
The animation is supposed to bring the UILabels from outside the view and position them within the view.
What seems to happen however, is the labels are starting within the view screen and animate outward and beyond.
I've copied the project below. Any help is so very appreciated.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var helloWorld: UILabel!
#IBOutlet weak var secondLabel: UILabel!
#IBOutlet weak var hiddenLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
helloWorld.center.y -= view.bounds.height
secondLabel.center.y += view.bounds.height
hiddenLabel.alpha = 0.0
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// Animate Hello World label
UIView.animateWithDuration(1.5, animations: {
self.helloWorld.center.y += self.view.bounds.height
}, completion: { finished in
self.secondAnimation()
})
// Animate background color change
UIView.animateWithDuration(2.0, delay: 1.5, options: [], animations: {
self.view.backgroundColor = UIColor.yellowColor()
}, completion:nil)
}
// Animate second Label
func secondAnimation() {
UIView.animateWithDuration(1.5, delay: 0.0, options: [], animations: { () -> Void in
self.secondLabel.center.y -= self.view.bounds.height
}, completion:nil)
}
func backgroundColor() {
UIView.animateWithDuration(2.5, animations: {
self.view.backgroundColor = UIColor.blackColor()
}, completion: nil)
UIView.animateWithDuration(1.0, delay: 1.5, options: [], animations: {
self.hiddenLabel.alpha = 1.0
}, completion:nil)
}
}
I would check hello world.center.y value in viewWillAppear, before you subtract the view.bounds.height. If center.y value is large and positive, then the subtraction might be setting the center.y value to be inside of the view. If this is the case, then you would want to change the subtraction in viewWillAppear to addition and then the addition in the viewDidAppear animation to subtraction.