Using UITextFieldDelegate I have two function that controls what happens the textfield begins editing and ends.
Using UIView.animate I am having a button disappear and hide using the .isHidden and .alpha property.
.alpha is being used because I also animate the button disappearing with a slight delay.
While the disappearing works well, upon using textFieldDidEndEditing the animation does not work, the button does come back but it does so abruptly with no ease.
To dismiss the keyboard I allow the user to tap anywhere by using this in ViewDidLoad()
self.view.addGestureRecognizer(UITapGestureRecognizer(target:
self.view, action: #selector(UIView.endEditing(_:))))
The delegate functions I use are below:
extension ExampleViewController: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == exampleTextField {
UIView.animate(withDuration: 0.1, delay: 0.1, options: .curveEaseOut,
animations: {self.exampleButton.alpha = 0.0},
completion: { _ in self.exampleButton.isHidden = true
})
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
if textField == exampleTextField {
UIView.animate(withDuration: 0.1, delay: 0.1, options: .curveEaseInOut,
animations: {self.exampleButton.alpha = 1.0},
completion: { _ in self.exampleButton.isHidden = false
//Do anything else that depends on this animation ending
})
}
}
}
Why is the .curveEaseIn not working?
The problem is that the button is hidden. You fade it back in while it is still hidden and then in the completion handler you unhide the button which already has an alpha of 1. This makes it suddenly appear after 0.2 seconds.
Since you are hiding using an alpha of 0 and showing with an alpha of 1, there is no need to set the isHidden property at all in either animation.
Related
I have a playButton that performs a "breathing animation". The button works just fine when I press it. The problem occurs if I press the device's Home Button and then re-open the app. Upon re-opening, the playButton does not have the "breathing animation" and it does not work (nothing happens when it is pressed).
#IBOutlet weak var playButton: UIButton!
override func viewWillAppear(_ animated: Bool) {
UIView.animate(withDuration: 1.0,
delay: 0,
options: [.autoreverse, .repeat, .allowUserInteraction],
animations: {
self.playButton.transform = CGAffineTransform(scaleX: 1.175, y: 1.175)
},
completion: nil
)
}
I've dealt with this issue in a previous game app where I needed to save and pause the game if the user pressed the Home Button or if there was an interruption (incoming call). So, I am well aware of:
func applicationDidBecomeActive() {}
func applicationWillResignActive() {}
func applicationDidEnterBackground() {}
But, I am not dealing with a gameState, timer, the need to save data, etc. I simply want my button and its animation to work properly when the app re-opens after the Home Button is pressed.
I also tried using override func viewDidLayoutSubviews() {} instead of viewWillAppear. But that did not work.
First of all, if you have a multiple animations within the same ViewController (VC) that occur after playButton is pressed, then that may explain why the it is becoming disabled upon return from background. Why? I don't know. But I had a similar issue and resolved it by creating a new class and VC for the multiple animations that were originally set to occur when my UIButton was pressed. Inside of my button's IBAction, I simply created a segue to then new VC.
With regards to the animation, you could approach this two ways: 1) Pause and Resume the animation using CALayer OR 2) Simply use NotificationCenter without even having to touch any AppDelegate code. I prefer simple ways b/c it will save time and effort. So, try this code in the VC where the button animation is to occur:
override func viewWillAppear(_ animated: Bool) {
NotificationCenter.default.addObserver(self, selector:#selector(goingToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector:#selector(openingApp), name: UIApplication.willEnterForegroundNotification, object: nil)
UIView.animate(withDuration: 1.0,
delay: 0,
options: [.autoreverse, .repeat, .allowUserInteraction],
animations: {
self.playButton.transform = CGAffineTransform(scaleX: 1.175, y: 1.175)
},
completion: nil)
}
#objc func goingToBackground(){
playButton.transform = .identity
}
#objc func openingApp(){
self.view.layoutIfNeeded()
UIView.animate(withDuration: 1.0,
delay: 0.3,
options: [.autoreverse, .repeat, .allowUserInteraction],
animations: {
self.playButton.transform = CGAffineTransform(scaleX: 1.175, y: 1.175)}, completion: nil)
self.view.layoutIfNeeded()
}
I have this simple code:
func tappedButton() {
self.button.alpha = 1.0
UIView.animate(withDuration: 1.0, delay: 4.0, options: .curveLinear, animations: {
self.button.alpha = 0.0
}) { _ in }
}
This function aims at showing a button again for 4 seconds before hiding it (with a 1 second animation). However, while the button is completely visible for these 4 seconds, tapping it doesn't work anymore.
Thanks for your help.
As per the documentation in for the method hittest(_:with:) of UIView https://developer.apple.com/documentation/uikit/uiview/1622469-hittest
This method ignores view objects that are hidden, that have disabled user interactions, or have an alpha level less than 0.01. This method does not take the view’s content into account when determining a hit. Thus, a view can still be returned even if the specified point is in a transparent portion of that view’s content.
This means that any view, particularly a button, with alpha 0.0 would not be touched.
However, the problem here is that the button is still visible, at least for you. This odd behavior occurs because the actual alpha value of the button is already setted to 0.0 when the animations starts. Animations work by changing the visual hierachy and transition the difference with the parameters you give to the function. In your case, you have two states: a view with a visible button visible and another view without the button. Only the visual part is animated but the corresponding values are already setted. A solution would be:
func tappedButton() {
self.button.alpha = 1.0
UIView.animate(withDuration: 1.0, delay: 4.0, options: .curveLinear, animations: { [weak self] in
self?.button.alpha = 0.01
}) { [weak self] _ in self?.button.alpha = 0.0 }
}
EDIT: This solution seems like a hack but works. I use this approach because the completion handler is always called with a true value.
func tapped() {
let duration = 1.0
let delay = 2.0
let delayDuration = delay + duration
UIView.animate(withDuration: duration, delay: delay, options: [.curveLinear, .allowUserInteraction], animations: { [weak self] in
self?.saveButton.alpha = 0.1
})
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayDuration, execute: { [weak self] in
self?.saveButton.alpha = 0.0
})
}
You need to use allUserInteraction in the options and also check for touches. The animation is added immediately and although you see the button to the system it is already hidden. What does this mean? It means you are watching a movie. But at least with userInteraction enabled you can check for touch events. This is great but how do we know the button is really showing or not? Well you have to use two different checks most likely. One that checks the true UIView alpha of the button and one check that checks the opacity on the presentation layer. I have never fully looked at the link between UIView animations and Core Animation except that I think UIView animations are a wrapper for Core Animations. UIView animations definitely update the view model immediately. So an alpha animation is most likely interpreted into an opacity animation on the layer. Armed with this we can check the opacity of the presentation layer on touches and see that the button is being clicked even if the view model thinks the alpha is 0. This check on the presentation layer will work as long as the opacity is above 0. So here you go.
import UIKit
class ViewController: UIViewController {
lazy var testButton : UIButton = {
let v = UIButton(frame: CGRect(x: 20, y: 50, width: self.view.bounds.width - 40, height: 50))
v.backgroundColor = UIColor.blue
v.addTarget(self, action: #selector(buttonClicked), for: .touchUpInside)
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(testButton)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 1.0, delay: 4.0, options: .allowUserInteraction, animations: {
self.testButton.alpha = 0
}, completion: nil)
//test the layer
//test the layer for opacity
if let presentation = testButton.layer.presentation()?.animation(forKey: "opacity"){
print("the animation is already added so normal clicks won't work")
}
}
#objc func buttonClicked(){
print("clicked")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if let touch = touches.first{
let location = touch.location(in: self.view)
if self.testButton.frame.contains(location){
//but what you might not know is the animation is probably already running
//and so the check above misses this
if let buttonPres = testButton.layer.presentation(),
let _ = buttonPres.animation(forKey: "opacity"),
let opacity = buttonPres.value(forKey: "opacity") as? CGFloat{
if opacity > 0{
buttonClicked()
}
}
}
}
}
}
I have a view (grey background) and another over it with a UITextField.
When I tap on the grey background, I want the view with the UITextField to disappear with an animation (move to top)
When I tap on the return key (virtual keyboard) the animation is smooth, but when I tap on the grey background, it disappears without animation. When I comment out the vueRecherche.isHidden = true in the completion, it's ok, but I want to hide it!
here is my function
private func fermeRecherche() {
if self.constraintVueRecherche_Top.constant == -5 {
return
}
self.txtRercherche.endEditing(true)
UIView.animate(withDuration: 0.3, animations: {
self.constraintVueRecherche_Top.constant = -5
self.vueFondGris.alpha = 0.0
self.view.layoutIfNeeded()
}, completion: { termine in
self.vueRecherche.isHidden = true
self.vueFondGris.isHidden = true
})
}
with this call, the animation is smooth :
func textFieldDidEndEditing(_ textField: UITextField) {
fermeRecherche()
}
but not with this one :
let tap=UITapGestureRecognizer(target: self, action: #selector(self.tapFondGris(_:)))
vueFondGris.addGestureRecognizer(tap)
func tapFondGris(_ sender: UITapGestureRecognizer) {
fermeRecherche()
}
any ideas?
Thanks
Here is my animation...one weird thing is when I type in a text field and hit done I proceed to set txtdata.text = "".....
and after that my image animation coordinates get all messed up.
override func viewDidAppear(_ animated: Bool) {
UIView.animate(withDuration: 15.0, delay: 0, options: [.repeat, .autoreverse], animations: {
self.smallCloud.center = CGPoint(x: self.screenSize.maxX , y:self.r.origin.y)
}, completion: nil)
} ///////////////////////////////////////////////////////////////
open func textFieldShouldReturn(_ textField: UITextField) -> Bool {
txtData.text = "";
return false
}
so the image basically moves from one side to another...if i dont hit done on the keypad the image moves as expected, however as soon as I hit done and put the UITextField back to " "; the image gets out of wack and goes off the screen anyone have any idea whats going on here?
This is iPad app by using SWIFT. It having two views.
1. starting_View
2. login_View
These two views are in same ViewController.
starting_View will be first view. By Clicking, NEXT button in first View, Starting View will move to left side by using animateDuration and same time, login_View will come from right. If we click Username/password fields (Any TextField), it will navigates to previous view.
Same time,,
if login_View will be first screen means, textField is working, keyboard is appearing.
But in animateDuration, i couldn't type. Kindly help me. Am using XCODE 6.1.
Code (from comment):
#IBAction func getStart_button(sender: UIButton) {
UIView.animateWithDuration(0.7, delay: 0.25, options: .CurveEaseOut, animations: {
self.clt_login_vw.frame = CGRectMake(450, 56, 574, 660)
}, completion:nil)
}
In General try to never change frames directly when using Autolayout.
The best way to move views have been for me to use CGAffineTransform.
let move = CGAffineTransformMakeTranslation(translation_X , translation_Y)
and inside your animation closure:
self.clt_login_vw.transform = move
Then to return it back just do inside the animation Closure:
self.clt_login_vw.transform = CGAffineTransformIdentity
Resume:
let moveOut = CGAffineTransformMakeTranslation(translation_X , translation_Y)
#IBAction func getStart_button(sender: UIButton) {
UIView.animateWithDuration(0.7, delay: 0.25, options: .CurveEaseOut, animations: {
[weak self] // Remember no not create cycles
self!.clt_login_vw.transform = moveOut
}, completion:nil)
}
Edit:
Im gonna add Noah Witherspoon's option.
Another way to do this is getting the constraint that is holding your view horizontally, once you get it, you change its constant and call layoutIfNeeded() inside the closure:
#IBAction func getStart_button(sender: UIButton) {
constraint.constant = newConstant // enough to make it off the screen
UIView.animateWithDuration(0.7, delay: 0.25, options: .CurveEaseOut, animations: {
[weak self] // Remember no not create cycles
self!.view.layoutIfNeeded()
}, completion:nil)
}
The way to get the specific constraint, I'm more friend of making IBOutlets, so I get easily its reference in Code.
That is if you didn't make it by code.