I have the most strange situation with a custom keyboard. First of all, I have set up a dummy view for the textfield, in order to hide the stock keyboard let dummyView : UIView = UIView(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
amountField.inputView = dummyView
Then I have my custom keyboard which animates when editing begins in the text field
func textFieldDidBeginEditing(textField: UITextField) {
keyboardContainer.hidden = false
UIView.animateWithDuration(0.6, animations: {
self.keyboardContainer.frame = self.keyboardScreenPosition!
}, completion: {
finished in
if finished {
//just in case
}
})
}
Also, I have set up a button which should end editing and hide my custom keyboard
#IBAction func calculeaza(sender: AnyObject) {
self.amountField.resignFirstResponder()
UIView.animateWithDuration(0.6, animations: {
self.keyboardContainer.frame.origin.y = self.view.bounds.height
}, completion: {
finished in
if finished {
}
})
}
The most strange part comes with the resignFirstResponder(). Let me explain: If that part is not included, the keyboard hides just fine (but the text field keeps on blinking cursor which is not an option ofc). If the resigning part is included, the keyboard animates from the top to its current position, then on pressing the button again it does slides down as intended. I am really puzzled on why is this happening...I debugged the sizes of the view and the heights are ok so it should slide down from the beginning. I really dont understand what is happening. Any help is much appreciated, many thanks!
EDIT: another strange effect is if I move the resign part (or the super end editing) in the animation ending closure. The keyboard slides just fine, then it reappears on screen
This sounds like an issue with autolayout constraints. Those are updated after the animateWithDuration which is potentially causing this odd behavior. If you have (a) constraint(s) in the Storyboard used for autolayout, try updating that(/those). If you haven't already, you'll need to add it as an IBOutlet and animate the autolayout change in the duration.
For example, say the constraint is called theRelevantConstraint. Then replace the middle lines
self.theRelevantConstraint.constant = valueItShouldBe
UIView.animateWithDuration(0.6, animations: {
self.view.layoutIfNeeded()
}, completion: {
finished in
if finished {
}
})
Related
I have a Header view that contains pager view (FSPagerView), which has an item (FSPagerViewCell) that contains bar chart, 2019, year review texts. I'm trying to scale down Header at certain times with such code:
private func setSelfHeightAnimated(height: CGFloat)
{
UIView.animate(withDuration: 0.2, animations: {
self.selfHeightAnchor.constant = height
self.superview?.layoutIfNeeded()
})
}
private func setPagerHeightAnimated(height: CGFloat)
{
UIView.animate(withDuration: 0.2, animations: {
self.pagerHeightAnchor.constant = height
self.layoutIfNeeded()
// self.pagerView.layoutIfNeeded()
// self.pagerView.layoutSubviews()
// self.pagerView.cellForItem(at: 0)?.layoutIfNeeded()
// self.pagerView.cellForItem(at: 0)?.setNeedsDisplay()
// self.superview?.layoutIfNeeded()
// self.setNeedsDisplay()
// self.pagerView.setNeedsDisplay()
})
}
However, the old frame glitch is very visible. What could be missing? Here is the video: https://streamable.com/owsdi
You have an 1st layout and a 2nd layout, and an animation that leads from the 1st to the 2nd.
Currently, the 1st layout is faded out and the 2nd layout is faded in during the animation, which gives you the „glitch“.
I assume what you want to achieve is:
- The top part (History, 2019, Year review) should keep their height, and
- only the columns below should shrink/expand, while the lower table slides up or down.
A solution probably is that you animate only the height of a subview that contains the 2 columns.
If the header view is set to adopt its height to its contents, then I expect that the required effect is shown without a glitch.
EDIT:
If you definitively had to animate the upper part as a whole, one possibility may be (that I consider as ugly) to set the alpha value of History, 2019, and Year to 0 immediately before the animation begins. Then these texts would only fade in so the glitch with double images at different places would be avoided.
Try like this to avoid glitching.
private func setSelfHeightAnimated(height: CGFloat)
{
self.selfHeightAnchor.constant = height
UIView.animate(withDuration: 0.2, animations: {
self.layoutIfNeeded()
self.viewDidLayoutSubviews()
})
}
private func setPagerHeightAnimated(height: CGFloat)
{
self.pagerHeightAnchor.constant = height
UIView.animate(withDuration: 0.2, animations: {
self.layoutIfNeeded()
self.viewDidLayoutSubviews()
})
}
The goal is to have the first table view cell content move left for a time and then back again.
The bigger goal is that we will bounce the cell's content view slightly to the left and bounce in a red box then return the cell to normal.
Although similar to another SO question, the answer does not reveal how to do this. Plus, this question would apply to anyone who wants to animate moving the cell content to the left temporarily and then back again. Thus, that's why it's a separate question.
The environment is iOS 11+ and iPhone app.
I have a new Table View project created that is animating the contentView moving via transform. However, it doesn't seem to remotely start in the normal position and then move as desired. The content starts off centered and then moves into place instead.
How can I get the contentView to animate moving a little to the left and then back again into its normal position?
Project: https://github.com/mikefinney/peekabooswipe
I rewrote your code a little. You were applying a transform to the content view and the documentation suggests instead animating the center property when shifting the location.
func applyCellAnimations() {
let originalCenter = contentView.center
let offsetCenter = originalCenter.applying(.init(translationX: -44, y: 0))
animateToCenter(offsetCenter) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
self.animateToCenter(originalCenter)
})
}
}
private func animateToCenter(_ center: CGPoint, completionHandler: #escaping () -> Void = { }) {
layoutIfNeeded()
UIView.animate(
withDuration: 1,
delay: 0,
options: [.curveEaseInOut],
animations: {
self.contentView.center = center
}, completion: { didComplete in
if didComplete { completionHandler() }
})
}
I'm experiencing a glitch where my UITextField's text jumps to its final position (it doesn't animate) when animating the textfield's width constraint. Take a look at this gif:
When the "Grow" button is tapped, the textfield's width grows. But "hello world" jumps immediately to the center instead of gliding there. When the "Shrink" button is tapped, "hello world" jumps immediately back to the left.
My animation function looks like this:
func animateGrowShrinkTextFields(grow: Bool) {
if grow {
UIView.animate(withDuration: 0.5, animations: {
self.widthConstraint.constant = 330
self.view.layoutIfNeeded()
}, completion: nil)
} else {
UIView.animate(withDuration: 0.5, animations: {
self.widthConstraint.constant = 100
self.view.layoutIfNeeded()
}, completion: nil)
}
}
I have tried the following list suggestions; none of them worked.
I called self.view.layoutIfNeeded() and self.helloWorldTextField.layoutIfNeeded() before and within the animation block as suggested in this answer: https://stackoverflow.com/a/32996503/2179970
I tried self.view.layoutSubviews and self.helloWorldTextField.layoutSubview as suggested in this answer: https://stackoverflow.com/a/30845306/2179970
Also tried setNeedsLayout() UITextField text jumps iOS 9
I even tried changing the font as suggested here: https://stackoverflow.com/a/35681037/2179970
I tried resignFirstResponder (although though I never tap or edit the textfield in my tests, so it should not involve the firstResponder) as suggested here: https://stackoverflow.com/a/33334567/2179970
I tried subclassing UITextField as seen here: https://stackoverflow.com/a/40279630/2179970
I also tried using a UILabel and got the same jumpy result.
The following question is also very similar to mine but does not have an answer yet: UITextfield text position not animating while width constraint is animated
Here is my project on Github: https://github.com/starkindustries/ConstraintAnimationTest
Solution Demo
I've found a working solution. It feels a little hackish but it works. Here is a gif of the final result. Notice that helloWorldTextField has a blue border to show its location within the second textfield behind it.
Instructions
Make two textfields: helloWorldTextField (the original from the question) and borderTextField (a new textfield). Remove helloWorldTextFields's border and background color. Keep borderTextField's border and background color. Center helloWorldTextField within borderTextField. Then animate the width of borderTextField.
Github link and Code
Here is the project on Github: https://github.com/starkindustries/ConstraintAnimationTest
Here is the code within MyViewController class. Everything else is setup in the storyboard which can be viewed on Github at the link above.
class MyViewController: UIViewController {
// Hello World TextField Border var
#IBOutlet weak var borderTextFieldWidth: NSLayoutConstraint!
// Button Vars
#IBOutlet weak var myButton: UIButton!
var grow: Bool = false
func animateGrowShrinkTextFields(grow: Bool, duration: TimeInterval) {
if grow {
UIView.animate(withDuration: duration, animations: {
self.borderTextFieldWidth.constant = 330
self.view.layoutIfNeeded()
}, completion: { (finished: Bool) in
print("Grow animation complete!")
})
} else {
UIView.animate(withDuration: duration, animations: {
self.borderTextFieldWidth.constant = 115
self.view.layoutIfNeeded()
}, completion: { (finished: Bool) in
print("Shrink animation complete!")
})
}
}
#IBAction func toggle(){
let duration: TimeInterval = 1.0
grow = !grow
let title = grow ? "Shrink" : "Grow"
myButton.setTitle(title, for: UIControlState.normal)
animateGrowShrinkTextFields(grow: grow, duration: duration)
}
}
Notes and References
What led me to this solution was #JimmyJames's comment: "You are just animating the UITextField width, but the content inside is not animated."
I researched how to animate font changes and came across this question: Is there a way to animate changing a UILabel's textAlignment?
In that question #CSmith mentioned that "you can animate the FRAME, not the textAlignment" https://stackoverflow.com/a/19251634/2179970
The accepted answer in that question suggests to use a UILabel within another frame. https://stackoverflow.com/a/19251735/2179970
Hope this helps anyone else who comes across this problem. If anyone has another way to solve this, please post a comment or another answer. Thanks!
Another solution for the issue is set yourLabel.contentMode = .center on init, and animate in animation block as usually
as the topic already says, i try to create a small line on top of my UIKeyboard appearance. The reason is, if i pop up the white keyboard while having a white background, it looks just awful.
One idea would be to create a thin 1px line and place it on the bottom, hide it and show it whenever there is the keyboard displayed and align it with auto layout. The disadvantage of this idea is to do it in every view.
Thanks for your ideas.
Every UITextField and UITextView has an inputAccessoryView property (inherited from the UIResponder superclass). When one of these views becomes first responder and the system presents the on-screen keyboard, the system automatically displays the inputAccessoryView of the first responder along the top of the keyboard. For example, in this answer, the dark bar with the left and right arrows and the “Done” button is an inputAccessoryView.
So the easiest way for you to get a thin line on top of the keyboard is probably to create a one-point tall view and set it as the inputAccessoryView of your text field or text view. Set the background color of the accessory view to a dark color.
Easiest way to do this to add an inpustAccessoryView for textFiled when it becomes first responder and the system presents the on-screen keyboard.
Try this out:
Swift 3:
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
let separatorView = UIView(frame:CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 1))
separatorView.backgroundColor = UIColor.lightGray
textField.inputAccessoryView = separatorView
return true
}
So as Rob already wrote, the AccessoryView is the correct and smoothest solution, because Apple solves all animations, displaying and so on, in a correct way so no worry to take care about anything else.
I solved it as an extension. I wanted it to display a small devider on the bottom so i have a closed Numeric keyboard:
Swift 2.0:
extension UITextField {
override public func becomeFirstResponder() -> Bool {
self.addSeparatorInputAccessoryIfNumericKeyboardOnPhone()
return super.becomeFirstResponder()
}
func addSeparatorInputAccessoryIfNumericKeyboardOnPhone() {
guard self.keyboardType == .NumberPad else {
return
}
guard UIDevice.currentDevice().userInterfaceIdiom == .Phone else {
return
}
let view = UIView(frame: CGRectMake(0, 0, 0, 1 / UIScreen.mainScreen().scale))
view.backgroundColor = UIColor.lightGrayColor()
self.inputAccessoryView = view
}
}
I am attempting to animate a tab bar to move from below the bottom of the screen to the top while simultaneously adjusting a view's height to shrink by the height of the tab bar. Essentially, I have a "hidden" tab bar that when it unhides should animate into view and the displayView should adjust for the space the tab bar now takes up.
However, the animation is jumpy for the display view. It seems that the display view animates fine, but the subviews automatically adjust their height without any animation. Any direction on fixing this would be appreciated.
I will accept aid in either objective-c or swift, as the translation is fairly easy.
//Displays tab bar with slide up animation. If animated is false, all other params are unused
func displayTabBar(animated:Bool, duration:NSTimeInterval = 0.5, delay:NSTimeInterval = 0, options:UIViewAnimationOptions = UIViewAnimationOptions.CurveLinear, completion:((Bool) -> Void)? = nil){
if(animated){
UIView.animateWithDuration(duration, delay: delay, options: options, animations: {
self.adjustTabBarDisplayed()
}, completion: completion)
UIView.animateWithDuration(duration, delay: delay, options: options, animations: {
self.adjustDisplayViewTabDisplayed()
}, completion: nil)
}
else{
self.adjustTabBarDisplayed()
self.adjustDisplayViewTabDisplayed()
}
}
//Adjusts frame of tab bar to display tab bar
private func adjustTabBarDisplayed(){
self.tabBar.frame = CGRectMake(0,UIScreen.mainScreen().bounds.height - self.tabBar.bounds.height, self.tabBar.bounds.width, self.tabBar.bounds.height)
}
//Adjusts frame of display view to match displayed tab bar
private func adjustDisplayViewTabDisplayed(){
self.displayView.frame = CGRectMake(0,0,self.displayView.bounds.width, UIScreen.mainScreen().bounds.height - self.tabBar.bounds.height)
}
When you modify a view's size, it doesn't lay out its subviews immediately. Instead, it sets a flag indicating that it needs layout. Later, after the system has finished dispatching the event that ended up calling displayTabBar, it runs the display refresh code. The display refresh code finds views that have the needs-layout flag set and tells them to lay themselves out (by sending them layoutSubviews).
Here, you are changing your display view's size inside an animation block. Therefore change to your display view's frame will be animated. But the frames of its subviews are changing outside the animation block; they're changing later during the layout phase. You need to make them change inside the animation block.
Lucky for you, that's easy. Just call self.displayView.layoutIfNeeded() inside the animation block. Also, you only need one animation block, since all of the animation parameters are identical:
func displayTabBar(animated:Bool, duration:NSTimeInterval = 0.5, delay:NSTimeInterval = 0, options:UIViewAnimationOptions = UIViewAnimationOptions.CurveLinear, completion:((Bool) -> Void)? = nil){
if(animated){
UIView.animateWithDuration(duration, delay: delay, options: options, animations: {
self.adjustTabBarDisplayed()
self.adjustDisplayViewTabDisplayed()
// ADD THIS LINE
self.displayView.layoutIfNeeded()
}, completion: completion)
}
else{
self.adjustTabBarDisplayed()
self.adjustDisplayViewTabDisplayed()
}
}
Use the below line of code in animation block
scrollView.layoutIfNeeded()