Text Field Drops Below Keyboard Upon Entering Text in Text Field - ios

I have a strange issue with regard to entering text into a text field. I am currently using the code below. My code is modeled after the answer here.
class RocketViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate, NSFetchedResultsControllerDelegate {
var offsetY:CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(RocketViewController.keyboardFrameChangeNotification(notification:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
#objc func keyboardFrameChangeNotification(notification: Notification) {
if let userInfo = notification.userInfo {
let keyBoardFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect
let animationDuration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? Double ?? 0
let animationCurveRawValue = (userInfo[UIKeyboardAnimationCurveUserInfoKey] as? Int) ?? Int(UIViewAnimationOptions.curveEaseInOut.rawValue)
let animationCurve = UIViewAnimationOptions(rawValue: UInt(animationCurveRawValue))
if let _ = keyBoardFrame, keyBoardFrame!.intersects(self.mainStackView.frame) {
self.offsetY = self.mainStackView.frame.maxY - keyBoardFrame!.minY
UIView.animate(withDuration: animationDuration, delay: TimeInterval(0), options: animationCurve, animations: {
self.mainStackView.frame.origin.y = self.mainStackView.frame.origin.y - self.offsetY
self.rocketSelectTable.frame.origin.y = self.rocketSelectTable.frame.origin.y - self.offsetY
}, completion: nil)
} else {
if self.offsetY != 0 {
UIView.animate(withDuration: animationDuration, delay: TimeInterval(0), options: animationCurve, animations: {
self.mainStackView.frame.origin.y = self.mainStackView.frame.origin.y + self.offsetY
self.rocketSelectTable.frame.origin.y = self.rocketSelectTable.frame.origin.y + self.offsetY
self.offsetY = 0
}, completion: nil)
}
}
}
}
}
In my view I have a table view with a fetched results controller as its data source, and below that are the text fields in a stack view, called mainStackView, that are eventually saved in a core data store.
I have gone through several iterations of this code with the same result, whether I compute the offset off the first responder, or simply the stack view. When a text field becomes the first responder, the view slides up nicely with the keyboard. However, as soon as I attempt to type in the field, the view snaps back to its original position. I am sure I am making a newbie mistake, but I can't figure out what I am doing wrong, and I have found nothing in my searches, except a similar question for android. Thanks in advance.

While I have not determined why I was seeing the behavior with the text field that I was seeing with changing the frame, I was able to stop the behavior by using a CGAffineTransform instead. My code is now:
#objc func keyboardFrameChangeNotification(notification: Notification) {
if let userInfo = notification.userInfo {
let keyBoardFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect
let animationDuration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? Double ?? 0
let animationCurveRawValue = (userInfo[UIKeyboardAnimationCurveUserInfoKey] as? Int) ?? Int(UIViewAnimationOptions.curveEaseInOut.rawValue)
let animationCurve = UIViewAnimationOptions(rawValue: UInt(animationCurveRawValue))
if let _ = keyBoardFrame, keyBoardFrame!.intersects(self.mainStackView.frame) {
self.offsetY = self.mainStackView.frame.maxY - keyBoardFrame!.minY
let transformUp = CGAffineTransform.init(translationX: 0, y: (0 - self.offsetY))
UIView.animate(withDuration: animationDuration, delay: TimeInterval(0), options: animationCurve, animations: {
self.mainStackView.transform = transformUp
self.rocketSelectTable.transform = transformUp
}, completion: nil)
} else {
if self.offsetY != 0 {
UIView.animate(withDuration: animationDuration, delay: TimeInterval(0), options: animationCurve, animations: {
self.mainStackView.transform = CGAffineTransform.identity
self.rocketSelectTable.transform = CGAffineTransform.identity
self.offsetY = 0
}, completion: nil)
}
}
}
This code smoothly animates the movement of the views, and there is no snap back to the original position while typing in the text field. I hope this helps someone else.

Related

Swift: Type 'NotificationCenter' has no member 'default'

I have an Observable Object class to get keyboard height and animation duration. I've been using this code in my previous project without any problems. The Xcode versions are the same and one of them does not give an error, while the other gives an error.
import Foundation
import SwiftUI
import Combine
class KeyboardController: ObservableObject {
#Published var keyboardHeight: CGFloat = 0
#Published var height: CGFloat = 0
#Published var duration: CGFloat = 0
init() {
self.listenForKeyboardNotifications()
}
private func listenForKeyboardNotifications() {
NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification,
object: nil,
queue: .main) { (notification) in
guard
let userInfo = notification.userInfo,
let keyboardRect = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect,
let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? CGFloat
else { return }
self.duration = duration
self.height = keyboardRect.height
withAnimation(.spring(response: duration, dampingFraction: 1, blendDuration: 0)){
self.keyboardHeight = keyboardRect.height
}
}
NotificationCenter.default.addObserver(
forName: UIResponder.keyboardWillHideNotification,
object: nil,
queue: .main
) { (notification) in
guard
let userInfo = notification.userInfo,
let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? CGFloat
else { return }
self.duration = duration
withAnimation(.spring(response: duration, dampingFraction: 1, blendDuration: 0)){
self.keyboardHeight = 0
}
}
}
}
Errors:
As I mentioned above, the same code works fine in a different project in the same Xcode version. I would be very happy if you could help me with what the error or the problem I missed here is.
EDIT:
I can say that the problem is completely based on this project. It doesn't give an error in a new Xcode project.

Variable used within its own initial value in Swift 5

I try to write an animation with Swift 5, following are some codes
let animations:(() -> Void) = {
self.keyboardOPT.transform = CGAffineTransform(translationX: 0,y: -deltaY)
if duration > 0 {
let options = UIView.AnimationOptions(rawValue: UInt((userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as! NSNumber).intValue << 16))
UIView.animate(withDuration: duration, delay: 0, options: options, animations: animations, completion: nil)
} else {
animations()
}
}
But in animations: animations and animations() it shows error:
Variable used within its own initial value
You can not call itself when initializing.
You can achieve it like this also.
var animations:(() -> Void)!
animations = {
animations()
}

swift 2 animateWithDuration not repeating

I have been trying to get this animation to work for hours but to no avail. Here is a snapshot. So I have a price label that shows current price of the product, which is in a custom collectionviewcell. When the collectionView:willDisplayCell is called, I then call the below method on my custom collection view cell called animateDiscountPrice.
The animation i am trying to achieve is to add the compare_at price character by character to the end of the current price label. I tried setting the REPEAT option along with repeat count but the animation block is called only once and then the completion block is immediately called. There is no animation basically.
func animateDiscountPrice()
{
let priceText = NSMutableAttributedString(attributedString: self.priceLabel.attributedText!)
let originalPriceText = NSMutableAttributedString(attributedString: self.priceLabel.attributedText!)
let offset = priceText.length
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
if let compareAtString : String = self.productData["compare_at_price_min"] as? String {
let compareAtPrice = NSMutableAttributedString(string: "$\(compareAtString)", attributes: myAttribute )
var i = 0
let count = compareAtString.characters.count+1
dispatch_async(dispatch_get_main_queue(), {
UIView.animateWithDuration(NSTimeInterval(count), delay:0, options: [.Repeat, .Autoreverse, .AllowUserInteraction, .CurveLinear],
animations: {
UIView.setAnimationRepeatCount(Float(count))
priceText.appendAttributedString(compareAtPrice.attributedSubstringFromRange(NSMakeRange(i, 1)))
priceText.addAttribute(NSForegroundColorAttributeName, value: discountColor, range: NSMakeRange(offset, i+1))
self.priceLabel.attributedText = priceText
i = i + 1
},
completion: { (finished) in
if(finished)
{
originalPriceText.appendAttributedString(compareAtPrice)
originalPriceText.addAttribute(NSForegroundColorAttributeName, value: discountColor, range: NSMakeRange(offset, compareAtPrice.length))
//self.priceLabel.attributedText = originalPriceText
}
}
)
})
}
}
}
and in the log lines I can see the animation is called only once for each cell being displayed, and calling completion methods afterwards.

Starting second UIView animation undoes previous animation

In Swift I'm using UIView.animateWithDuration to animate a button and some text out of the frame. In the "completion" block, I perform a func called showQuestion which is another UIView animation. However, the first time the showQuestion function runs, the button and text that was previously hidden by the prior animation appears back in the frame, as if it had never moved.
I've checked a few questions on here, and none seem to directly address the issue.
EDIT #1
Here are the three functions.
func startQuiz(){
UIView.animateWithDuration(0.2, delay: 0.2, options: .CurveEaseIn, animations: {
self.goButton.frame.origin.y = self.view.frame.height + 5
self.languageLabel.frame.origin.y = self.view.frame.height + 5
self.label.frame.origin.y = self.view.frame.height + 5
}, completion: { finished in
self.generateFirstQuestion()
})
}
func generateFirstQuestion() {
var aBase = 0;
var qBase = 0;
if(self.lang == "fr") {
aBase = 25
qBase = 5
}
var offset = (self.currentQuestion * 5) + aBase
var question:String = self.questions[self.currentQuestion+qBase]
self.questionLabel.text = question
var answer1:String = self.answers[offset]
var answer2:String = self.answers[offset+1]
var answer3:String = self.answers[offset+2]
var answer4:String = self.answers[offset+3]
var answer5:String = self.answers[offset+4]
self.showQuestion()
}
func showQuestion(){
if(self.isAnimating){
UIView.animateWithDuration(0.2, delay: 0.2, options: .CurveEaseOut, animations: {
self.questionLabel.center.x = self.view.center.x
}, completion: { finished in
self.isAnimating = false
self.currentQuestion++
})
}
else {
var aBase = 0;
var qBase = 0;
if(self.lang == "fr") {
aBase = 25
qBase = 5
}
var offset = (self.currentQuestion * 5) + aBase
var question:String = self.questions[self.currentQuestion+qBase]
self.questionLabel.text = question
var answer1:String = self.answers[offset]
var answer2:String = self.answers[offset+1]
var answer3:String = self.answers[offset+2]
var answer4:String = self.answers[offset+3]
var answer5:String = self.answers[offset+4]
UIView.animateWithDuration(0.2, delay: 0.2, options: .CurveEaseIn, animations: {
self.questionLabel.frame.origin.x = 0-700
}, completion: { finished in
self.questionLabel.frame.origin.x = self.view.frame.width+5
self.isAnimating = true
self.showQuestion()
})
}
}

Swift custom Keyboard Error

I keep getting an Cannot convert expression's type '[NSObject : AnyObject]?' to 'NSDictionary' error and I don't know what to do. I tried everything, looked everywhere. Can you please help? I am creating a custom keyboard in SWIFT and I am totally new at this so i could definitely use the help.
// Called when `UIKeyboardWillShowNotification` is sent.
func keyboardWillShow(aNotification: NSNotification) {
let info = aNotification.userInfo as NSDictionary **<<<<<<<<<ERROR HERE>>>>>>>**
let sizeBegin = info.objectForKey(UIKeyboardFrameBeginUserInfoKey).CGRectValue().size
let sizeEnd = info.objectForKey(UIKeyboardFrameEndUserInfoKey).CGRectValue().size
let duration = info.objectForKey(UIKeyboardAnimationDurationUserInfoKey).doubleValue
let curve = info.objectForKey(UIKeyboardAnimationCurveUserInfoKey).integerValue
var animationCurve: UIViewAnimationCurve
if let value = UIViewAnimationCurve.fromRaw(curve) {
animationCurve = value
} else {
animationCurve = UIViewAnimationCurve.EaseInOut
}
let insets = UIEdgeInsets(top: 44, left: 0, bottom: sizeEnd.height, right: 0)
UIView.animateWithDuration(duration, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations: {
self.textView.contentInset = insets
self.textView.scrollIndicatorInsets = insets
}, completion: nil)
}
// Called when `UIKeyboardWillHideNotification` is sent.
func keyboardWillHide(aNotification: NSNotification) {
let info = aNotification.userInfo as NSDictionary **<<<<<<<<<<ERROR HERE>>>>>>**
let sizeBegin = info.objectForKey(UIKeyboardFrameBeginUserInfoKey).CGRectValue().size
let sizeEnd = info.objectForKey(UIKeyboardFrameEndUserInfoKey).CGRectValue().size
let duration = info.objectForKey(UIKeyboardAnimationDurationUserInfoKey).doubleValue
let curve = info.objectForKey(UIKeyboardAnimationCurveUserInfoKey).integerValue
var animationCurve: UIViewAnimationCurve
if let value = UIViewAnimationCurve.fromRaw(curve) {
animationCurve = value
} else {
animationCurve = UIViewAnimationCurve.EaseInOut
}
let insets = UIEdgeInsets(top: 44, left: 0, bottom: sizeEnd.height, right: 0)
UIView.animateWithDuration(duration, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations: {
self.textView.contentInset = insets
self.textView.scrollIndicatorInsets = insets
}, completion: nil)
}
Your dictionary is of type [NSObject:AnyObject]? which is an Optional type that must be unwrapped to be used. Since it is an Optional, it could be nil. One safe way of dealing with this is to use the nil coalescing operator ?? to unwrap it:
let info = (aNotification.userInfo ?? [:]) as NSDictionary
That will either unwrap your dictionary if it is not nil or give you a new empty dictionary if it is nil.

Resources